From 68b8f49df0d29fd5f849a45b81655e92b12db177 Mon Sep 17 00:00:00 2001 From: mwinter Date: Sun, 31 Dec 2023 16:42:38 +0100 Subject: [PATCH] now interval envelope is signed and adding sq2 --- lilypond/includes/part_I.ly | 28 +- lilypond/includes/part_II.ly | 28 +- lilypond/includes/part_III.ly | 28 +- lilypond/includes/part_IV.ly | 28 +- lilypond/score_template.pdf | Bin 1313294 -> 895830 bytes lilypond/string_quartet_1/includes/part_I.ly | 20 + lilypond/string_quartet_1/includes/part_II.ly | 20 + .../string_quartet_1/includes/part_III.ly | 20 + lilypond/string_quartet_1/includes/part_IV.ly | 20 + lilypond/string_quartet_1/score_template.ly | 153 + .../score_template.midi | Bin lilypond/string_quartet_1/score_template.pdf | Bin 0 -> 1328883 bytes open_stage_control/modules/custom_module.js | 4 +- .../seeds_and_ledgers_gui-backup000.json | 2859 +++++++++++++++++ open_stage_control/seeds_and_ledgers_gui.json | 10 +- records.json | 1 + .../piece_ledger/314s49e1/lilypond/part_I.ly | 54 + .../piece_ledger/314s49e1/lilypond/part_II.ly | 54 + .../314s49e1/lilypond/part_III.ly | 54 + .../piece_ledger/314s49e1/lilypond/part_IV.ly | 54 + .../piece_ledger/4c01589b/lilypond/part_I.ly | 38 + .../piece_ledger/4c01589b/lilypond/part_II.ly | 38 + .../4c01589b/lilypond/part_III.ly | 38 + .../piece_ledger/4c01589b/lilypond/part_IV.ly | 38 + .../piece_ledger/7e170ef8/lilypond/part_I.ly | 34 + .../piece_ledger/7e170ef8/lilypond/part_II.ly | 34 + .../7e170ef8/lilypond/part_III.ly | 34 + .../piece_ledger/7e170ef8/lilypond/part_IV.ly | 34 + .../piece_ledger_sq1_candidates_stitch.json | 4 +- ...iece_ledger_sq1_candidates_stitch.json_bak | 5 +- .../4200a90d/4200a90d_mus_model.json | 5 +- .../4a8a6e53/lilypond/part_I.ly | 2 +- .../61ce9067/61ce9067_code.scd | 945 ++++++ .../61ce9067/61ce9067_mus_model.json | 55 + .../61ce9067/lilypond/part_I.ly | 12 + .../61ce9067/lilypond/part_II.ly | 12 + .../61ce9067/lilypond/part_III.ly | 12 + .../61ce9067/lilypond/part_IV.ly | 12 + .../774ed940/774ed940_code.scd | 945 ++++++ .../774ed940/774ed940_mus_model.json | 70 + .../774ed940/lilypond/part_I.ly | 22 + .../774ed940/lilypond/part_II.ly | 22 + .../774ed940/lilypond/part_III.ly | 22 + .../774ed940/lilypond/part_IV.ly | 22 + .../tmp/tmp_mus_model.json | 85 +- resources/string_quartet_1.json | 25 + .../4200a90d/4200a90d_code.scd | 945 ++++++ .../4200a90d/4200a90d_mus_model.json | 58 + .../4200a90d/lilypond/part_I.ly | 18 + .../4200a90d/lilypond/part_II.ly | 18 + .../4200a90d/lilypond/part_III.ly | 18 + .../4200a90d/lilypond/part_IV.ly | 18 + .../43b009ff/43b009ff_code.scd | 945 ++++++ .../43b009ff/43b009ff_mus_model.json | 48 + .../43b009ff/lilypond/part_I.ly | 28 + .../43b009ff/lilypond/part_II.ly | 28 + .../43b009ff/lilypond/part_III.ly | 28 + .../43b009ff/lilypond/part_IV.ly | 28 + .../443ec222/443ec222_code.scd | 945 ++++++ .../443ec222/443ec222_mus_model.json | 54 + .../443ec222/lilypond/part_I.ly | 18 + .../443ec222/lilypond/part_II.ly | 18 + .../443ec222/lilypond/part_III.ly | 18 + .../443ec222/lilypond/part_IV.ly | 18 + .../4526b76b/4526b76b_code.scd | 943 ++++++ .../4526b76b/4526b76b_mus_model.json | 44 + .../46985d14/46985d14_code.scd | 1058 ++++++ .../46985d14/46985d14_mus_model.json | 85 + .../46985d14/lilypond/part_I.ly | 50 + .../46985d14/lilypond/part_II.ly | 50 + .../46985d14/lilypond/part_III.ly | 50 + .../46985d14/lilypond/part_IV.ly | 50 + .../490b1e6e/490b1e6e_code.scd | 1058 ++++++ .../490b1e6e/490b1e6e_mus_model.json | 63 + .../490b1e6e/lilypond/part_I.ly | 26 + .../490b1e6e/lilypond/part_II.ly | 26 + .../490b1e6e/lilypond/part_III.ly | 26 + .../490b1e6e/lilypond/part_IV.ly | 26 + .../4a8a6e53/4a8a6e53_code.scd | 1058 ++++++ .../4a8a6e53/4a8a6e53_mus_model.json | 97 + .../4a8a6e53/lilypond/part_I.ly | 56 + .../4a8a6e53/lilypond/part_II.ly | 56 + .../4a8a6e53/lilypond/part_III.ly | 56 + .../4a8a6e53/lilypond/part_IV.ly | 56 + .../4b7745df/4b7745df_code.scd | 945 ++++++ .../4b7745df/4b7745df_mus_model.json | 48 + .../4b7745df/lilypond/part_I.ly | 28 + .../4b7745df/lilypond/part_II.ly | 28 + .../4b7745df/lilypond/part_III.ly | 28 + .../4b7745df/lilypond/part_IV.ly | 28 + .../4e7d35e5/4e7d35e5_code.scd | 945 ++++++ .../4e7d35e5/4e7d35e5_mus_model.json | 52 + .../4e7d35e5/lilypond/part_I.ly | 14 + .../4e7d35e5/lilypond/part_II.ly | 14 + .../4e7d35e5/lilypond/part_III.ly | 14 + .../4e7d35e5/lilypond/part_IV.ly | 14 + .../52c9a980/52c9a980_code.scd | 945 ++++++ .../52c9a980/52c9a980_mus_model.json | 55 + .../52c9a980/lilypond/part_I.ly | 18 + .../52c9a980/lilypond/part_II.ly | 18 + .../52c9a980/lilypond/part_III.ly | 18 + .../52c9a980/lilypond/part_IV.ly | 18 + .../5e54c468/5e54c468_code.scd | 1058 ++++++ .../5e54c468/5e54c468_mus_model.json | 70 + .../5e54c468/lilypond/part_I.ly | 44 + .../5e54c468/lilypond/part_II.ly | 44 + .../5e54c468/lilypond/part_III.ly | 44 + .../5e54c468/lilypond/part_IV.ly | 44 + .../61ce9067/61ce9067_code.scd | 945 ++++++ .../61ce9067/61ce9067_mus_model.json | 55 + .../61ce9067/lilypond/part_I.ly | 10 + .../61ce9067/lilypond/part_II.ly | 10 + .../61ce9067/lilypond/part_III.ly | 10 + .../61ce9067/lilypond/part_IV.ly | 10 + .../62820081/62820081_code.scd | 945 ++++++ .../62820081/62820081_mus_model.json | 48 + .../631e2af1/631e2af1_code.scd | 943 ++++++ .../631e2af1/631e2af1_mus_model.json | 48 + .../66f6a618/66f6a618_code.scd | 1058 ++++++ .../66f6a618/66f6a618_mus_model.json | 91 + .../66f6a618/lilypond/part_I.ly | 70 + .../66f6a618/lilypond/part_II.ly | 70 + .../66f6a618/lilypond/part_III.ly | 70 + .../66f6a618/lilypond/part_IV.ly | 70 + .../6d635e88/6d635e88_code.scd | 945 ++++++ .../6d635e88/6d635e88_mus_model.json | 95 + .../6d635e88/lilypond/part_I.ly | 70 + .../6d635e88/lilypond/part_II.ly | 70 + .../6d635e88/lilypond/part_III.ly | 70 + .../6d635e88/lilypond/part_IV.ly | 70 + .../6ed95c4c/6ed95c4c_code.scd | 945 ++++++ .../6ed95c4c/6ed95c4c_mus_model.json | 66 + .../6ed95c4c/lilypond/part_I.ly | 42 + .../6ed95c4c/lilypond/part_II.ly | 42 + .../6ed95c4c/lilypond/part_III.ly | 42 + .../6ed95c4c/lilypond/part_IV.ly | 42 + .../6fb60ab6/6fb60ab6_code.scd | 1058 ++++++ .../6fb60ab6/6fb60ab6_mus_model.json | 56 + .../6fb60ab6/lilypond/part_I.ly | 28 + .../6fb60ab6/lilypond/part_II.ly | 28 + .../6fb60ab6/lilypond/part_III.ly | 28 + .../6fb60ab6/lilypond/part_IV.ly | 28 + .../761e4585/761e4585_code.scd | 1058 ++++++ .../761e4585/761e4585_mus_model.json | 80 + .../761e4585/lilypond/part_I.ly | 36 + .../761e4585/lilypond/part_II.ly | 36 + .../761e4585/lilypond/part_III.ly | 36 + .../761e4585/lilypond/part_IV.ly | 36 + .../774ed940/774ed940_code.scd | 945 ++++++ .../774ed940/774ed940_mus_model.json | 70 + .../774ed940/lilypond/part_I.ly | 22 + .../774ed940/lilypond/part_II.ly | 22 + .../774ed940/lilypond/part_III.ly | 22 + .../774ed940/lilypond/part_IV.ly | 22 + .../784130cc/784130cc_code.scd | 945 ++++++ .../784130cc/784130cc_mus_model.json | 53 + .../784130cc/lilypond/part_I.ly | 14 + .../784130cc/lilypond/part_II.ly | 14 + .../784130cc/lilypond/part_III.ly | 14 + .../784130cc/lilypond/part_IV.ly | 14 + .../79e0a4a7/79e0a4a7_code.scd | 943 ++++++ .../79e0a4a7/79e0a4a7_mus_model.json | 47 + .../79e0a4a7/lilypond/part_I.ly | 28 + .../79e0a4a7/lilypond/part_II.ly | 28 + .../79e0a4a7/lilypond/part_III.ly | 28 + .../79e0a4a7/lilypond/part_IV.ly | 28 + .../7d3c9a80/7d3c9a80_code.scd | 945 ++++++ .../7d3c9a80/7d3c9a80_mus_model.json | 48 + .../7d3c9a80/lilypond/part_I.ly | 30 + .../7d3c9a80/lilypond/part_II.ly | 30 + .../7d3c9a80/lilypond/part_III.ly | 30 + .../7d3c9a80/lilypond/part_IV.ly | 30 + .../7edbdceb/7edbdceb_code.scd | 945 ++++++ .../7edbdceb/7edbdceb_mus_model.json | 53 + .../7edbdceb/lilypond/part_I.ly | 14 + .../7edbdceb/lilypond/part_II.ly | 14 + .../7edbdceb/lilypond/part_III.ly | 14 + .../7edbdceb/lilypond/part_IV.ly | 14 + .../string_quartet_1/tmp/tmp_mus_model.json | 78 + resources/string_quartet_2.json | 16 + resources/string_quartet_2.json_bak | 15 + .../40119c5a/40119c5a_code.scd | 945 ++++++ .../40119c5a/40119c5a_mus_model.json | 58 + .../41e447bc/41e447bc_code.scd | 945 ++++++ .../41e447bc/41e447bc_mus_model.json | 64 + .../41e447bc/lilypond/part_I.ly | 20 + .../41e447bc/lilypond/part_II.ly | 20 + .../41e447bc/lilypond/part_III.ly | 20 + .../41e447bc/lilypond/part_IV.ly | 20 + .../42c3365b/42c3365b_code.scd | 945 ++++++ .../42c3365b/42c3365b_mus_model.json | 123 + .../4660b5c4/4660b5c4_code.scd | 945 ++++++ .../4660b5c4/4660b5c4_mus_model.json | 59 + .../4a8a6e53/4a8a6e53_code.scd | 1058 ++++++ .../4a8a6e53/4a8a6e53_mus_model.json | 97 + .../4a8a6e53/lilypond/part_I.ly | 56 + .../4a8a6e53/lilypond/part_II.ly | 56 + .../4a8a6e53/lilypond/part_III.ly | 56 + .../4a8a6e53/lilypond/part_IV.ly | 56 + .../4c059f33/4c059f33_code.scd | 945 ++++++ .../4c059f33/4c059f33_mus_model.json | 58 + .../4c059f33/lilypond/part_I.ly | 16 + .../4c059f33/lilypond/part_II.ly | 16 + .../4c059f33/lilypond/part_III.ly | 16 + .../4c059f33/lilypond/part_IV.ly | 16 + .../5201b8af/5201b8af_code.scd | 945 ++++++ .../5201b8af/5201b8af_mus_model.json | 58 + .../5201b8af/lilypond/part_I.ly | 16 + .../5201b8af/lilypond/part_II.ly | 16 + .../5201b8af/lilypond/part_III.ly | 16 + .../5201b8af/lilypond/part_IV.ly | 16 + .../525274f2/525274f2_code.scd | 945 ++++++ .../525274f2/525274f2_mus_model.json | 66 + .../525274f2/lilypond/part_I.ly | 14 + .../525274f2/lilypond/part_II.ly | 14 + .../525274f2/lilypond/part_III.ly | 14 + .../525274f2/lilypond/part_IV.ly | 14 + .../60adbbef/60adbbef_code.scd | 945 ++++++ .../60adbbef/60adbbef_mus_model.json | 65 + .../60adbbef/lilypond/part_I.ly | 38 + .../60adbbef/lilypond/part_II.ly | 38 + .../60adbbef/lilypond/part_III.ly | 38 + .../60adbbef/lilypond/part_IV.ly | 38 + .../61e92979/61e92979_code.scd | 945 ++++++ .../61e92979/61e92979_mus_model.json | 67 + .../62300302/62300302_code.scd | 945 ++++++ .../62300302/62300302_mus_model.json | 77 + .../62300302/lilypond/part_I.ly | 36 + .../62300302/lilypond/part_II.ly | 36 + .../62300302/lilypond/part_III.ly | 36 + .../62300302/lilypond/part_IV.ly | 36 + .../628706ec/628706ec_code.scd | 945 ++++++ .../628706ec/628706ec_mus_model.json | 117 + .../628706ec/lilypond/part_I.ly | 46 + .../628706ec/lilypond/part_II.ly | 46 + .../628706ec/lilypond/part_III.ly | 46 + .../628706ec/lilypond/part_IV.ly | 46 + .../63bf7942/63bf7942_code.scd | 945 ++++++ .../63bf7942/63bf7942_mus_model.json | 67 + .../6a7a8dd9/6a7a8dd9_code.scd | 945 ++++++ .../6a7a8dd9/6a7a8dd9_mus_model.json | 126 + .../71add9fc/71add9fc_code.scd | 945 ++++++ .../71add9fc/71add9fc_mus_model.json | 210 ++ .../72dc057f/72dc057f_code.scd | 945 ++++++ .../72dc057f/72dc057f_mus_model.json | 59 + .../72dc057f/lilypond/part_I.ly | 30 + .../72dc057f/lilypond/part_II.ly | 30 + .../72dc057f/lilypond/part_III.ly | 30 + .../72dc057f/lilypond/part_IV.ly | 30 + .../74307bb4/74307bb4_code.scd | 945 ++++++ .../74307bb4/74307bb4_mus_model.json | 120 + .../74307bb4/lilypond/part_I.ly | 142 + .../74307bb4/lilypond/part_II.ly | 142 + .../74307bb4/lilypond/part_III.ly | 142 + .../74307bb4/lilypond/part_IV.ly | 142 + .../74b8f8d9/74b8f8d9_code.scd | 945 ++++++ .../74b8f8d9/74b8f8d9_mus_model.json | 77 + .../74b8f8d9/lilypond/part_I.ly | 50 + .../74b8f8d9/lilypond/part_II.ly | 50 + .../74b8f8d9/lilypond/part_III.ly | 50 + .../74b8f8d9/lilypond/part_IV.ly | 50 + .../76e45e56/76e45e56_code.scd | 945 ++++++ .../76e45e56/76e45e56_mus_model.json | 71 + .../77b0d2dc/77b0d2dc_code.scd | 945 ++++++ .../77b0d2dc/77b0d2dc_mus_model.json | 59 + .../7bb0e931/7bb0e931_code.scd | 945 ++++++ .../7bb0e931/7bb0e931_mus_model.json | 69 + .../7df3df4c/7df3df4c_code.scd | 945 ++++++ .../7df3df4c/7df3df4c_mus_model.json | 59 + .../7fe1da13/7fe1da13_code.scd | 945 ++++++ .../7fe1da13/7fe1da13_mus_model.json | 198 ++ .../string_quartet_2/tmp/tmp_mus_model.json | 71 + resources/string_quartet_3.json | 21 + resources/string_quartet_3.json_bak | 20 + .../45fa07e8/45fa07e8_code.scd | 966 ++++++ .../45fa07e8/45fa07e8_mus_model.json | 443 +++ .../4ff624b0/4ff624b0_code.scd | 975 ++++++ .../4ff624b0/4ff624b0_mus_model.json | 552 ++++ .../5201b8af/5201b8af_code.scd | 945 ++++++ .../5201b8af/5201b8af_mus_model.json | 58 + .../5201b8af/lilypond/part_I.ly | 16 + .../5201b8af/lilypond/part_II.ly | 16 + .../5201b8af/lilypond/part_III.ly | 16 + .../5201b8af/lilypond/part_IV.ly | 16 + .../5488f7e9/5488f7e9_code.scd | 945 ++++++ .../5488f7e9/5488f7e9_mus_model.json | 163 + .../55bd25a1/55bd25a1_code.scd | 973 ++++++ .../55bd25a1/55bd25a1_mus_model.json | 548 ++++ .../55f9b81e/55f9b81e_code.scd | 958 ++++++ .../55f9b81e/55f9b81e_mus_model.json | 443 +++ .../577cf188/577cf188_code.scd | 945 ++++++ .../577cf188/577cf188_mus_model.json | 198 ++ .../5ec14635/5ec14635_code.scd | 946 ++++++ .../5ec14635/5ec14635_mus_model.json | 163 + .../69c568c6/69c568c6_code.scd | 973 ++++++ .../69c568c6/69c568c6_mus_model.json | 535 +++ .../6a9928d6/6a9928d6_code.scd | 973 ++++++ .../6a9928d6/6a9928d6_mus_model.json | 554 ++++ .../6f0f638f/6f0f638f_code.scd | 945 ++++++ .../6f0f638f/6f0f638f_mus_model.json | 105 + .../726a40c7/726a40c7_code.scd | 946 ++++++ .../726a40c7/726a40c7_mus_model.json | 163 + .../75316bf0/75316bf0_code.scd | 973 ++++++ .../75316bf0/75316bf0_mus_model.json | 550 ++++ .../781442dc/781442dc_code.scd | 945 ++++++ .../781442dc/781442dc_mus_model.json | 117 + .../7c2de94c/7c2de94c_code.scd | 945 ++++++ .../7c2de94c/7c2de94c_mus_model.json | 163 + .../7e230015/7e230015_code.scd | 975 ++++++ .../7e230015/7e230015_mus_model.json | 532 +++ .../string_quartet_3/tmp/tmp_mus_model.json | 532 +++ resources/string_quartet_3_rise.json | 19 + resources/string_quartet_3_rise.json_bak | 18 + .../43214be8/43214be8_code.scd | 981 ++++++ .../43214be8/43214be8_mus_model.json | 158 + .../43214be8/lilypond/part_I.ly | 48 + .../43214be8/lilypond/part_II.ly | 48 + .../43214be8/lilypond/part_III.ly | 48 + .../43214be8/lilypond/part_IV.ly | 48 + .../44490863/44490863_code.scd | 981 ++++++ .../44490863/44490863_mus_model.json | 154 + .../44490863/lilypond/part_I.ly | 48 + .../44490863/lilypond/part_II.ly | 48 + .../44490863/lilypond/part_III.ly | 48 + .../44490863/lilypond/part_IV.ly | 48 + .../4874dd07/4874dd07_code.scd | 979 ++++++ .../4874dd07/4874dd07_mus_model.json | 121 + .../4874dd07/lilypond/part_I.ly | 38 + .../4874dd07/lilypond/part_II.ly | 38 + .../4874dd07/lilypond/part_III.ly | 38 + .../4874dd07/lilypond/part_IV.ly | 38 + .../4b40ed47/4b40ed47_code.scd | 981 ++++++ .../4b40ed47/4b40ed47_mus_model.json | 86 + .../4b40ed47/lilypond/part_I.ly | 22 + .../4b40ed47/lilypond/part_II.ly | 22 + .../4b40ed47/lilypond/part_III.ly | 22 + .../4b40ed47/lilypond/part_IV.ly | 22 + .../4bf1af12/4bf1af12_code.scd | 981 ++++++ .../4bf1af12/4bf1af12_mus_model.json | 87 + .../4bf1af12/lilypond/part_I.ly | 22 + .../4bf1af12/lilypond/part_II.ly | 22 + .../4bf1af12/lilypond/part_III.ly | 22 + .../4bf1af12/lilypond/part_IV.ly | 22 + .../4c745666/4c745666_code.scd | 977 ++++++ .../4c745666/4c745666_mus_model.json | 166 + .../4e9f1dcc/4e9f1dcc_code.scd | 981 ++++++ .../4e9f1dcc/4e9f1dcc_mus_model.json | 86 + .../4e9f1dcc/lilypond/part_I.ly | 22 + .../4e9f1dcc/lilypond/part_II.ly | 22 + .../4e9f1dcc/lilypond/part_III.ly | 22 + .../4e9f1dcc/lilypond/part_IV.ly | 22 + .../521654f8/521654f8_code.scd | 981 ++++++ .../521654f8/521654f8_mus_model.json | 87 + .../521654f8/lilypond/part_I.ly | 22 + .../521654f8/lilypond/part_II.ly | 22 + .../521654f8/lilypond/part_III.ly | 22 + .../521654f8/lilypond/part_IV.ly | 22 + .../531df78c/531df78c_code.scd | 981 ++++++ .../531df78c/531df78c_mus_model.json | 157 + .../531df78c/lilypond/part_I.ly | 48 + .../531df78c/lilypond/part_II.ly | 48 + .../531df78c/lilypond/part_III.ly | 48 + .../531df78c/lilypond/part_IV.ly | 48 + .../553fbac7/553fbac7_code.scd | 979 ++++++ .../553fbac7/553fbac7_mus_model.json | 88 + .../57ef90e6/57ef90e6_code.scd | 975 ++++++ .../57ef90e6/57ef90e6_mus_model.json | 166 + .../6522664c/6522664c_code.scd | 981 ++++++ .../6522664c/6522664c_mus_model.json | 85 + .../6522664c/lilypond/part_I.ly | 20 + .../6522664c/lilypond/part_II.ly | 20 + .../6522664c/lilypond/part_III.ly | 20 + .../6522664c/lilypond/part_IV.ly | 20 + .../6db2efcc/6db2efcc_code.scd | 981 ++++++ .../6db2efcc/6db2efcc_mus_model.json | 86 + .../6db2efcc/lilypond/part_I.ly | 22 + .../6db2efcc/lilypond/part_II.ly | 22 + .../6db2efcc/lilypond/part_III.ly | 22 + .../6db2efcc/lilypond/part_IV.ly | 22 + .../7276dc78/7276dc78_code.scd | 981 ++++++ .../7276dc78/7276dc78_mus_model.json | 158 + .../7276dc78/lilypond/part_I.ly | 48 + .../7276dc78/lilypond/part_II.ly | 48 + .../7276dc78/lilypond/part_III.ly | 48 + .../7276dc78/lilypond/part_IV.ly | 48 + .../78a94ed1/78a94ed1_code.scd | 981 ++++++ .../78a94ed1/78a94ed1_mus_model.json | 90 + .../78a94ed1/lilypond/part_I.ly | 26 + .../78a94ed1/lilypond/part_II.ly | 26 + .../78a94ed1/lilypond/part_III.ly | 26 + .../78a94ed1/lilypond/part_IV.ly | 26 + .../7c30c182/7c30c182_code.scd | 981 ++++++ .../7c30c182/7c30c182_mus_model.json | 271 ++ .../7ede7adb/7ede7adb_code.scd | 981 ++++++ .../7ede7adb/7ede7adb_mus_model.json | 83 + .../7ede7adb/lilypond/part_I.ly | 18 + .../7ede7adb/lilypond/part_II.ly | 18 + .../7ede7adb/lilypond/part_III.ly | 18 + .../7ede7adb/lilypond/part_IV.ly | 18 + .../tmp/tmp_mus_model.json | 521 +++ supercollider/material_tweak.scd | 37 +- supercollider/morph_quickproto.scd | 14 + supercollider/seeds_and_ledgers_backend.scd | 8 +- .../seeds_and_ledgers_backend_rise.scd | 992 ++++++ supercollider/seeds_and_ledgers_main.scd | 3 +- .../seeds_and_ledgers_transcriber.scd | 4 +- 406 files changed, 102640 insertions(+), 149 deletions(-) create mode 100644 lilypond/string_quartet_1/includes/part_I.ly create mode 100644 lilypond/string_quartet_1/includes/part_II.ly create mode 100644 lilypond/string_quartet_1/includes/part_III.ly create mode 100644 lilypond/string_quartet_1/includes/part_IV.ly create mode 100644 lilypond/string_quartet_1/score_template.ly rename lilypond/{ => string_quartet_1}/score_template.midi (100%) create mode 100644 lilypond/string_quartet_1/score_template.pdf create mode 100644 open_stage_control/seeds_and_ledgers_gui-backup000.json create mode 100644 records.json create mode 100644 resources/piece_ledger/314s49e1/lilypond/part_I.ly create mode 100644 resources/piece_ledger/314s49e1/lilypond/part_II.ly create mode 100644 resources/piece_ledger/314s49e1/lilypond/part_III.ly create mode 100644 resources/piece_ledger/314s49e1/lilypond/part_IV.ly create mode 100644 resources/piece_ledger/4c01589b/lilypond/part_I.ly create mode 100644 resources/piece_ledger/4c01589b/lilypond/part_II.ly create mode 100644 resources/piece_ledger/4c01589b/lilypond/part_III.ly create mode 100644 resources/piece_ledger/4c01589b/lilypond/part_IV.ly create mode 100644 resources/piece_ledger/7e170ef8/lilypond/part_I.ly create mode 100644 resources/piece_ledger/7e170ef8/lilypond/part_II.ly create mode 100644 resources/piece_ledger/7e170ef8/lilypond/part_III.ly create mode 100644 resources/piece_ledger/7e170ef8/lilypond/part_IV.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/61ce9067/61ce9067_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/61ce9067/61ce9067_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_IV.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/774ed940/774ed940_code.scd create mode 100644 resources/piece_ledger_sq1_candidates_stitch/774ed940/774ed940_mus_model.json create mode 100644 resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_I.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_II.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_III.ly create mode 100644 resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1.json create mode 100644 resources/string_quartet_1/4200a90d/4200a90d_code.scd create mode 100644 resources/string_quartet_1/4200a90d/4200a90d_mus_model.json create mode 100644 resources/string_quartet_1/4200a90d/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/4200a90d/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/4200a90d/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/4200a90d/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/43b009ff/43b009ff_code.scd create mode 100644 resources/string_quartet_1/43b009ff/43b009ff_mus_model.json create mode 100644 resources/string_quartet_1/43b009ff/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/43b009ff/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/43b009ff/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/43b009ff/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/443ec222/443ec222_code.scd create mode 100644 resources/string_quartet_1/443ec222/443ec222_mus_model.json create mode 100644 resources/string_quartet_1/443ec222/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/443ec222/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/443ec222/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/443ec222/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/4526b76b/4526b76b_code.scd create mode 100644 resources/string_quartet_1/4526b76b/4526b76b_mus_model.json create mode 100644 resources/string_quartet_1/46985d14/46985d14_code.scd create mode 100644 resources/string_quartet_1/46985d14/46985d14_mus_model.json create mode 100644 resources/string_quartet_1/46985d14/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/46985d14/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/46985d14/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/46985d14/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/490b1e6e/490b1e6e_code.scd create mode 100644 resources/string_quartet_1/490b1e6e/490b1e6e_mus_model.json create mode 100644 resources/string_quartet_1/490b1e6e/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/490b1e6e/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/490b1e6e/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/490b1e6e/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/4a8a6e53/4a8a6e53_code.scd create mode 100644 resources/string_quartet_1/4a8a6e53/4a8a6e53_mus_model.json create mode 100644 resources/string_quartet_1/4a8a6e53/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/4a8a6e53/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/4a8a6e53/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/4a8a6e53/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/4b7745df/4b7745df_code.scd create mode 100644 resources/string_quartet_1/4b7745df/4b7745df_mus_model.json create mode 100644 resources/string_quartet_1/4b7745df/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/4b7745df/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/4b7745df/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/4b7745df/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/4e7d35e5/4e7d35e5_code.scd create mode 100644 resources/string_quartet_1/4e7d35e5/4e7d35e5_mus_model.json create mode 100644 resources/string_quartet_1/4e7d35e5/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/4e7d35e5/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/4e7d35e5/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/4e7d35e5/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/52c9a980/52c9a980_code.scd create mode 100644 resources/string_quartet_1/52c9a980/52c9a980_mus_model.json create mode 100644 resources/string_quartet_1/52c9a980/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/52c9a980/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/52c9a980/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/52c9a980/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/5e54c468/5e54c468_code.scd create mode 100644 resources/string_quartet_1/5e54c468/5e54c468_mus_model.json create mode 100644 resources/string_quartet_1/5e54c468/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/5e54c468/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/5e54c468/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/5e54c468/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/61ce9067/61ce9067_code.scd create mode 100644 resources/string_quartet_1/61ce9067/61ce9067_mus_model.json create mode 100644 resources/string_quartet_1/61ce9067/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/61ce9067/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/61ce9067/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/61ce9067/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/62820081/62820081_code.scd create mode 100644 resources/string_quartet_1/62820081/62820081_mus_model.json create mode 100644 resources/string_quartet_1/631e2af1/631e2af1_code.scd create mode 100644 resources/string_quartet_1/631e2af1/631e2af1_mus_model.json create mode 100644 resources/string_quartet_1/66f6a618/66f6a618_code.scd create mode 100644 resources/string_quartet_1/66f6a618/66f6a618_mus_model.json create mode 100644 resources/string_quartet_1/66f6a618/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/66f6a618/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/66f6a618/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/66f6a618/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/6d635e88/6d635e88_code.scd create mode 100644 resources/string_quartet_1/6d635e88/6d635e88_mus_model.json create mode 100644 resources/string_quartet_1/6d635e88/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/6d635e88/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/6d635e88/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/6d635e88/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/6ed95c4c/6ed95c4c_code.scd create mode 100644 resources/string_quartet_1/6ed95c4c/6ed95c4c_mus_model.json create mode 100644 resources/string_quartet_1/6ed95c4c/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/6ed95c4c/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/6ed95c4c/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/6ed95c4c/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/6fb60ab6/6fb60ab6_code.scd create mode 100644 resources/string_quartet_1/6fb60ab6/6fb60ab6_mus_model.json create mode 100644 resources/string_quartet_1/6fb60ab6/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/6fb60ab6/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/6fb60ab6/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/6fb60ab6/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/761e4585/761e4585_code.scd create mode 100644 resources/string_quartet_1/761e4585/761e4585_mus_model.json create mode 100644 resources/string_quartet_1/761e4585/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/761e4585/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/761e4585/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/761e4585/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/774ed940/774ed940_code.scd create mode 100644 resources/string_quartet_1/774ed940/774ed940_mus_model.json create mode 100644 resources/string_quartet_1/774ed940/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/774ed940/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/774ed940/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/774ed940/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/784130cc/784130cc_code.scd create mode 100644 resources/string_quartet_1/784130cc/784130cc_mus_model.json create mode 100644 resources/string_quartet_1/784130cc/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/784130cc/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/784130cc/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/784130cc/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/79e0a4a7/79e0a4a7_code.scd create mode 100644 resources/string_quartet_1/79e0a4a7/79e0a4a7_mus_model.json create mode 100644 resources/string_quartet_1/79e0a4a7/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/79e0a4a7/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/79e0a4a7/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/79e0a4a7/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/7d3c9a80/7d3c9a80_code.scd create mode 100644 resources/string_quartet_1/7d3c9a80/7d3c9a80_mus_model.json create mode 100644 resources/string_quartet_1/7d3c9a80/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/7d3c9a80/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/7d3c9a80/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/7d3c9a80/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/7edbdceb/7edbdceb_code.scd create mode 100644 resources/string_quartet_1/7edbdceb/7edbdceb_mus_model.json create mode 100644 resources/string_quartet_1/7edbdceb/lilypond/part_I.ly create mode 100644 resources/string_quartet_1/7edbdceb/lilypond/part_II.ly create mode 100644 resources/string_quartet_1/7edbdceb/lilypond/part_III.ly create mode 100644 resources/string_quartet_1/7edbdceb/lilypond/part_IV.ly create mode 100644 resources/string_quartet_1/tmp/tmp_mus_model.json create mode 100644 resources/string_quartet_2.json create mode 100644 resources/string_quartet_2.json_bak create mode 100644 resources/string_quartet_2/40119c5a/40119c5a_code.scd create mode 100644 resources/string_quartet_2/40119c5a/40119c5a_mus_model.json create mode 100644 resources/string_quartet_2/41e447bc/41e447bc_code.scd create mode 100644 resources/string_quartet_2/41e447bc/41e447bc_mus_model.json create mode 100644 resources/string_quartet_2/41e447bc/lilypond/part_I.ly create mode 100644 resources/string_quartet_2/41e447bc/lilypond/part_II.ly create mode 100644 resources/string_quartet_2/41e447bc/lilypond/part_III.ly create mode 100644 resources/string_quartet_2/41e447bc/lilypond/part_IV.ly create mode 100644 resources/string_quartet_2/42c3365b/42c3365b_code.scd create mode 100644 resources/string_quartet_2/42c3365b/42c3365b_mus_model.json create mode 100644 resources/string_quartet_2/4660b5c4/4660b5c4_code.scd create mode 100644 resources/string_quartet_2/4660b5c4/4660b5c4_mus_model.json create mode 100644 resources/string_quartet_2/4a8a6e53/4a8a6e53_code.scd create mode 100644 resources/string_quartet_2/4a8a6e53/4a8a6e53_mus_model.json create mode 100644 resources/string_quartet_2/4a8a6e53/lilypond/part_I.ly create mode 100644 resources/string_quartet_2/4a8a6e53/lilypond/part_II.ly create mode 100644 resources/string_quartet_2/4a8a6e53/lilypond/part_III.ly create mode 100644 resources/string_quartet_2/4a8a6e53/lilypond/part_IV.ly create mode 100644 resources/string_quartet_2/4c059f33/4c059f33_code.scd create mode 100644 resources/string_quartet_2/4c059f33/4c059f33_mus_model.json create mode 100644 resources/string_quartet_2/4c059f33/lilypond/part_I.ly create mode 100644 resources/string_quartet_2/4c059f33/lilypond/part_II.ly create mode 100644 resources/string_quartet_2/4c059f33/lilypond/part_III.ly create mode 100644 resources/string_quartet_2/4c059f33/lilypond/part_IV.ly create mode 100644 resources/string_quartet_2/5201b8af/5201b8af_code.scd create mode 100644 resources/string_quartet_2/5201b8af/5201b8af_mus_model.json create mode 100644 resources/string_quartet_2/5201b8af/lilypond/part_I.ly create mode 100644 resources/string_quartet_2/5201b8af/lilypond/part_II.ly create mode 100644 resources/string_quartet_2/5201b8af/lilypond/part_III.ly create mode 100644 resources/string_quartet_2/5201b8af/lilypond/part_IV.ly create mode 100644 resources/string_quartet_2/525274f2/525274f2_code.scd create mode 100644 resources/string_quartet_2/525274f2/525274f2_mus_model.json create mode 100644 resources/string_quartet_2/525274f2/lilypond/part_I.ly create mode 100644 resources/string_quartet_2/525274f2/lilypond/part_II.ly create mode 100644 resources/string_quartet_2/525274f2/lilypond/part_III.ly create mode 100644 resources/string_quartet_2/525274f2/lilypond/part_IV.ly create mode 100644 resources/string_quartet_2/60adbbef/60adbbef_code.scd create mode 100644 resources/string_quartet_2/60adbbef/60adbbef_mus_model.json create mode 100644 resources/string_quartet_2/60adbbef/lilypond/part_I.ly create mode 100644 resources/string_quartet_2/60adbbef/lilypond/part_II.ly create mode 100644 resources/string_quartet_2/60adbbef/lilypond/part_III.ly create mode 100644 resources/string_quartet_2/60adbbef/lilypond/part_IV.ly create mode 100644 resources/string_quartet_2/61e92979/61e92979_code.scd create mode 100644 resources/string_quartet_2/61e92979/61e92979_mus_model.json create mode 100644 resources/string_quartet_2/62300302/62300302_code.scd create mode 100644 resources/string_quartet_2/62300302/62300302_mus_model.json create mode 100644 resources/string_quartet_2/62300302/lilypond/part_I.ly create mode 100644 resources/string_quartet_2/62300302/lilypond/part_II.ly create mode 100644 resources/string_quartet_2/62300302/lilypond/part_III.ly create mode 100644 resources/string_quartet_2/62300302/lilypond/part_IV.ly create mode 100644 resources/string_quartet_2/628706ec/628706ec_code.scd create mode 100644 resources/string_quartet_2/628706ec/628706ec_mus_model.json create mode 100644 resources/string_quartet_2/628706ec/lilypond/part_I.ly create mode 100644 resources/string_quartet_2/628706ec/lilypond/part_II.ly create mode 100644 resources/string_quartet_2/628706ec/lilypond/part_III.ly create mode 100644 resources/string_quartet_2/628706ec/lilypond/part_IV.ly create mode 100644 resources/string_quartet_2/63bf7942/63bf7942_code.scd create mode 100644 resources/string_quartet_2/63bf7942/63bf7942_mus_model.json create mode 100644 resources/string_quartet_2/6a7a8dd9/6a7a8dd9_code.scd create mode 100644 resources/string_quartet_2/6a7a8dd9/6a7a8dd9_mus_model.json create mode 100644 resources/string_quartet_2/71add9fc/71add9fc_code.scd create mode 100644 resources/string_quartet_2/71add9fc/71add9fc_mus_model.json create mode 100644 resources/string_quartet_2/72dc057f/72dc057f_code.scd create mode 100644 resources/string_quartet_2/72dc057f/72dc057f_mus_model.json create mode 100644 resources/string_quartet_2/72dc057f/lilypond/part_I.ly create mode 100644 resources/string_quartet_2/72dc057f/lilypond/part_II.ly create mode 100644 resources/string_quartet_2/72dc057f/lilypond/part_III.ly create mode 100644 resources/string_quartet_2/72dc057f/lilypond/part_IV.ly create mode 100644 resources/string_quartet_2/74307bb4/74307bb4_code.scd create mode 100644 resources/string_quartet_2/74307bb4/74307bb4_mus_model.json create mode 100644 resources/string_quartet_2/74307bb4/lilypond/part_I.ly create mode 100644 resources/string_quartet_2/74307bb4/lilypond/part_II.ly create mode 100644 resources/string_quartet_2/74307bb4/lilypond/part_III.ly create mode 100644 resources/string_quartet_2/74307bb4/lilypond/part_IV.ly create mode 100644 resources/string_quartet_2/74b8f8d9/74b8f8d9_code.scd create mode 100644 resources/string_quartet_2/74b8f8d9/74b8f8d9_mus_model.json create mode 100644 resources/string_quartet_2/74b8f8d9/lilypond/part_I.ly create mode 100644 resources/string_quartet_2/74b8f8d9/lilypond/part_II.ly create mode 100644 resources/string_quartet_2/74b8f8d9/lilypond/part_III.ly create mode 100644 resources/string_quartet_2/74b8f8d9/lilypond/part_IV.ly create mode 100644 resources/string_quartet_2/76e45e56/76e45e56_code.scd create mode 100644 resources/string_quartet_2/76e45e56/76e45e56_mus_model.json create mode 100644 resources/string_quartet_2/77b0d2dc/77b0d2dc_code.scd create mode 100644 resources/string_quartet_2/77b0d2dc/77b0d2dc_mus_model.json create mode 100644 resources/string_quartet_2/7bb0e931/7bb0e931_code.scd create mode 100644 resources/string_quartet_2/7bb0e931/7bb0e931_mus_model.json create mode 100644 resources/string_quartet_2/7df3df4c/7df3df4c_code.scd create mode 100644 resources/string_quartet_2/7df3df4c/7df3df4c_mus_model.json create mode 100644 resources/string_quartet_2/7fe1da13/7fe1da13_code.scd create mode 100644 resources/string_quartet_2/7fe1da13/7fe1da13_mus_model.json create mode 100644 resources/string_quartet_2/tmp/tmp_mus_model.json create mode 100644 resources/string_quartet_3.json create mode 100644 resources/string_quartet_3.json_bak create mode 100644 resources/string_quartet_3/45fa07e8/45fa07e8_code.scd create mode 100644 resources/string_quartet_3/45fa07e8/45fa07e8_mus_model.json create mode 100644 resources/string_quartet_3/4ff624b0/4ff624b0_code.scd create mode 100644 resources/string_quartet_3/4ff624b0/4ff624b0_mus_model.json create mode 100644 resources/string_quartet_3/5201b8af/5201b8af_code.scd create mode 100644 resources/string_quartet_3/5201b8af/5201b8af_mus_model.json create mode 100644 resources/string_quartet_3/5201b8af/lilypond/part_I.ly create mode 100644 resources/string_quartet_3/5201b8af/lilypond/part_II.ly create mode 100644 resources/string_quartet_3/5201b8af/lilypond/part_III.ly create mode 100644 resources/string_quartet_3/5201b8af/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3/5488f7e9/5488f7e9_code.scd create mode 100644 resources/string_quartet_3/5488f7e9/5488f7e9_mus_model.json create mode 100644 resources/string_quartet_3/55bd25a1/55bd25a1_code.scd create mode 100644 resources/string_quartet_3/55bd25a1/55bd25a1_mus_model.json create mode 100644 resources/string_quartet_3/55f9b81e/55f9b81e_code.scd create mode 100644 resources/string_quartet_3/55f9b81e/55f9b81e_mus_model.json create mode 100644 resources/string_quartet_3/577cf188/577cf188_code.scd create mode 100644 resources/string_quartet_3/577cf188/577cf188_mus_model.json create mode 100644 resources/string_quartet_3/5ec14635/5ec14635_code.scd create mode 100644 resources/string_quartet_3/5ec14635/5ec14635_mus_model.json create mode 100644 resources/string_quartet_3/69c568c6/69c568c6_code.scd create mode 100644 resources/string_quartet_3/69c568c6/69c568c6_mus_model.json create mode 100644 resources/string_quartet_3/6a9928d6/6a9928d6_code.scd create mode 100644 resources/string_quartet_3/6a9928d6/6a9928d6_mus_model.json create mode 100644 resources/string_quartet_3/6f0f638f/6f0f638f_code.scd create mode 100644 resources/string_quartet_3/6f0f638f/6f0f638f_mus_model.json create mode 100644 resources/string_quartet_3/726a40c7/726a40c7_code.scd create mode 100644 resources/string_quartet_3/726a40c7/726a40c7_mus_model.json create mode 100644 resources/string_quartet_3/75316bf0/75316bf0_code.scd create mode 100644 resources/string_quartet_3/75316bf0/75316bf0_mus_model.json create mode 100644 resources/string_quartet_3/781442dc/781442dc_code.scd create mode 100644 resources/string_quartet_3/781442dc/781442dc_mus_model.json create mode 100644 resources/string_quartet_3/7c2de94c/7c2de94c_code.scd create mode 100644 resources/string_quartet_3/7c2de94c/7c2de94c_mus_model.json create mode 100644 resources/string_quartet_3/7e230015/7e230015_code.scd create mode 100644 resources/string_quartet_3/7e230015/7e230015_mus_model.json create mode 100644 resources/string_quartet_3/tmp/tmp_mus_model.json create mode 100644 resources/string_quartet_3_rise.json create mode 100644 resources/string_quartet_3_rise.json_bak create mode 100644 resources/string_quartet_3_rise/43214be8/43214be8_code.scd create mode 100644 resources/string_quartet_3_rise/43214be8/43214be8_mus_model.json create mode 100644 resources/string_quartet_3_rise/43214be8/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/43214be8/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/43214be8/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/43214be8/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/44490863/44490863_code.scd create mode 100644 resources/string_quartet_3_rise/44490863/44490863_mus_model.json create mode 100644 resources/string_quartet_3_rise/44490863/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/44490863/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/44490863/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/44490863/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/4874dd07/4874dd07_code.scd create mode 100644 resources/string_quartet_3_rise/4874dd07/4874dd07_mus_model.json create mode 100644 resources/string_quartet_3_rise/4874dd07/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/4874dd07/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/4874dd07/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/4874dd07/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/4b40ed47/4b40ed47_code.scd create mode 100644 resources/string_quartet_3_rise/4b40ed47/4b40ed47_mus_model.json create mode 100644 resources/string_quartet_3_rise/4b40ed47/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/4b40ed47/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/4b40ed47/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/4b40ed47/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/4bf1af12/4bf1af12_code.scd create mode 100644 resources/string_quartet_3_rise/4bf1af12/4bf1af12_mus_model.json create mode 100644 resources/string_quartet_3_rise/4bf1af12/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/4bf1af12/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/4bf1af12/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/4bf1af12/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/4c745666/4c745666_code.scd create mode 100644 resources/string_quartet_3_rise/4c745666/4c745666_mus_model.json create mode 100644 resources/string_quartet_3_rise/4e9f1dcc/4e9f1dcc_code.scd create mode 100644 resources/string_quartet_3_rise/4e9f1dcc/4e9f1dcc_mus_model.json create mode 100644 resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/521654f8/521654f8_code.scd create mode 100644 resources/string_quartet_3_rise/521654f8/521654f8_mus_model.json create mode 100644 resources/string_quartet_3_rise/521654f8/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/521654f8/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/521654f8/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/521654f8/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/531df78c/531df78c_code.scd create mode 100644 resources/string_quartet_3_rise/531df78c/531df78c_mus_model.json create mode 100644 resources/string_quartet_3_rise/531df78c/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/531df78c/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/531df78c/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/531df78c/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/553fbac7/553fbac7_code.scd create mode 100644 resources/string_quartet_3_rise/553fbac7/553fbac7_mus_model.json create mode 100644 resources/string_quartet_3_rise/57ef90e6/57ef90e6_code.scd create mode 100644 resources/string_quartet_3_rise/57ef90e6/57ef90e6_mus_model.json create mode 100644 resources/string_quartet_3_rise/6522664c/6522664c_code.scd create mode 100644 resources/string_quartet_3_rise/6522664c/6522664c_mus_model.json create mode 100644 resources/string_quartet_3_rise/6522664c/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/6522664c/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/6522664c/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/6522664c/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/6db2efcc/6db2efcc_code.scd create mode 100644 resources/string_quartet_3_rise/6db2efcc/6db2efcc_mus_model.json create mode 100644 resources/string_quartet_3_rise/6db2efcc/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/6db2efcc/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/6db2efcc/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/6db2efcc/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/7276dc78/7276dc78_code.scd create mode 100644 resources/string_quartet_3_rise/7276dc78/7276dc78_mus_model.json create mode 100644 resources/string_quartet_3_rise/7276dc78/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/7276dc78/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/7276dc78/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/7276dc78/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/78a94ed1/78a94ed1_code.scd create mode 100644 resources/string_quartet_3_rise/78a94ed1/78a94ed1_mus_model.json create mode 100644 resources/string_quartet_3_rise/78a94ed1/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/78a94ed1/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/78a94ed1/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/78a94ed1/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/7c30c182/7c30c182_code.scd create mode 100644 resources/string_quartet_3_rise/7c30c182/7c30c182_mus_model.json create mode 100644 resources/string_quartet_3_rise/7ede7adb/7ede7adb_code.scd create mode 100644 resources/string_quartet_3_rise/7ede7adb/7ede7adb_mus_model.json create mode 100644 resources/string_quartet_3_rise/7ede7adb/lilypond/part_I.ly create mode 100644 resources/string_quartet_3_rise/7ede7adb/lilypond/part_II.ly create mode 100644 resources/string_quartet_3_rise/7ede7adb/lilypond/part_III.ly create mode 100644 resources/string_quartet_3_rise/7ede7adb/lilypond/part_IV.ly create mode 100644 resources/string_quartet_3_rise/tmp/tmp_mus_model.json create mode 100644 supercollider/morph_quickproto.scd create mode 100644 supercollider/seeds_and_ledgers_backend_rise.scd diff --git a/lilypond/includes/part_I.ly b/lilypond/includes/part_I.ly index 682a22a..2067438 100644 --- a/lilypond/includes/part_I.ly +++ b/lilypond/includes/part_I.ly @@ -1,18 +1,10 @@ -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4a8a6e53/lilypond/part_I.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/66f6a618/lilypond/part_I.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/490b1e6e/lilypond/part_I.ly" -\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" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/5201b8af/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/525274f2/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/41e447bc/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/74307bb4/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/628706ec/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/4c059f33/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/60adbbef/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/74b8f8d9/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/62300302/lilypond/part_I.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/72dc057f/lilypond/part_I.ly" diff --git a/lilypond/includes/part_II.ly b/lilypond/includes/part_II.ly index 8cba3f6..d1bd448 100644 --- a/lilypond/includes/part_II.ly +++ b/lilypond/includes/part_II.ly @@ -1,18 +1,10 @@ -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4a8a6e53/lilypond/part_II.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/66f6a618/lilypond/part_II.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/490b1e6e/lilypond/part_II.ly" -\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" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/5201b8af/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/525274f2/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/41e447bc/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/74307bb4/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/628706ec/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/4c059f33/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/60adbbef/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/74b8f8d9/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/62300302/lilypond/part_II.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/72dc057f/lilypond/part_II.ly" diff --git a/lilypond/includes/part_III.ly b/lilypond/includes/part_III.ly index 4e39361..6bc7a83 100644 --- a/lilypond/includes/part_III.ly +++ b/lilypond/includes/part_III.ly @@ -1,18 +1,10 @@ -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4a8a6e53/lilypond/part_III.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/66f6a618/lilypond/part_III.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/490b1e6e/lilypond/part_III.ly" -\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" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/5201b8af/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/525274f2/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/41e447bc/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/74307bb4/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/628706ec/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/4c059f33/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/60adbbef/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/74b8f8d9/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/62300302/lilypond/part_III.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/72dc057f/lilypond/part_III.ly" diff --git a/lilypond/includes/part_IV.ly b/lilypond/includes/part_IV.ly index beb2a80..4901429 100644 --- a/lilypond/includes/part_IV.ly +++ b/lilypond/includes/part_IV.ly @@ -1,18 +1,10 @@ -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/4a8a6e53/lilypond/part_IV.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/66f6a618/lilypond/part_IV.ly" -\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/piece_ledger_sq1_candidates_stitch/490b1e6e/lilypond/part_IV.ly" -\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" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/5201b8af/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/525274f2/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/41e447bc/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/74307bb4/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/628706ec/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/4c059f33/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/60adbbef/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/74b8f8d9/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/62300302/lilypond/part_IV.ly" +\include "/home/mwinter/Sketches/seeds_and_ledgers/source/resources/string_quartet_2/72dc057f/lilypond/part_IV.ly" diff --git a/lilypond/score_template.pdf b/lilypond/score_template.pdf index d945c8fe5e4b8cfe2f0e92117f5064c99cdc0e6f..f97631f8af97991f4631b1b99186e4ae75bc9ce5 100644 GIT binary patch literal 895830 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

X_Rh>LdJ_958~G0j~#H zSb)+4&-d?7)uwgR{>T{B%z5WDa8{gM-lng;wb8XH*SLG&FWZ)Co44(>jkT?|&E2M- z(Z|SN+$};|*|r~Y7vw%N9XcK2ZK7=o9tt|lH5l#CuOaCn#RJy^-vi+T`5{SeSni>? zR&C4nNrpK&vvg~9vvpez0}iv7xSbET{LL?u_|4|OPuKdb`o?!=ga-age82ey`S$uI z{3gDmzTiLHulbvQ=Dx*0s3B(r&-c%NEgKdKCkS^4=Lt6omj*8u4jOJB@P>~g{5a(t zy*3?jjo-wH_VajpJdCHB-(aPn@_5V^*w(>2xm)u>~_l*nZ^7`B|32r5k zrDN%16ese|#BI@A(wzw;=D-=UR1*SABI`-JM;gy?pYYFs&!o@t49Yg-=+Wj;#_06u z^$3n=_lTFsQX|I3e1HNT10VSu+SQ2Zp_OCzYtL)fYul^v1Iq*dXn*9-wC4Wxzj|Wc z&1cdR$(iZgRjxZXq2piYI0remIY$Nu2KPEwJ2yM`zI~oUpRG6Fv+_B9fG}nrtOIhV z0)w;8X*Zpl=55oIY40>fx-Nsbj*pI9*GOkcmr2*L#B?Jk7*)PcDvwY->#Hgx3$AxR zFYuYd!o^qI3*_c$H24>i#6A-bX3{GCyi1T`9c|oc+(C8fcV*&CUW9NVZDp_@`c0yZ zPO{H^AsC;Iw6Otm-0@*~R-7Y7;5Q^1OKF2W+Fn&`O&?X|iW=HF3%agPMz|~eF{Zdx z6AO7HH4GtzWY#g#U<@U0Lgp!w*Q7lN+nD77vH zF5$bd@Eh2+P?y(Sk9feppL2N`-)$@({Hu<&W}Z@}TBccY4qWVUnbzq~XwoB%Fs3ro zl&oYU?1?7w>NR7F<)8%~MeI6;c|$Rnh5!^Z&ydH9Zs zHGHUyqoe1yD*SI&D*CbfDps0Via8XnZ^l71nAr}yM( z+hbMHAI#t!%eP@Y^p%ZDc(^Hv(?F3gzSbZJ$P)nJ7jN$75`dNw2CSTRO<3MZ8QWc- zuWM(*KJ&u_f`T^v-e7L7%wE$TIoYhPvvK4co85jPiVJ>g^$T%ZygKr8_+>D$cx{@ zdKMm=lcI)IEkT_)Tflm5Reu3>(#g_EN~w8NU{DV1eUWEF0Mrxw<~gpVB+C!bKeN0_ zXvM@1eY?!*LB4Y5jVD4P)|)|>G+3kP7fD-82A2%90EOT8Z$5-GV(^~N_I@yd=kd(E_>dV2cEtLH&cDzGSteMMC z7_hL;h}Bm@P1h;#YUqFUoEZ3wgIll%#YiTeg!pPlTM!Jzh)K#eif5k|?~7e9$g9~AzUr%~lD zWug;8z`~{27`+ia$c4NIoUtq7Oyr~OrzA?@++a#`K>2)AI@R}hwNT2e??WCKa!HQ; zO~ubTw8t30e?05B$sL zmA1S8@lqb#yWkiCSU$szB_1}fH!XgvT3#zS-(C_zemYLMq=DWk69;(nJri{bDY&F& zW8odVCAWSmrp6?2BNd?90zX6>087V`)g|g^}rTXS$&^ z%SAU|B;5Kwe)9n)U6W(&?T(m38_3-LCnDS01P0I0vzmgoZuk?*72h)BVUbci#TTy{ zs`;DbZwHb-YFhU3pp)|{huc1#Ml}ZUCW`{ zRcNbS?`dRdtx{ZER`ksPo(MW{3E#33@FJ$yW2`f2o6G{wu0}C_xhPv_57)JqUIqs9 z{+u|Q{w>8Ewef1C>K8Fuu#BEx$=TAKNO5ky_`(}?@_F}rJ^MSksnQvxA}CbGL!25F zbDK9|$jdMsHni{oFnB+f*CBXO>>PZTYSsp&m4E#qL~XIZ)!=JIih@}5FLGuCeHkE} zez@o+@}{iXOre9N*ZMwSTN>bj4$O1DU=-;`H>Nps^Bo5s_^=6H20FG!tpjuJ4G(J1 zj2hW3^vULe+c!K2ECPu3IR1WyE^40%>>z~3@Eu(G?PZCV69k>DfCVV-Z56L+Wc9xR4XhMV1P7&L@zT~{g48XX=1&^ z-1V*lJR@W!~Z5im1#7H0WC57Ew()$*X-F~vC!qehtnmgCuT2{y$O z#Nw$kCFNLKC^ihbT72sceMP>aOv($KgS0*B4x6Q1??C*Nq$C{q_mMteNi`RMDC`?u zD+<|rT0un(Lh^s{B&Liq3bME7gcmo%kG`XtV4h}V>zOzBUMfwOjubkeuiq|*Yt`i7 zJX4tm$$T5MDfk;m2t|k`%X@@_rU%un{{kjt9Ay2!ST8n0FPdd-guUMf-o>y`%)q61 z9PTjH&*g)iY|x`fEnIIu#N|ohfFe5iB^@<3nrpFJ(d2 zqx>pq;^s{!0@1;O>#iy;{j1rvcpiyLGH3cnmOSr2z2R`$k zdT%0`We$&p1f=LY^zW|FQ%X%PP*aGgskIFgQb(HG&2IB%<)i1N6eXHqHMxWX>w01^ zukg9}y#xUs7QdUXK_Un`dN~3%CsG1pj#5AbEn@70&|AqnJRJ&3-;p+1-GGpNu`!k% z&o44x_FuYMOiHzA7>n@#l;FW{ zk$XO$BXYne#fuf%vI9?WLnOzrm`6hPzE_%Sp_o_3*Sp5$z}o>g^)wUc#0=R{lQrvM!MrA-@3?1FWhw`Gl zQA03_F&H4o^VY5Ju0@1#T-$Da>+XE-?DPf|zfZUG6fg%sjtIOL10q3+a2H=WQ$$W>ELWgoD5-XSkpRd%$4&Y~Yh`DtH=y^9 z+T5;YX}P0Ew}xd^K`g5}Q%zMg#_~fwp@-!?=@hL^3!RgRRvICf0=h8f{=C5cz(j4K z=)XhVqD9yV)~*{E9R|2xYVtX%&RLBMwoa35d+~~keRT{5(LZ}rf9@b&1sdRCu$ytWLB;lG3QCtP6^RfhjI!q}m8~5u3Y%WM@=!IM@ z?xc~S4dT~fz}XR!8>&Ca=%G|E&%O%e+yOn!xg2M!DB08=d0D}{LpESvz?f)b-P56S zmtnn9l5l}Ls)%?Q1u~Nftw-0h&pf7U3&V4rmTV{+c6&GMnKl0@B`mZn zERU*kp=x#X*v8b>Pq4LwzhqS!%V!Bz7iEKd@fQu}o&|`Q&zG@aI-AuqnotxNpFL8& zNM3{n8Q+;{I~r;n?wIn)Qyp}u%B8RF3=J?(a7Uj6prQ~L&>horci41Ku8tj+ZSh@d zRSEE|iZz3ndWZZ;Oaj5IUYyB`PEk*ZF*iYYSef}N2(wV6oK69iv2I;#w(@YiGGeC6 zn}#OHD1gHew`tW*8Rk41bnISGSoGq29YoWEhoYu%_Iz`y1r;or`yPVF3otP+F(G!~ zA6F#)D|O0mT#&;iOCa|Mq*x1jQc_I!Me`8)Rfe&dP#jzd=jIlEAmQE6Pz#!_Ku%B(CSaCVjLsPOwN(KHPr$DqQ0v-oDzoW53 zm{9fn+R>?X<`7bBo5m8FCO*JW)PY?O5Mq;iBapj@BKjIoR$%eRH)z;|2I!`>vAU4zxTH{PHyV z!aE12etp64dc;$J-k5zs2Yz$i6GcaIF^9DK9`kSA3=Ug5c>(*o$nF4reCv-06UF;_ zJ}5thaHL7E~7KbR!R}odKVCggqH+;Ey#Q2~Z~^4DeBo zgqB(D)>IQHF5uwt5aJ1h^L;V^)E2PW`pcKSm(Hn_M9_KeimL|YbxtG461Hg|sOXkYj?vFDmWYHUJC9^)7$X4lk(LD1AQX#$6?nq_^oN2?c2Qgq zbQQCpS(0u@qy|}QFz-~92|jB9nYb%#GXw#w1K7&-1oT+eS=|AC^1aiY^mrzKm{<$TczDRjc!;7k=P%=uLFoz7^tVDR%35a71kl=ew70ivm^;iN#kP)s$Y>8AU(Ff1c@iX zEjxJtMJ1%sFw495bdN#C2*mU1PPwP2pwBkj0kiTRp^u6Vlv2Yr$N>MR7&a$r%PwkU zrY!>sNOvm{-g5wg_ZH}^6Wm{d;14g9D2v4wt{R3gXgFEeNP{I4LZ15G_=#6>O^*b@ z7cp&bw31{G(w_H^tQ8;|)Euu9D%2S?Ky=FKd5TS(TJ9PlV5j?RAl%0)MWfZn9<;C8 zojXEpEVT>`a*j<>tREpDtCS~TbPwqJn54x!(MTZ1AdJJ6NX`L7Ps^gt41f-xQcT&fm;s9|Gmr<0B@Y^JJXbBy0^mmSi9iO_&;WqrADI>$!HTH) zeGOGj)5t~;5=DxaUy;77y@igEij=P!BTi@zkdm)wjBo>25P~EPDoZY1)G>%vcNt1| zkG(HnOwl^bI<9n}-JCD0I}U~koK1R~zq2)S5gC=iO6yktdsvEC#n4#zDy^n@nMq$f(p@FEjrbSB6%PnnhouantTJHMAAb;Lv)Yxt)+@ zw}!@wR*kww``?+}ncWL{H1_1v+S2sWBGZD?hL@W`KJz1fKM84JxJB-{7TCTlER}gF zGj)2?{#LbH4}(Q{M;;cAOLJeg=Wx&A@v|ek=5~2j-Z=43LmE)v);|dztpfMCYerss z2@S|QtQPD~4~YZSjpbfhMBmpNNDsLVDnXjx_V3s7lG_VmpDj7w(h(o2Rm7GNPhg^+ z@CjV$`v&+UEXSNpDs1U)L|E?!_0jKEqLq+Q9Y-SVwl(|=&dm*8j(@n|g97j&(Ot zdqs4yEiLP()XJ+8($P}V6h1tv zNU25UTg6!OZLav#arZ2qY6x5*;)^|v6mbo7Pvjn)foh{&ojrJwhoSPedMKk|oRHmH za(fbIBJw7CFBZ+ymYlsDPlh18BW_7X++_dlIcsJuqnD6p-5-V07nnIaBVRZ`#w#&E zflQ9*H#{4s&!-uiDg^yKl}|IEz2or5L>t6C?T>4f!w)Y${9FAKN9v6B#aYdO0Rx|$ zFWc`8OnAkExdk8Uz<-OiWgm*xi^#;1(;!DMgMK)eY=M0z>CS|)>Fi?71MUPG{&`oP zwY5r>7W|u-C;Tq~q_{A{dZ-1CLdklZROqOy$w}FRQ-*dt46*}92ko28IZH>{4b{?h z!>3lToYrf6?8Hj4vaqM4vPq)6zOnIGa+a_WZB!C-KB+wieH^<@$EI_}snj(7IN}&Q z4PU$0?0IElY2!~;OP*=(eAlMegxA(bz7P73(U0|)#Lw=VzJXc>Jz7Al;jfU?JR+lq zeh*0f0R2Jn8|+tF0O|}n47A-Wfw{+gM7APZvDesNOmy=tGBkwgNcM(D$Q=AluAybl=}b~ebZ(_EpvK+!|iL*PU5 zBmda7Z{5Il%esAD6J0G`!?=lcyLIbzN4w2mjlRTu>-@_%j=pN&xNX?A_FwqP=*{=d z@-6Ef>l^AD>l^Ny|0()b{nUQBUH7;6%>=&LJ0o!OnA|rFH-)>zY5T`KLO3sQt#Il% zd(7`chxc=TodMwbxx2XJoO5hCb{=Al+s1X{!gGFbfN_m+mvN;1Lm=Te`YzuJ5831T zxqir+P#{I%iZY8ji;jzAi*SkT7abKlDD+ZjlA=?HYAX~}h`H#yXuXJ-IPY_Da&>Y} zIBEZ-L|Nu8o0a3riRTvOD&;iguE?dz$;#Qv@yem+isg>w+;H}?Z@c@QJk>7yCvs2{ zo+d?4m>yvtX*m&lMm&+rCn=iXWQvp}ay5n25L`~MJ@9af?GW;l^%MO{W{vRN!!!o3 zYtBTRM(LyVQ~3$^$ohy}(ZV6-qfL)cIaYQ^Zc+CU_0juL{AhfYzTq1sGtXx_w7H{k z&pYY8iJ!hFG^aZ^IL9~_HE(I|*Es&+R_9#fZ0F2(@4N7se{Xz5pWEg5J^RXj^zXdW z4TuhgJ|}WC)J5x{and|*`fFN0O^D78>}bCy&S6Yl=twO7S+uRa{_?Fl%>fO0+MM2e z)DY|kJDfN(8974UFHsCgvV<0Vm4GR?`qiiGm5glRZHU!cH6b_y4H$1D~Ouexk ze9wo~YMpK}omrdii}@Svez;S-7xNnK&pYph^?oV$ma`>_%dNPHI!wlDnyaXbY*Cl6 z>!OQ~ZTH~K%tp)Mad#D75)6|zfdBl8d!8d@|5j0c$`AN%gaT>6r@D; z_{eG%D>i%){SY-p!fQComj@V#7Y-hb=j&w*fuxSnF>12OWLR@C{VCl3yjCtUohtd) zlBS|~?Er78V#R`tg!4@W;MnnF#+&A9^l~P)MAkFXG;wCXZ2#_aC0IHZzK(cS1qwf* zeR&R~oq;ycn|h~&!z}ZYYd3BoFtB+xm-MeGCaH~BWG9OLU%ee>J?!RK$Khtx`@eyi z-pcZnKBho-_yxbTK;QYl_=5NueAzJMy^QU~Uv1SXM znTmD_QSPT`rfHu!hc&u?oU~E~{12(CoEG`i3YBMam$Q|83OJUFqeA=oA7_2d(U++g z3@=cBgkkL1bS>V+Rj)nHXB6egN3~TMiWxdYNH6*3#bmB~=@Ph#vRKAd4)4u=4 zf3>p>%qU`l=@q9Apjg&*ftis^AAlA%{y_SzTo~I6ds{gF{1m7Cwk~Y&eNA>D&Y?*< z4rn^mJJ1V8hu%HAgU6=NG1Jzf)*b@`0hmwnZQF&!*AFC~?@Y>NfWSwC?X$kYKTy3C zUdV|2Z20)2e~*2Q6j2QBd`uhSD=ZipU8%Q;MBOS*3Jp_wX7vdSQbDPPU9knywKKHG zOkO1UB03m~PR}G3i3tlw8hp+CrEVuRMijQgmGK>zODaWCLZ)7@(vEn;QBM^Ji+qug zrH>OkEOJwywIqd&t|?@%>rzs1kS4Ae@4Mmcdm`;uup>)SqOXtRc|z-UiUuD)HIwKt zc%y2^2>X2#{65nK6ImMjT3l^$st1u-8;C<5P=PW(U8VFMYHjZMm~X(GtPEK zo@QYOA&YnWkmBe)<&WX(#1SJNeEFSb?fcR17-Rn(WEDX8Ru6_6s2#ySRxUQiZ^$$Vn#lK+n!%e}?`oIEU^*Y9km-+Rr*eKe_BW^mwp| zHQ+SMG7s`;07hb&4;ch|D8GHv?X5NaUO^~#svpLlVpcwig2{|=edTe<&(};TUXzTH zdSaEiM_@mTYyF1@V-4@=*abs7$qOo2=xAkFm2u19N{tdDMOQ_+h=zt}-5m*>XkGr{M*__#2ioDbBu^P1>scho$^iTeCZA%=_}5Iu7Q!3&G-5Zyw&L1@p= z9=g`=sJp&_Gds;KkWZ+%A|`{+;+bF}LG{;%OV1Vrq$`{KK)mmCIg`oeaafJ5Y6|Z> zVJc*J9n|jK0s`z34Fn%i<1?tc)mdvv02*rrIkP9*gXA#h-NFPsjqrQxpTmvJ`xY`T zDwkL+-b3WY7PXi&&89h79O`wUASiBx@W+4mx)7n>sa&dj{tJ{VR11^t5a>WxD>DmQ zI`n0z>P~}}W%OrJDp$){4oFN6A>b;+?yHALRx$8MN5b5h z`$a%%Cl@U2>006yR}Gtd)L21kV995w#Umxfu+CpD(1M(#4E9a-jm&l>P61yyX@@v- zqap0=8}K8^9unpIH?VlgGJiZoSi~0!`V^RqH-cU^it|_)RA*SMoEwlUx1gdy$~z5- zws+>)Yi~`ZG_wjJklT*};5D)mTh?fK{$7;yHaE^6SLPoYs>1cMF>V@J&9YG6oP(-b* zM6X0d5FI-j%WA!fi`PSMeHnX}HND%jRkT$`S5db#k!tDG9%4 zz47pBU}PsyHRmx3bPI^gb@Yc^ht>~7y|k(+<*z0tq*-d^Q$T*e=I2Lx-PWNuLknYB zaqSg z!ZK^Bv>b}bRP`o_g?jd&RSp@-lX$TMJE`rCFwm_${^@rB+s%r`SOEsm(GZklU`mPP z?O^42kKG~D1$eurAh7126#lgyWwwr-u-icaEUi60UUWJAVuEMjqhWP|<|FVq%WnHE zLVGkV>&g4JUW)g7VE5}COb6s#053a!lhD5|yLS!x`PrR%X%FU&YX_MAEM75K$Zqy z%jVw*4+FI&DJpDeE6V8?LoYV5a8EH&RCIIG%SWe;&!eNqtVwOk ztb~A!jDVkY`5qsS+=ue~>yEyb9zJa}36vev^L928=O%}l72lriP;+40%!d!FD4 zcqgvI#dqo+D^o0-ovBwGE#*egqoNMg9c!J*IBxf&^K4D_PHJD9>#gwbf$Vl)*Y`V|i zH}-oCop`U}-u4#{pTpWbIz99IIV?wvAHI%(^Vt|#k-@<4F+u7?%MYlIK!EB6MH@qMc!a|Jopp-7 zm*DXt1=K4FB{>K`fs;DKr<+_Kh%5s8}d3lZf*k>F?MnFKccqDJskW1iWl-;BLzaDThq( z#PIz^ZACoM3H-%5&eWSG_uH&1_h*-gU2kQE9Gr{_X~9T)%qaR=8y&@)&pC&3C60Vs zjF>uk2pVfLbGy^#gE_Wg7$D0l={-8B!%q(*>VUylKc-HX?y%+XcrM9*#hV!I}Lt%|+YM*nF>mh$1xqbPgR%X_g$`lv^_WeJKB^4T|Jin2i94(wgAO(?70q_JN;g6y&>zBdiB&d5s! zOe8+lG%u&lnZ2G>Hsoa+E>MxHNhJM;{F;2A1a%CwuFg4ldm-|dZ_ZVCk@8=K9`@Uf z3Rcyolw*>)>D7OFaIHg$WIyU3(O&h!)atYQgR z@~i`D=iKfmJR$Bh85^aRb3OFlX@*!mn+1u(;L5Xm4{kT&qz5Ua!N z<66GulUL5(pCWvKFulTkp&~lw);Q1zwfYam#Y2Nkc-!_@}aFe=|d zwYxo;wN-JXvk^_cy`D5F<*!{Mg^LuBRu=wg$2h4AGF7O`t z;n`A(9(IJ#Q3*!Ec98g02WD6lOi8K!^AZ-el7W}wQiMBa9&cBN8^)+jA2?>=JFQ0( z90JbU^PAJgf0xu*Al?aE1GkI#?H>mvv=)T&^3iQ)#LK7vtTzKpo!5vU_%&UhQ^b)< z`Y@ifd43NIJnV*k@Xw#HO(=!k4N^?r_1=@zZ#-+?7(v}em;_=Nqq<=i;@e?3m-!zN zXxJ96LERvr&{cFu1r?RrYk>6-u3d#06jZF%;^ky+2?HXdz0F}6THt}l^AI4BZlxux9bBcOJ0g4HccMcSE63WAP>KbppQ0BRu3%8~x8&ZR0_{g}^V3NI~+ zvhw$x_dWwt4INKIR%#k=1yzmeq#XUg&?EBrq)tst&C1OtoQPO}0t(l#ZF>x;j^z|v zyzI*iG=NMNjUycIL$e0O#1r09!96nDEGG=37ViLfq;Te;BA1a)DYa=65YAZ)R?>S& zAztka9KVmUB5y9jiWSHrXk=?FQgbY;t}iRWUol34Xc<&2>e;Z#N=TgzDZhkfTC9}E z(BiW!J1nW9WV`Mr>@<&_Z0F@-V-(Oo>A75eNpC4i2Umx3=hM9u7 z)+W?@SU-+5)GUW3RP2(h$X|n}%6Z)@_|A0m-5tw3dey)l=z76%kFcOzPphF0fPr>L zvK#=rM}xh0vI$7Y=cIob-M+XTm{wl*T=^3)Ym9gk&iCYw)V@>DSYF^HZmuwNUN+>` zZ2Mvpx6Q3%Z41h)icV@wIQF&*hkwT5Jy^V8^9%-HZ7PVj#;F;6e#b|U{e(3oNw);C z_Y)Z#4v`#fF|=TRHF1GeAhrT&OA{x~$wkluG_wX+LEaL@rUB|aEknKg5g?kE_Q{5} z#=9QrF~MZ8BIGR@3etJC!)q*A7L5m}2X=uCZVPfFxLm2Ynr>8-m34KMmn*v{M9dI1 z1GFItC}>wMq)ACNy*i1+OC=1M22>X4lo#k))JaJ#GsNW30wGKC08o};0i@1^4Dwik zRo+u8GnbR^IhRKjSl@b{d~S6o4`b6g8#c*VSE3nzf#>8|JktN6`v6nzFb+Op3R@OH z^8jsB9Moh9zSHa`k3=Pr^Z}zRF4lvBR8yo_8#>DX8U; zuOT|l)EW?Z=p76O95nJ(T%m0J30#D$S%6=(1coqxwsLOhgX<=t<2?f=6UI*gXkx36 z;|<2lRll!ufd(iZoE}?}rd}@o{CsFM1Tc^XBA`SNe$R_z&mj@)AJys3yx=Y(7WM({ z{~LKWwS$V5>IWj$w~J^@quGYvWopm!41keHEFV>*l)(C(2@d{4 z577LJp81V0St@GmFw8)(JuFZDsmw@m%QcW|z?Ahgm>13ykllgmfNZjFvj}eha#NoA zNdi#cv7o&m?Ynx~9s*i=ZX@WX7pIhjL;|iLHh^9x8y@4H3ikE!i+AItXDZAI418yc z;hGGdcjX6@n+ITbJ%1*@Gj$fj0+d^p0A&8`HBy`mgb)wL5mO#$3F}#l0tLYUGe3B& zw}vaNXtA|7_`>++Fk`iZkAjx2sub3w^A9u1GjA5Y+UI#{0?OLTNieXtM4&!eYFjYK zBEZZBNOiLE%G<+2Spc98Y>kNhCEE;@okwUC_;knO$O>OebLN3mh?k9;y@0vmx5s-z zl=qVL`o0MbRG^Fc1^ok7JX0+GCt?2;v>Xk{X|C0GpxXr+^anm;olo=sWSRb(F!%oj zX8OO#cmKOjl7RM~x0U&y{gaJ}@qgeE2zKO;^5_4YrJCPvo(i%^pF zAF=em3MJY9t^5z6Bn$gL-swMulBWM8k}m%nr}Q6=*#DJN`j3_W{=okqoDvHY`~S=- z<$6PEAiKA>qnO7$u2qBtnuKNE+ge{2)vsDLTIo6t>mnc|`0lnhgwYBB5hgT*kkT0L zzUm4HI0}YV)!MFBsaD&zem=b5?-aiJAz%6S|0|PsPCxT*CxIo&k~Qaka~?mx=QtU) z8tzg-#l*yT*9{w) z21krar=g-#U#4l)@^tzJ)+Z*M+Mi~X$**_4PIFnkg@zA^zI?xn)a*RmzsFMX9I1al zOiEMTOZx36rr0~s7k2~;4F`7!6~i9ud^~r4zej)jg)udq+nWfi23?EYW9#}3ex+8Z z(_`H+__aL@3a^Lc6PYEzoM7JpNfohIAipuceHX(-wb60)N;Fhj&ZzaK4@{%kY1(vp zT<_yX#nJP%cp5w~ng`U3><#RV^8;4*cpad5G?E%cT1yNh&yzMt^J(9$CLc-rX)?9D z3@E!+OfC16CiF+*kEP#2zZ`!|f5gAWzs$e%)81*IHPD)5nyyBh{OLa1j|HcR)5dA# zb$%?~+o${Wf5xG@QG2Py)MaWj*fb5Bc1R{gtddNXY?lm{Y?;iMESXH5lm(#GAYY*1 zpyD8}K<9wq1q%qY2oO;qra*Fo@@3GovNEG&^x0dPcrC1qt<13usaRDr;xhc~rw%Ck zV8-wJ_wDyD@2}q+zff*syblRINbrKE@9n;zztnZ?&CJXU+orB?o1e|JXW+7M*|;sy zY|9v?nJ<~UP29$>w3~Nl&1QdQeF{0}zi1zWPkv4cPZm#*9+@7o9?c#VAE8h1vwuzM z*ngEfG$;_DLSyb=_p$cn3X>PqF(_bA#~_nKEQfRo#Wd(@(AXfnLl1{A_tA&Shfas` zMWaPp&R7?-i)M?4iqv#^aM3#%di^_}8i~bG6 zhad(b`2dki1QF_y|3T!AL>|E;3VaZK%y^pWEs=_ArZ z;79gHa4tdYj9?*_e4t%Q+LGQ#cqzd?mkvu@G~vkSzxAB`9_ zjn+ncrNPtWY4$L@Ka_7y^T6fO%?kG&&=W$6nd<(IRk#1YlzsJ`;N{UCS;B z@GIL7pbzVu&=NIs094e&AJw0xCFA|8rQiaLEQ*|?9k zC%>h~m#N`8NPL0A1C(pHUt6C=#jmJaz79uS19n}TDneEog=~cBAjX7HmF??40Ai#+ z{BR2*+$?RT>uCR!!pmf4uiPAMGO506B&#!}n+=SS+rj9k^R=$dfmxb}T7I8Z)Jn)V z^64$#V|HDF{Z!=pfh=Z+P<4f5ig=wF4}oLz*RBM(*9R66=CAw?Ouzh)JK+N_%FiC( zSdih#x0{J}wEh~#{a^89`QMD4At>hNWfY|@eq6vi{5#w)2@MYmfJ}_Ma!BKVdPr$i^wDuStBVet)7^;xr9j)6Caa6wunkcNd@u5 z@GG4^T#UAm6YpssU0h1a>4|FNBYE@cEeWNhoSv;RA|6sz?j+qG+ZAug;hyF=L%G3_ zl7%YTnkqXgX|*&oYLr3c7WWS7R^Xn88>y$XFdD(STq2r?Kf=)faIr-~^IeH`b!}Hb z?q#Hio(r{*eKR(VlU6dWD82PF{Isq}<%i0hKcG^l_xZ zMMXh8Uch7Yx%R(LOu#Yb#vQ76;_*0wbeo%hYzcR@ZaZzUALqE;X!2P}*Ma+MLb~;I zDm#5r>2`u(iqs8`P}EAX$P2;8c3OuvIsg(PmyZ^kFvPGzdQi=5f8i#v@H=4B{nlX6 z;rWgChNkoNeZ+RMo_2OeQoK(+DPLd8p$A(`gnrp`Wz3g8Fg|vJEz?0?3o+~pj`}1x zH_B7RO3lb74lupl=##Gg1v8Sxy?V>Tzo@meFT1*KM9n?&go+SK62iT7&u?X%c`yj- zzcr!Lc?&>@g%53f4;7N9DWQq4g;g#ULEf$p@58l}Rwq|8pW|;ctYoe!`b8pc<%bDQ zh&{1%v;J7zIm&A%2G{$S@MF@{SyiZ=sLe4LLvgk* zhSM$&WeN4dVSu#4+SE~2)Y4X$7kE4Hn%4*zA(fS!B{)JljdM;Q%mdu!mBFOA75|IU z_mlap9>mR%+xTHbGfHorF?o)sUlNKC$1~V z5>+h>L?qPvqUZXFN!R6meYw-|GGL`uBdhxN!Xs$&59_@t@5tZ57ir#SO2`Kq&9WG| zn8_r`h=!{5w3n=|xyoW-Y|A}W^c~K1UG5D*b=Fi?v^DTl=yvZ<&yeKFrJ^MwRZlYyZDb?P_nAcILrKcX#E}UX8EJE2 ze*&q=#a_0GHHzbF{C;h^e)mCO>&o)+8pz_;L2zBoaNxkS?MQdJY`&kgMxffyFlDq&0!jwdzzE89_aoqMAsXs%ndy_zGw^7^$!z z6RVkg;=?tfm+1)LjYn_tNHlU#vII#+NXNKxuA|U?`4m-j1kJ3OIDk{gOw9f1)$|cM zYeH>=kA;aQUp9Fs)szXQspJTPiHm!Bt&6|H#k~3U2>?7pgAp<2rvJP3{!~0Tj(-05 z>P_gDjsbIh7af5ZK6Xl)KKhGl=f@~6EWfC|6_>*&=mpXW-*)txCd&Y@lkq&bTZ*t3 z3H!2|b6ra9V!9d&Tu$i_c7R}ttX4pEs$@b=LhwMVGeeco6mWF~L!?O5UJP8r@D>V; z7J{cV;XF+Km-Q|T0WbXW*7pG)QDt{RbRl&k?1QdJB{`2ueS%pj^XaH2Mx_JZy0X#| zkAKP`%2EPA`b9RUaZ?2cI^0 zbl5&Uu~>8d*(gWL_>YcNC~HA_wNj8#31eX%jJNk3EM!cKWJ~}M6d1R`V5O8Q6>@&M zmWou0g;mSy6SM^30+_4$+8{fF-Qu(CnQq3hW`+M9tiN=_8gs_UI|B`M0UASIttS>YF3k05qrnv@XqwC z*=x!6cKn>{&l@Bx`E!~(VSUS+Ss=wNITZ&TB?(uGq44_S<-U7_PPVF=%nJH(?2;`U z26s>yH!VhPi^cSDglVI%hM2P%6VWmWy2%t%69M}f z!9j0e-lU0Kg;HhpVU&SSow=LM>rc6>A=B1+_xIs8E}F~z_>=p2uDg7*->;f)?ET37Uf;de zY(E}zFS~!lns#S1_ZHCV_VL;l!0HUw8rrTT-Dp4EU)Htp+x%Mc`4|pA?=S%o{Gn_H zOmu{BcfE zGPLWj`;5)?w&j71*R2FN)gi%$a1jQHGI49jh{do{E(oO@7lzF|iO(&o9@pU7o)>y9)8TI;$1d;ME=d7#H%0>Ws07p@Ii8fI+P} zn~oDNRgp9%o9$ycxMjcC9ACTXyc&wwZ_VIt**W1tlcd{f5F2+~Pvzt{C=Way+(6@F zjdn-&OMuD3pertda)eYVfvZ#Rdh_?#4jMmaakpEHYt0wQWaavp$jSfpEA?G}y<>7i zZjF|6uuIJ)k0EEQHOH6aM;P#+$nWtG2WPBZL2uk0MJ3PDHvjW!z~CUO965hYfc&9)Rjn!rB;72@ds0ai7x%a z!Mnqv;eQtt{C~$5+UCN-;0CVoCDxev^5SNKl68RZ2i4LCmrYHrX=?vaeP*fA#0C#Vh!|`BKDjLQ}rwS zQ8GzR>Et!*!Z5ZV4ii}`3L>EoF>5SE7{Dpgrw&0n1Xv`^*eJ&(Qhf-3^ZH zkuBoyTpi-$VPXt93QtD-wAv;2)c5v>jn_LKHDFZT8>}#Pc)nfy?aS`9P3rg!b9kQ5 z!SG1e?KmD$`=q;_R@%_T%OsRLfW-5RF6Nm>@UH9V@!;Bid;`K&ip6nhfNwap>oh$w zl0QXZ4hn*yK@BlC+Ov-NMyMO`^ZvqGiUgXAuh%woeMBHUCLl^X+=-iDT;ZyC_sVP1 z(445qMihLU`y!0nL&)dK+wMEBSmv8=8~6!)`O|k$6BJ{_#VlW_hL|-Oq!NlKU(pv3Hf}isKW$_(eef@9uow>L zQhuOxj5+$;&B*3FPA!i}Q(+A*_J~>Uf1&K1qAQ8ocJ0`Dac}v7Hs09ou#~ zb~?6gr=t~H9ed~fzA-lbJ@&zWP*t;P)I6DIb3V^?U+Z-y8-YeFZrD!nz9^njqoCk@ zg6H*?uE%b}?q~BL)eEj0I^(||e7;A}P7pa0&zOD1t_`UY5J9rnpgT%0^K++lByLX} zy2E;?nxY_jG$k!M2%BYFQx^Zf2-t8W^8HDp=66)y7;C@2AvPfXgBL!bw6wjwc@h0_ znKd&jZ@jN_WoW^~t##G>%hwfG_6lvpdg6BJlmdG}={KO9I&-j{Ebm%qWp6+s&F}rg z+j36sRD7)9~izSeiFnY`U2*jormzzEaXtw(hp6KlbniO5hX1j_`Ns)J?&s@ zf{Q~sXUnxeSBAjmkSySN)P^BC)MBc zy`47IEse%hjP^#zO2xEL?mM#CbX_fl5$QKp5pLn5EexKGWu4`}3YsS}^e;6~>hOY% zk2fesz_PMN=9aR$h&Gsu_%~};Sq_$+CExMW*$fU^OLa2^PZpM)EgSEr=S;zS}kJjE@~qjt?7c|H87i;DZWgmC>WWi7V#+$aUiMn)%6hs{;@ke%cw4^Z$j@@5*cz*{wh1KT6b@_FySG`Aq>(*ubQD{-3R5__ZJ zd|Yq+;GFKjXQdQkJV^O_BM`X;A-$^J0po?>Bpzaou`zut-(^@B$f<}I*v4WC!q%*! zZ0yV1ksbDN_1Z-+M51bRq2PT*j7An&J95^8BYkngW5vk-6*~ZY4{rgNNvsjhDxG}< zG`7)beM&i?h+d1@!u>@M9BEyw`&W>+mP3Y`ZJFsH3*-5^l$TnlJD+mI zMbl%nJFlp7zFuJ9Rb9Ue3@~fX#D_@6efNJQqwm@bu}5t5`Ob*zr}QP#P-Yo{c}2Q-qkIZ7@(iu zPtnBiB}c9)?c7j=*w|*`=1J#1-+kH==zq!n>Og{Ade^&~)q20f-fdr>Rc@a|b*M)C z=VO9s)5yjQLw6e*a_$<-{k>>V1v=oBt7l>ki=CwxJn(3)kR|w*nyw#=Puw*>{^j|G z5H5TnBqXA!KY(1ay>Cb;=Drun6c?Lv4|w#HJ~Kcn*zHkETQk=b_)yy`jF~D0_Fj>Z zfp4M|B;T^lLmCUdJoqg#D8gh>Tc!y7BpE&ZahLT=t@sKd8!XE_|6&+lO>E-KXS;1~ zY%wtA9C$~9BMCEmh9=Bf@$HZGm|`gbV>Bx$zBDV3_Jly-n%n0>RmxXi4-lc~AE5~- zk5In#SW@5S@xVvI#8r5_{KIGZXQGy>L0j$$`O&4GG9W!!CW=d0s{SWPqZQiT^ZAW; zm_KiDwR%I4nLV%Ez{`LOnricM>Si0#_Sa$5`jf>sYeZ)6!91P$XY$V0yY2` zU36C79lXh(s^UL`=d91#!9PY}*m<4BZm&z7O?2JCdeXOjIeo--LGz;OU;D&^N8#FW zhp-;%4$iXal@99-4FnI+EEX9AEr_2Qw?-iDOsQFDs;X*AO0j(tgq|ta{G8>rjwPj3wlm^ zUP_YWVz%8eu`L3uYxE^jE@?3WBKhh8$@#db!)!;+W2*x#G2b_yemv2+^%UO9sW+!j zg&2BI(2tvwh(BlF<6iv?EdpB31|Vbh_B~`rPm*dryzzP+b0-;*-YD?XeGOpmJc;Pv znb@&{ZrJyn8-tHND;~Z_+CxKUi#;{WEGi|3Cl#LElFkjcQ_i=Vd z#Es!qd5~~3iuKHyL7+WWIunx2{`3ZfzCaK-=E2JBg)^BAr68dlY}?;AFNI~25S-*q zs5IyPnyp{-O!94dP_2th++=fkBQ<;poCr0oXxQ^1T*M!W9FrGrZ2}uhjP>FJv)*AKO zl%c?96u{o15G&G9D0=fWLZ#41Wl~=Z|8>f`W@~T$jDZ!n-Sw3n&3MEq{fxkECp06r ze(>tj=xufm0iCG2jA{;|^=HL6jg##}m>!P%cT{WwB-F^s5XQ_K1VqxEgljY~^F`sSUP!{@etcIZ26V3W%sc+^T zN8=aTGa7uDZ8Q}FVzERy)F57bJ@rK`1R1MY6_SECkfTDD`)NlA^$12nH0Xw_4#Dgp zMavzsyiG78fol#YL`7*a5Q4V}ULMpt#`4+qn;Py2QnP*12y+LJX;QrL4z9(!3HgNw zhK~jmEo}9Cux|Pm^A|4X9U2rPEPxOY1gEIkXIEFJl?%XNCZC;foq^N1Lb8mQ0Mlua z{5i{7BNhy>6_q+W473#ZzglFwC=mvh{E%|5_Hp)REcababQo3cUV8eb;SZ}U?CGNc#+*G6^oh^JHIJ38WO?( zO=2!EsD-?3vpwr1BuxO@61UV{gi6e(g+3%_fG9H$>t=>n8~9UuA{1V$s0xP{V5D0k zD3)>t#&Zu}SgrPLNi0_`2b{~I9Twq_tAx@Ts=)Q>D!^{>zXyy;tw9IZhG0QASlmUhN%TS~ z2q^Kx(9vSHkb#~YkEeWpPnPyE^FQ5=3hG$rnK||Wi;HVNzOCs}$bwt?TS@Sh{z=-z zix0C!a((y+kdtHz_^MBApg>x(-3e9A87@PD1Ej|=Sb3VNs`+s7@JG=s(7_B?U_3~1 zdji@JFp+r8a+Cs0JV??~xj$S>ei&c> z4Vm-T{f*41^8x`m)z}{Ss)iE>wk(`@64jXBWd`;!WxH&(e4o<4pvy4uB1jeSfkDgCv=NAI{-Y(YCv=zA=uKjhZto}FL-CCb z-Xlj>5>7?aG28?8G1{A|F^@Nl!^D(&xw>_eOwy~%7)H^r(Ae&SplE0Y#7hEdyp(R0 z*YPUHe8=FM-#0Ueb0M<0nG749z?FVp#5^)@bj?9Tz33VtD;HERJYqeyW~r8A6bFu( zUumNg`kih!FGGG&khmjExw>x)yZ(j}t7ofOtK8=hy@7DEAdYLAW2i1e1UV+|=0Wp_ zb&OhSKUo1iC-?G+`Rw8lS^88BCOhrFU3VMvW5o-y4oqDgcN4{mX#xg36R{K&DDE#u?;x^7*#8d0zZ0Ie?sCs?w_4 z1x4=B+iE^lh3hADQn-hhhq#5j@C}p&eYD(?$R#Ru3f8J`@QD+ zC(C?$QV}sdv&XEv&nN6z|EsntIb}EbK8ZQCboigzs^6{iA?{Rd#y0ck;9L1K>Y;!7 zX9utVSU)Q_3b&2ZO}|04LEDJyg;*3K-A`oHU8l23osFiC>G%E1$*1z9_EdfCz2ngD z9~muH28UUVmJwC<3=bhYCoo4oXZ4k0^j!vHO#GX2L7Ccb%%vzD@y$Z;xmHUZ7s)%h zyFbTyP$J*SHPFnV#5)x_<*O*0FO8sCNTrccGER*CarG;+NRU_Z6EX^j@Fj=bknDxA zqCr6`hd=+3(7gifzp|>PDM85vh)Nd$;)M@4ndWx-W{%j-JG@Xh`5fo0RGiuw$r8ab z^NREE8hF{jUKJKMj*X~4Ms$)KUg*~bp5v(3AO5P{;Mats192qoQ4N%F&v*JWnyKGp z@xnPYT$J(>?Us~36ol=Y`=T9O#Qye)kys{g@D=|;4Ktdki|`{9(|?tu*QGfACG$k@ zhxy*X(a$4|o`gLG7s~2pI$<2a4;i~s^Z4*y6!x%0;oXl}C^}|oH3U{fBnkyNo_Jnp zyeoZueJSx`Z{%*I34NtwGqcF<$ZfgUoUrW4jiZ%#eu11$=7p&7-caC7_yd$?Bz4nP z*pFUpiS_O@=zKM&RH_;%*Qls8Y>qLgj0y-0@uMDD=wGG`YIaTT^<<#7G!f-rajY7T z+?xWnBaU~N%nXDigO++uQE5IOUj~vs+di)EdMX9K_N#EQQOHPSBC?{DdndD71<6cf zq)a$1v8<3Wu8;#_incaF)WljO={*Kb!Z|f^lLxWM7zt^*x(bWSsjjJ(*xJ-C5(c@2 zvDrl5?amFoex6R}P=}iEbP-s&C9HY;j4yHy5*0(kB*x9e5<}?z2^{b7{kL}9q==U) z*n&v zPDKYnjh7l6$k9=-cCS0Pb(Z@Ql;t8`sLI+-F~A%w7}7_F3Nn2NVy;IiM0Kc@b4K`)@mrS zTCoIeu_Ubr8;A&3+zH9Wd`qEw`M>+!D6|v zyaw$4F~7AV88Pw@v!FN*BJD-%ge9()0!}GW0TMGNl|rau>#C7S&m0&DkAK^oR_eIg z3G?iPK5xloe&-bY!b!|6CTdOj6BkJsh!=78S6qI?!NY#}$_ZEnHB*x(g3BAFkxgxU zO)L~GFYqNl!v{X((oMomOgmLmM+tL+E{Xh;ukxuHxFkTSv4O(Tltk+Qe1vH(r5E(p z*>sas(miZCzQ5&{Z{clAOKk7ZjvQ~h|ocB2F2bS%aLm7eP`sm251fk4`^~1W)^LO`F@*n%Bm~FEAdLo*8S} z$7%%h(G6jIb`&W!B`XzW>k1BwuxEBf$2TA@#rvhF~Xoriyy&O|z6ufHF~=qlL%_pA^?y)+uju_8_uQ zrCO(YRE1@%{jEx-p|qgBN3d?pvuCq_q0%8IK$hImouK>gln~`RZ?VZ5_l}ydQHr(L zm@0n%9VHf)j70YI2?I>0m)vf*yl?*YqtV=fD zH~f|GEoc1B;#;XSSimW%W_u*|7cM0c#+& zw`Zq4F7mXJ#{=>{r)kqPijig(u)5cn4lj2n-z2{WU$w0`y!TitAqIQnL!nE_FTEZ9 zS3$yXNpFE&N3Dx&}lKz;Mf8+mN#aGbQRa0O?mVXc(W7BKSs{nF5RgiZSvFj#T zWV_~ms09z-jMdMDRFh7qm{(GxpCtJao)2JDS&Z+2hUl9C;=98hAtCd61VpBGh+uT^ zt82|oY*-O?JEq~u9belNQ(B8OxtSG($Y;6{D_&oS+1x&1IRc)tMJe?zc2rt(Ojqh3 zZ)9hC-mG)IbOF{Pq%JU*%@KSD*pp&V?Zrg2(1Nv&!?gvJ7Hq$lCf8QIm?ZlJ=BIfB zP*C0E_+UO2u=kV8Wbg&7m0A-lgFgfDEK~@>qw{mxB3;@i@=D1} ztlmPoRc7I(knoK+Yl4OhYVUKJG5DuHC%ee8Lw5$4U+Tk{Gl$?Sf zZdZ2#p*6we)tP@73@=VG?ogc`X2tv1BAJ9;ciI-S#!VBeJT#&S(rlsT7OZ0$j}J(v z`A-41u9hw7N9Qr>CDB!z+B~|b(W|d#T#ER&<>O*02F>8r?HGKI5Kj`BoDu)h@-n*W z5_`aH7x9FrK5v<0(vU-75WPRn*Xu`q_m(B`r=^C)C01NHCtK6hSg+mcKi9u{C$NzN zV~^NPP3e%Zw6lc46F%qlxURoR_Cv0joKUS856K?z_nqZg?^H z`>ixrI~&1*T6mIui10f4f^kbY1=5MCgP%PW@cg* zVohO{u0qLtOl)y5AzJLK=2w>_5+xvEM`a(O~B$@`T z&w`#4d`O=Mx^&-J5EHRZ5u9Yyhma(LSrej)GyR{XFu%IXth|E^%ipV(Qu!OxCJI_x zxSfn;18G}v+n2qejJcy_y&x)u3}OF1@jBTu@9whgdLrubG0Sfg_i#Q1^9jbI&G)jy zr10`$OamlM;ue&>`*Zm8x;DEgY>hTY9T-n3B zKc9H03h==VOmGD(TKm z9i7&YqW-EO!Ysc~bgU_j0ctKPn-UVpc8(3zBXOQp==F9WMI|<=^~0Y(G25g*!D}Z+ zCk~Q4FZXh~*3UJs7|u1-we@lPdtXKJKJq4Xd4{H@si}mfKBVJzw}~)_1S0P8aC#Sp zHnoMV^@qB{$m^oe(3kGCeJa`1;e;ep?Ri-z*1uaEekqi+>#_Kw(yM+BvDvn&;cf+( zv&wq8s_99!li1~rZKs`lxe}_ynPlp80q8Gax-XhHcT?G z^6Sd7%VMj;;UZ#CxCC~cA4g_NE+56&JCf($bm8&!?DH9M735C=#D-y1m|h>AMH@%g z1GvMeAmq2(8QwFVv2S)FYEp9H?ohYGex@D1=`9S7o!b^KQCUTwt>Ukm6udLpe0y@C z;+h-}YMlC)W`1oEy65F~q!curJc{T1mmO}1N61((1E3gAy52!(vauLs1C`HT6?z+5 zYr?(~%}y1|+@tq`%5l3F{fkCaZDL9kE8h{-5adMcFB%dqkhT? zZC{&C0AclX$^n&EsLSvxL@3CJWSGU`2hihh*${`&Z%}~}*JMKkjx>!y9dz&Gul{_~ zo>cL@PtW?-k7^DX>@t~v1pN4J)DPr9o3QTC&Y97dhQaW3_D53(bNZiAe8RAxiuN$q zq-sbCrPv2GDU=AdZ zU^5|s)b$HyA3-ar1}K|G2uk@g5qg6`H;f#PFErFaOvTqMfh9P92GQpR-;v{~K@`^) zn^8ztx!H;lU2(~5aS8pA7gX2{-~Okdx-hBgdqSyo#*1Mmod`Cu-0Gk`D!+6O)Y{YU z4Ig;OQ|6rZNN_0z1c|yrU3qZjDI;MIW8?Vj!%vR++u8^>NVI8As4mg|H;Brjb#>3# z+{Edul0XO;FqjxM+HpyL()$e;UE3?-YeL+YZG4EXkmW)ve~y;h=~EB)_16(bbXA3S zjdQ}Pc;BSWNyhZ%P{d2H2)hkcJnKi#G)YgX)(=ypV%H2#iu98|%B27j88TBPg@RTeteNST+PNivAA=`fsGbPcvKbdWzOv?52! zN-j|zZHiJE&vl1pvrNTM>p<{rtfK_&Jy^#d^qDp^DOq|?*{`ivXoV`kost=LEdf(kun-;lFnh#uXsCDQ#ZGV=XB zs@3VSrR_O;;{q2`bN{$9T3X_F`NQ8;6++efPMAps@maOx1jZAhZxjPvdOrt-?H>&S zQjdz}_0B#W7QOMS8#(UKD`hiH{rH%7+Xs1HpNqz&m_#WIRx3vZIv0x*k zdRTn$2;LwLA`fp5+L8*H#FI=qPlfOJ1jK~+8x%0JM}CV?>jMb!7g2_&Hacw6FML8s zc1T;yLNm%BJ~!*`ixo9ML^KvH(8HLnd2b2-ONfvUYn63H?2b@v&7*c3v0;J&FW z=m)z_R=ushx`q36N3w@L2e|P+at;0My+8N9hH?qM>UaGLm}@v|V<^RI2?8zyXx`zSlTUF-eC5WumXWM?tDUW7{Z zz0hP*XlPqhC)kjZx{(@l#Is)kji7Ve47ov1vff`w)MLhsD}6vDyo$^?W>EOpg_IJ4 ze4T$xRMUwKNaw8>27De#T!Ug@M0Aj42mTrG*M-I)Hw!>X)u*yf83=85y0PJdjXYd0 z4y4~0pr#g;1mqPJ=){Izj))Yh4pJ!*>i7kqh@eVxJrh%+db`M*vA%u64aoJ@n98rN zfV(f|FD{h+V%Hf^c0Oqc;*;lACM`xRehyWb2n#pL1l1PQJ|{TVoi(=LmL^B4A3#q{ z#$BT~pf?#QQuwGXMNkzfz;B57DB_#{W)D?<*(2(o{M<4w%ZHVdg)#pW=w0(9_aO#{ zOh*SHm4m}=NUGa7MIsOwOn*tgQlLW$6DdHG_F_UDMGVWyR&cH*I=R%EbgU=IQuTb% zcL(_p-rS+R=x8icc|SRd9&>K_Tf^h>4r_=(FY0X08=?4EXdR)t_JV|2!H1#Mutz@k z-E2`{%J2hpXt;!wQ?k%9_jPFbRWq<0580Yo6z(|n{7PtMu)fVej0gGCQTWH6tI}uW zi0&p5K1xo%cKFJwREuehmJ-*@8@{|fHTA7t#`Xo%u#KJ=FX z*JK*`4Am4(I-dPxkR-;g^{lZBU>lfblB@Pqc$a!mAgAvf#2Z8ZT7lI^>9{_uNa17o z&JXu4PPy!w*`JQ$&Q_9ysQDx32}x1;I1+Umq>uk1`O`j$lm~37S|*voBzctlGPyDj zTOO{>87CY4Wdw-b8+F?}IVX-9Vn&jcaTa&rCDD~`?evT7gJHP@@TjLCLa%;;iO|jk z0c9X%BC`-q%q@SP`9g$f=E6M>4ULttLa%_uo=p;x;O(ayMWTi8=pz8njFFB*^N?@% zdsL6OhDVF>6_($55j=`|0M3~av7-crzYU&N@g^A*`Cwk)5Bo*-!(Ns<-i}_wsQvr)VO{9bjXzw=6F=$8fuxH#eeifuw$& zS6*MK0yovCD^U%Bau!fJ;klQo=$Stee-5Z!E5aGhvji+UvrLT5T4POyTrPwa8d^7^ z68Itkk7EP-W>^fLY)i1pwAQ^P4C6%V1s4qQrjJ~hP4Q;;cF)fn#Pqvb={CZ^?!$r* zD5ZLOxjx}&v>^(S#}4j%)T>sNu=n0V91XYar)GbU?IAxu2grqoh^E{em*Z_hhZcPw zn8x(FC0By0Mt(P@9Kgy^hP2RUW9$QK#aRfd# zAk19(xs&RM^d|ljIf*TK&==Gbv)`MG7#RK7O}`)bG#` zYs{tub{ukireg~ytG#Gzei+xkzxH4l4b|-ggF>=$reJS%UM)$NB(#r69ZsC|Qwm9# zshB@Nf|d}Ube^k!Y-Fadkpl)fPebU16e5@)KX7-C^2+{i4w8lSe^G&IoF&Vf&g8-5$>e3` zhChP@5E3DS zrvev|J?K`z+Ni{I6rFJkpZrRzI^cwOM(2|n}#%~HC0Azn$jCyyz`bmbK zcW-xHy>EK=@7LfPD#@%85hW^%3o-6!9G}mUGYL^*#FZ zx%xbp(N|X9xv58A=<(^_8xS*kTAMt~vSX!ZsbqiEx+^KuWB>MYh7^b2&1Rob%NEC? z$efmCkJv*CiSzbGC6#=Oa}y}5A6_uY{&$J2_%C^4pcRSc!P=l<7_U9$cDJQOC42= zQ~BLZt5rws=b|kBiR?VVNpf)pwUw3GbIrkz39bpM36%-!3G)eoiM>>lRR5OjY?B|b zlNoFl_t^~2Zd2UMN7rA>9dky(b>%*L&7JjCrkInL5_o zez-+%>UKPyH?LjP^O_!+*errBwla~ir&UzTi+xaF<$H@*&EdUy1!zZ*H2U0Hqx zz1uuEtY0+>TKX?;tq4pC%n57?jBFVS8vBobK)%LZ^fY`fe>gn!tbPs%?+bA$p){l1 z665ezxoDl$jTyxaki)veI)uT&7K^rtmWt*LIEE#m91+)WZ@4`T?gS1fM&nbO9I{=p zekpV&NrodFje;`pB>J#e59Ih4#-mv^GiEY+nEfp*>Yxy{QgbF4P171G*J92U9>C5f zz3K#P(Fps78^&yTDoxdG(SOc7)crb#^3HkBevf_s^rN!-{5F46zxNm`$osLw zH59=WRK%5;HPJQ9u(#~E-u9kNcr^B`ppxWwmz8}(n&&JDZ_v25?v4#h+KO5G&DRH~ zevUa%i5T`hX~GK9QCL0eXEynqS%tISSIT9Y2~a>?=>-Nhx|=yQ6p>@XPf`VEtuu6T zPUNniGm89mV5@Q|0iq<>kkH#5 zVc`>n^L9~Wiw)>nOh?*-N2**g@>5(?w4#B}P7IDhrR;Qdn+=9BBy zjFxG&l>v}YfF$k-Wfa;2%oQ2LIvqQ-jFfC5(=qg< zrO6GnbbjL-Ru0cPKEfDlsh50@wbwU6&GklP&f7O4Ir<~X*ZppJ@8x!1qji+iPhZJ* zNST_k(tJMe=C`oq(B*H~)4AyL-agwR=0hZbrr0{pk}mJ7o$TAIx|2k}&?<2Ow=r$M zJ?F}!bj_RRbA$>bu^j@25dZY1clUrMC5BY1#6c9$&TvTv z(k*KJP%%@_8!jlry%<ZiWC|$K2{Xr zlu>O9$^4WlRFq_ufRsryfCMLGAp&y_7=Io@_h9I0#sAg8bgU_+ozdLin-v*FQ z;3Iog*D0ojR2x^v@}T;3X#p1qmO)_!(y$B3t^vw|dS-DW@| z`>_B~rWw&X!>72d3eaukB!N`PHN4JL9m zOrz(~+b~!Lw5|IzT;Z&%Cy-FfrI^6R6`@dJo$kOe`e(DGCdp$L>uD-LUs~EsoEQPf8es;uyNS`_Y85^_Qm`YpweO zLI*DY>@NFq<~J6#wU1d{%4-UrI=jx_-Z+RxF3Exfq$D_zNfiKpuvUhl{K=zyNNU-D`c<4Wz*?E(=i>I@NQ^p^ zOO@TL@~wMn8U%o zffiG6t*?l_usgxyA({7c$xFMHXz^OvQX;Q<6&p8uxM^y>-RLT=cho&JSI#fDsWVK5&hKXMo+2+ zD8Xs#m`TH{swovN-53bGyL7kGiKbN;svN9!clMLw-Pfi6a^-3~tB?4&nZP3!=d!^l zfI*Got7cSE8G6YKio(x6W!?~Cpz$e0KwyJz)30tD#e=jdk0Ovh!D~OzhdEG-rQZ-? z_z3JT@w|qHt|+f&(Pd&22?~EnPp!t{k~5-ART0WARXaLlt#^%J=#rQB{Y5Ts9~Ly= zHuelhrEJ<&lqlvS4qDXAsHI>0$0Ma$MwZPh(@m^UlajWjfQ2DA1xQ$tF>P{Wp)EsLe14ABs9Z`po)?XJ3iW*XUB54XQqXsEC52M@ZGPWY z#9Fje=?qua{(|4DyQ{mgK5<18J2fqq<5NfPnM`xOH5jgTIX z2y#|zS95@H;zu#+_8zC-KQ55P!&Tg?z=zh;e$t17zlq=NtNG^0Tj>?kiFcs5pq%!? zEIeaB6RigrqexUd=HED+qLsN^%@voejedu&ozH6<7#Cmp2$1Y5G!1?cCNb>KK~;k; zu|8Onh(W*eJ?)?dF+E@=i*-JOYj%Lcek{p_$WLmp8432>m=xW2sS)P&4aT@yvWnArFS5^y$Hk*ke{TvNin<9`;}ExHPDlKDIK5rD*x^k>pCH|XK6-RfFKP|OFODdQXr?1)JZg})uh5>xkqt-u=;{_uFA36r=c z7q$wFWXO=(cXtubHRX4q2KM%^&_U!I)LoFoiT6JORKZ8H;AKZKbuK(V2yV^*E4cb@ zZob0t71p>;WYp1dGvNs%4SiwTMT{rXck1YzU zl#}s$Wa@$kGs#73{$`+%h^L(d8?RGgmn%WkzJf`@qHRi0%uE&DI93pd9y1=hB~3lG zxq+IW38UfPiV{N1)*Y%l|DgSS7ve?!K<|h4ybYFMQ=rD#ZwIWmDe?|<*YyyN|u+y^3qa$~VUHIpqIiomuViZQ$ zX_m#rsc9qtOWaBW_&GbYcN1KdJ(JMlL%~oS!>CxQG=v?TS=q%>vh*6Dd!wsp#cqmVoR5b)41x_G5HS;SR#HbtJBW*yxXlo~UPC$$u=cM!9x z5BSsH7(#BWR;{co{qI;Ae_@a?l8(&|vdXibsin836>N2lrOOp6zs%FfOGDT(lR{FJ zX+MFWoku1juWOH8BrJ}bro1#Egzj;^KE)EikfH`sjbkCYrGxITuKuT?eO51z8O8xG zw}Y2(QSaas!W@=lRttT6uajYO6776!V>vN0O1HKL;Yt37+gCeU+Ta<%?10qS>}PyA z+^*zSnF7{kIxKeB&eEfagx@#UYjn~mZDgBP&Ja$?=i^c_k;Vfik~4@)u)9)J0zH=# zGm_JyvXI+wY0E9|$&_u;#&FrlShL`idRDtMy7bHmeu#K9D+FLUd)Sr#%yQ!PZ8+f^ zEsYf2)~s%Xr2PT|8K|8E)GK*c?RQsScb6>nJ15X-et)ZPs_eY%jGTGy^tpp^vE{X> z1)VFohS21*nY)<36lLuy}Z$^CD~&7o z{VonLAZJaB4xD}e<&))Jt(ozsyh>duC7t%r^q2WJSY}tqCE6?Yu-d)Nu{L;RnIn$~ zxj#IMBd&yho_7;!1&JMAKu8YZ3ji4`jJ4tXkBJ-HwX52=53X;cp(Pqy`R)fn<^Y zH(zht9ZZ#zK7ayU%j;||glFeJISI(AngG0NY1G$j5Z%phoTfYbiJ-Ho2{jORrs^Rb zb_V<_0N9>ix!N12cHf9ZV~B&BN=!(IoTkz((4i7SB60|~y>I=8%?4_%&@J|pKAT!V zO70@(B`{Vn!SR3`vPhtx+Wa!+#xhNydoIhKr%JFcTzL5(@>Yk(QU1Hp`kFlKI@1e0 zNJHPTdHiQXu#a9F|5VzeqGEKE1zSTC^|N&hjln3BvQ49$v#z|BlC50t+*X}4r^Ks+ z=Iwz6qVRjbu<%OM#(QP>N5x%ubZ1o+*ABLt0BUS_%96Ef-{)eplaS!5iFc{(P%btW zK5_d|=n?1kHkV&9e*+60AB_O3beeJsHP!*nzB${!cqH63G~eRp{QR82-;z3xHK9-_80X`ao&CUqIEXZi!*+afV0s(=^Wa3N$*{IZ{NG-aT`{>Z* z{Oo9_%9P1rr=5EE>=S$M$b?=VZ zZ2Lh0wnyGxTio3)MT>uI)$NhWP09r__d@GAT*YjEvj|X(0?+%I|Ej@+WU#NRsAyxu zB!Dmke^p1Lit7v>kIN;;>_rDOVu0OGzlJzV$XoG6*2@J`jbp6Og3aS|D8e}v-0RH= zydNduezIMy>pP;fL?wQ`@}ZD|eh$M52;*wRs0pIq89(aLMj)jET4H~I!>}iQKB<~| z*=JHn$CJ=YP)L(*IEF$Z&drgxUe`+eF}iYYtr$0=fbK8X0AmQ>9?>)K>zle4H-dE} z`{^wb{}F;91x5oWs%*G^#uY?j!2OYo$kQ-{Ex2i)ZS##bnhlA9ZdmJr<&cmpM+VkK zOwN`oTe?r_8!h1oc}+rqSl}j>2TUBr<_@ZfkOKPrN%J(LoD8^801S%|R29w<&XJM6 zu3ilx>&>pqr*UBq1!b8?z?I?@u1T7cDE%CQ3m);Wt9+A=2T%eY+B!wCQqng`j%3|m+j zD0-i{9Py9rvi@LEf{h!a2=}BID-F4^ zv%548ErpCZtKHOy0CjZi@oMlp03m`(SO8Z9JsELpPnkBTXe9}MJQ>AjNI%Nc-wDwH ztWS*1{&Y3mvnd@$cd-2ahL~;6@Z7Lk0W2in3jzpg4g>!ABDC(}0X<0|fgxI&sRk-Y z|AVr34ALxW+BM5omu=g&ZQJ%!Rb94in_XSDZQHhOPk(2=Gv|#nh?rj)nGqSWWB=H> zc4n^YPEiGeJ1BN!!0;l)Jrw3SfU~3s^vcJh97;*rIs(n1oK@VJ8`0(DeB|dGiw=9n z1=oUZ`_NKUc0mX#{A)$O8P(=!Tn};?2Mf#FZ`*!&_mo6q!7qTb)KwD!sW6oClPklO zJAtVWqp~xiHBe^gRXBhy= z$U5ln0rzQdJYJd~9r3SJSy@?GS_b%o=GcL%wTYdj1PbScd59_r0f)!Q4*MxWiE%Bs zlCUm7n_9U@6SXK-S(#{G!&isMNIEuGeD%9RB*OBGPNcq;aaA1%G3m+$Q2p46``OM} z`N2AD1ql^VFwjJN9$7Tdcu$+Mt-}4;Q28^ z4mNcr+RD`q#(-VVpg_IaXr7cRpGpUu4TC?&VfC#kb%2}hb-DuAoAsKzUBi^c_`=V1 zm68=i+B#MgzPz#>Z^xnl^uFeW`P}ml9K(T)^O-oxME?x#0t8slBtedA4!<_r2j#zm zdSASb>#_tP9`_w{Z-C3I_JJDPvu1Os?5yk){jv9HAsrL>v5w}O!@yTDh>ymIcIi=J zT_F=kK?O6$EX+s`1M_7%JVTC)^8d8g2UHFIY6kP-o*b?=mtL&s6*+utgW}D`?P36t z{@Vz)3)Bk;eYnLOJ!GjDWLgQ6@Dxk}VnaO*i0Y`cA^)TodOonr13vW;1A}Mz{UhQV zl1yFX^qQeN8nhOzciK^5!Kjvh%Q?TtD=R6YW9$Wijq^@_bK`{)Z<5|D``exorS|}f zWSo%%krY+PI9?QMcs9-74IxJ6*{GYbliuJbxc9ZMF zEDv{AV&iNnGUTAURbMyyyv5MkQLY+hJWbp zg#q(B04jQu-F%ZBvemMV^WGj)#Xj7>wO$m_5b7OY(2Jh25?&X%uX^J4VB$E5e;0jI z7Dr5cE;$@Xq@^2sKZ_zX%GdbkQafYa3rM3-5kZ$p8!jrBANZ<^uiz>GL_W&lD>%qN zK`RL7C*FZXF#0nx3^-p*WENL8biO-X|NfP0b1#U=|Dirv!(8mdvpx!%0LY*=$0f_< zqYQ_?EPOlZ0H-!-3%pTw;o%zyvyE!;gDk$UoC#=!B|6g(PEkRY8(W%_@qxhEu$MQ&-n) zEE^`xE0_LjLJcA2)8-dbx|8hbZJBng8@A0;wjFEq4M*l3QW9$y6~Ccr&24F;f<%TMf0^ff z^W9&x*bfphQPR-jJjvSG<9)rq&zrm4@^$*~0her=&BHZ_yNsXoI(T2YCNQ}%4Hal- zkR$*U1{xT!#HjiXopRdt=je2EaIE)%Kf)k%y{2jGWse+)^dLUWtI9tTTX;(HCN?Fb zhN=uJ6b!ywPj}#<0N%3l4srx;(BJ{ z8)-0bs6Bp?o{_W}IiIDEj`nd_HXiVUCq>6?2vk(GrQhTeh{Rks4Z3@IJ4(A%662@k z?w@hnjmKP<`mtl)q+j=F^})*}2@o(}XiWfH@A3bQ!y?~{Iy_L}s5Y>C&>t&;FUI+Y zq@${DC?J8v8!anZxz@FO((NxoE2io&x1b@C=*u}&Gt)EW*uglqb2Mq`2(f)erKiBd z-5kH>$8KJL%pxB$^uBL8O`OZZS6cL@?^e<(b}(rgvQ~@84^Fjyf^O~eaI150 zpr;f!U*Ko7VzT9XoIS#)?YQtZaGR0D!g{|pyg9p=Y@x9yhEAl{3z_7rh;0nLUAj{{ z>w=a@LS{BbA*&zZ@2j()j#ioIJ0S9Y?G66K_ImkS@Y3qo+4DAzb}W3^`kR5kAnDJ` zKkik02&6x%&!Rc5izc5T3MwtlHtH;0%Q5Q@*e4qyoI#jy8c3&$r}0 zFN>a}&(rrwBT(N={B^mPUb$($0$SYa8$^#BjjA!V%BHd$q=GQ!LNpgO`6)A6Ys)ut z^6wWFB7C?E6Kq71`shGArTkf>R$7Ddm#G_vEpFcYQgAwPKXb2O^lc1D3ZVmRkJF8W zJ4KZaic__5jPBApsvPd%#{AC(>dM^Fp1edG86WQEJImvOt%@{8`X_n5?#{}0&7HMYolA)mxPuk&jLoM* z!F{gh^}F{7{JvTJ`BJ=i*|q^A?n~!!O9DV0;B+v8o`7Pf*re5&bmAyuWS^}5{6&3s z<=2+aD?^QBf84MsM7@lL-`AtA%FYq~q$CA7AAI4q!KBfNDO}eE11a-u`e7H@cDN`y z9I@xgw(j^#hilmOed1#REIvWd@hu@JB7*QkS={T%PH31fQ=VYzV@h}I@ioy{SzH+7 zK^k-X?T^U?$@Yt?;YR8KvcUbBPS7YWp6P6#iRE!%i@O!)F+b!@l;M&OKoeYHmeLRn zPB7rbh!Czd4#cpRdeYwgJVPKV_CV((3tjGmJcp4xGYp+b;P8v%5!-ZcGjG@X^ug`0C z>t_zvaF48wGTah>#pU-hv_E^39Wbi1I~o=Ml{Mw%EmS2$)xj?nlM@UU)KBneOWqO- zicgpyx5*FpuE07Pon)0d2YZ>xyEiGA0GB+{P51O{gz7Al6?n&)+NZmYYed7#8V5nZ z8o{l@aK)&!e#P^aGeVBvL944|GWyUg~&a&#+IR zxh7anO7poATQ$Y3nNc)ywKsINI5bB&o?KE8>J9R?0!OQ)K14)qp zlP;zMu@?w225v0*8t2`X;$--3d3YXyH&oJc!$;DJkeHF{kEmo70je{CG9PyvStA%- z9m^IKI5&;#k7T6A@ehcS5C(1hN&?Q=1o&3nzA*ud_Y&c&`^a?*IbpguUw5P5Vv_rH zzFiW62j!MTZ6p;Pac@VDkcf`?#-n=@Yx0>%i19mX;=ya5+GvRd-z;+mf&zB}Q&I~j zN#BnMDQ8n1vp2UQ^3*k~?`pzkWKN0i2-%5WBK9m)B^|I$>w$s&{r&gN6Ac!5%jl`6 zu#vD)u#p$$I=m>q-_zm4aT$9(qz;L=$=^5NJcswO2Ve{|2B^{uQwm0tYMYXD8Ej6m6n^PpR%3mOYLK5Wzx3k zoO7i9k6WQVk(7#(`kR(O)7S280M~Om3V=ST#bmLStp@-(8Jv{LOyW9u&E5dSquH_T zSl2BnSD>kXROwdnR)SRaR~1(7S0$@kR|r=qS3p-}spsi@ZNX^IG?!N4RT|W88+Q$O z<~aZt*UOYZal6=uSaiq zTR)^sy|DLed*__Ee%D)r(1V5LEoN01LOc%A8Wr z6}cU_om^F~Z#8@@UnBCE3QTNI`{sV>JkwlqY}_>e+ff_*PWK7-N%jf!`RkL~wfs*q zdg{CNi|eD}x#dcr>7Qiu=r_+t-lZRa5s-dmcyn7gARJbLK!dmad-bw*!Kf#wAt)@U zC8$TFSR_y+RU|+pL?lV1W*~h4D-0GvBc_BLZz>Bll2pSCk^=0uBe3P8#+2j61|J@o z_`M#d$7HwYNADo8n~)<3JS;NEW`Jx%p7%b;7(G(p;`_FE)}!$9(gb2SiTc#i@EO)G#0-?`ONPHsu`2}-_PS0f*8r?WjX9x~Rg%|r#ztcLB z*LhOPmm)l+D*2nLbFt1H_*Qlju;<1nvM)?z4k~y_`GU)PK$cLmG0PZ5#hAj{VA>oT^OE~`uUGZ&)}DOPvev|C;;PoEFWnhj-X`E+ z0vPg#X2H9^UML)0L2vYF#cRYnTM)GBPgCUl!ieJkd=`JyJ9ZyP&$vEMtM%Z03Db>^ z`9^{KZp@zRdu4{l+ihkcXu_I4;Zcn#P=A5hH`UV?^{gi5)9TG_2ICT>s1wdBT48=q z3gVS@ZAtzTI;(8LA1%KKoGNXJx-R^xAp-zf>i4prXQfR$C;i~p74J^HD{Yrh0 zkyeaWYQZ$d^*4u_e-n@QBADjWXwB`@|CTm+>cD@V^qj@D*GAS3({Y4!vn@EGa1L00 z-jdB0)gt966_x48tR4M(9<%*IUm^PMcybIsV@AUW>vVDaW}XyPSpn{*WWR>d*X>vA zunjoEkjbB!u7Gq)N%&Nn1D0Afd?CMa?7y6vuw)bYC6_JFQUdE;q23wJVLxKMu06*6 zpDrihgKRJqPfui1O~@Hee~Qi)t&ZR85TDuZ)V}G5ytGL3XDrhN5t1)vtY3xpMg7#R zb7paLE?oURhNdv)K9zsJ|04Q;r{}+kF~1Cdx|Kpo6zN3e(PHQ)6Y*m=i2s``Ln#65)C#(~PE9P)0-TAF*gw00&mRTLlu)KnG=AnazEKR3NdcimbU7p#R;c{Uvc^e<{ zC*}$92_0U@_l5PV3aPc3n}&-E;65C%$JvuY6G?%ceCZ&m_YkU!_9mswd5cF_Zrfl= zZjmrPL0^C&4Ni6w97JwL5i+YvO${=dJi#yYG{Kf_b!uA{j%1Swy=6X4n3&*uwhOf&NJsWS;Df7SaLwt5`u-$&feRU2VJ4NVvKqb zPNuOqwNr&tpI4hKpgtjv;Cs@$=Yd5u`Y!AOA-S41`&*YySM^nqgehG*H{exS%@o;y zX3W-EWx7vRp$%+|cZDHuhW6c%j&$?&?iKdTz*MGQimd!@d|ay_aj*lWRhYIA@ucG- zh??x?B{;5574P=~fV9BG6%iB#}TA%R( z3nT%Gk@K!o1WlwJRXla@PAJ7rD(Ld8;tQ|+ zvhZ^AvQxJtUs7sX-o%F)E)V*a8~Wikx;o!;372u3vAh9Ily^QtB7(lPy##hsN=u6l@WdV$)1 zS7hyKRayL|I8=Q#N#o1&;u-v+UrGOGQT@RT@BGucl%<-Cnwp-NlTM(BFo@x0N@I}u zqyLNV`Q7LCv0}LfGW0c9a6MLY_~2R|O+E<+gqQpJWVrx5y*Mk$D z_LLjzd#MMk}^RxqMHelqvs7? zPyG0?D1Q_A#_WisTzmX)&fCfp2TEMGzL``BkJz56y>$Q-=U_MfF4WC+1o#dQ5V;3iypS z3mN9aUoi2OL4g+|D`g$naB4HoYCuca+gl6k>kD|>*p&YNTIhZd*?YszRH%{=NzmN% z8VM;mC9;zb8dB`(kM%!u>6zkH38>G+IoTjyhbZD7&?+RXcN~CIU=9_Pj#xl#Cx;IT z*eQxq8d2|QVD`SP*ImGL(ydrl zr_byKSEB!qEiENc-v_F$b1o0s(R_Q@)-`(bQ#jD50nEqa<_|D3XQld*((x?3`NA;m zylR_di~2Q)#}P4@Wr}82N?y)Daq$V*9rSl?dm3kc48sF|kbZF3rGx~%9;d!v#p*wJ z@7wSAoRslJ7cp(;dZ1IHKfBa2$Xv7P20JjhXUyFF;`vxj+Y1a_tl5b0((sD*Im=b_ zGf9_0)xqbD5NXy)^PipK$}*$qF$G?n$@z<bW$5 zZ1vih`6Q~8jR~eFB@snM`N96b;s0?2c)OKeMuT$5yJG~6$s>VY{ zTL!$X-bX~Q5z=!Lz&=RZ>4YVRSWV_20w^y6e&?MEVp+%=_x<)mRExOe!L2gJ zt=j$Ju}$;LY##Tw62mElX9=B)hCQ@$%(EBuZ zWuWRQxw1C0AN=j^H3WGB#Q;~mI=Mnm&6yb2EDcu?>o{XNUTT>Ij{)dk>BG?0v zM0gi69~_Cny9<57J+SkxktC&fBgl)924H)BG05V6Ayk`mS8;AcJ-FVq4I{20&WLq9 z!F1W9<&&3_*AZ$YR<9hP)^S&=y;wts_(_%JQ56!Ewlx)WFBM0&$_y<^ zKoJ`FB@N7hYA6x5bGLOzQqe7@-hzwT0K85t11v>{9t=SPy88wDK_ z!+{PuaX9SLLEnivgs=#JxMi_$a0W_EQB}dA1izm?;wTL%2PtV10x{hT4DE0DyS#_w z)2{fRe&xuqslhK{p*iCbs1&MSq(AqiCM;a-+2=ZShsj2*3jCXrOHY%kQQ)*zhn8+f z(Ra}RKYAen(BTO)-07ep3B84-$*}r)#4*yF_1AVG+a4a z0auJGFyDH-OkIJr;2Hr2oGXdow7I}EQz3domH~&T`E$Qy`*(!!N3rNdCJJDEtNbSl zSX_p0)C+^sRbImzCIAynvQvHs^c1QoXKE4!*LlhxLLi?n@VE_r!;td=FIz^rka12C z*Z#cu(=rp@xfp%&ZfOoLDYktCwN*`HOmlT(yVHiZ0X`Bw%^mnGtY`O4LnXpiP0f{o zXW{lxFwv2>pn1xqy5LUVY_6v7BcO20{?#hJ5E|hD*wy>8XYH+_%vYmP?K-9oR16oG~0vq6&qUtF;k! zBz@JUvJ~*P#(|Oq%L9=>Ce>wVM0!C3McEkv!xb?% zX6lbYy}5!;4t5h2$ua}K4!Q?d!$32@mFS!z*P!30h>4Y`$pR{gXc?)wEnB3-+)QBdJc5A6;rKzdl}pX_ z?Uh1{DgRX*Uix3ShX%nIqvU^(ypV~)@1e8H5?@tO2!TBp(tW_e_y_)M@sUSUt7R=N zIQARX56C)ZS?9<@kyHC~Wk4tG%ZeXAtDiHR$n;);mm~_`i*7xZa^gZR&pP$DdVu`n z{$>r*RrKb4z?S@+q4DZea9CL*2%zGHy&aDjL3V*03y_vzOGD$;)+8%C23g@ZV|~SZ zZ$Q-w6bK@wDH7UZnlSjgQE^`K?cs(~Admu?k5IK_QAnJJdA=pE&~ebS5V*&J9J@^Z zQQ#ri#MEq-5-7Eyx35U?hjT$fx$KW3yNanw6c^DkV}U)#;U3>uYsev*=JEM#h6T=D zO)kYw8(sp^eX|Gbex8oL{nHHtdWc7`w6dRX+I&sR1`;q`2_>0sq|qwZ2s+V{KMr3A zuuY)J-}6F!jeUS|**T~8hxvQ{oBl0>+r@K-EPo@bP*|j>P%@U`=8Qd@nnFquB9?`Q15NHe{bq-F1;<&By}KIFYZ_H1js=4St3}rOn`AK zq(W?8t088(sXpZ5-^OSG(AS-f6&FmaYA8j~H|PyM&=m&bcvDnE?PTNi36xi)ldr+o z(cb3ZyS(nsyS1^|<*D93Z2~1k#a)Wau&V+e{hv0D5Sj3dL%z)zegd%hkV_DY8haNS zyEqzRyEX{>tmaaC1V*Z*=)Ft2Fw;OLWVol@i6pNmhSc5-Ot_GU6A_MYc7$v|Q$Iel zK>eRz>;1io9%g!In50pXKt+@yM6ZZ&QF4`bAX7ZT(uJ%3yMw)yL~;n}$|dZ4JQ^uE z!xlplfp46&C2@D|>KoT}d;X!wy0FcwlN#EE2vuZ1^6OxJB+8oz|rIPOWG+2t-_%oal38{a-xQC ze2z813m?>S2#s_E`EiR7Kv#RhoRB{)En2{i$2lTd-d->Yz`U}26h1&*fz1Vami)d~ zV5LL+ciJFhv%OkCzb)LvaC#e8wcjdxO@m7AX@*_B_AW-Y zX3lg>O#eYUVCEvEV`t$aWanZbWMblC{hzYW|BH9r9{ivF|3y6bm;C=mJYZz` z-&CL382?k~*~rAi%JhFc6oB5|$i>y!$d=(hV*pDd=YP}#XCo6EGZz;tM*#i*z#;r+ z;2#yiXjb(#&WCL|cCU?uGks7y}=1!EPjm>4gMtZdHQRdq(kLPc5-j4gZDmem;^8+G99n#tSv09?)xez`u@9K zz#NbC&nO;m@f#Z}6&V=`8{8ybc2;h3(pD#b>E8J-405 z^)A&^O~h19`52cJRmD7NWc1}`pT}C1mRi-Hs8inoC;o!BXx{IppOARBq^7V+Wp0cI)2Jc@KyH}aDU+%q;5v(rY6FxQ_353jP3bIMkjX* z#66)!0f=N{ELmptz_A$IU@lbpBrCG$>KQTBQbJd|6l))o7`TuwxZ zBfm-SC8PitEPO4g8Snz@xcNSkLUOC)4;-5f}6|XLtcGCQRy^x zQYVYa&-eTLm$Rj$n;@u`0p6d2Dx1&-PIM(^w|JzFIF*GY20eJoE$*D<{M3PBwgq%r zBDpcg%{?~sjO5xD*L)wDLVm!5%XyriM{}Gu;v~uYJ~&-&IrWTQ-8Fg{tVh6O z$j%o|fTy9Rb>}C>S=!`73yz__Zyq8>%Lj=da`m=1KpCn8$W`aM#ybT!ne%CSEn}^c z8bq+9SE-wxzjyd)l#g5J@Q{G|lLePTwjJy_MKucr!-wU$xRZ{Bq=1mztZEy@L z^_KNo&ta!{mr}%2G_q_Yq^RTA@B3GhvPrCFx>-Z`V1uPbWFBautb=Wo*J1fB^mfef zvbk%%s&V}-d?`(dL_{!lmvgf9t))!;hlBe)Wc+I~KxQLnw zUM!6!@D+)vu)2*ELre1azXHbyR|tbj6zLMoLhS)9(y$jNt=_0qa$&{G>u;pBn4P3n z!S~OJv1U493e42%!?Sh)7f}RE=R+sBtSQ`e2ux$P3pX)3y^X%>!1pD^+_#6iFZ=8+ z-W~MhNOvq;@96p7bd@nQT;HGiRIV7c@797LsQe^IdLGNkR+v`_tn|d+GI8bV zu=*{v(VP`1AGgrfZPyXr6KSS|&%|i#H+UBAK)OXbWAczf#3cEbbfa$XT~Sy^O8d!@ zt$rz5yc4RmjG&tL-UcnV{u6;$m>D zjczk+-l90E1W;^KLSC-nDeEbX`SdQs9VBp02E8jo-mTN2NSduKeq@#Yl>_e)?ntoqEdduyI)_NlsB zZ*FgHvU_RvskutKT5rVJvX|o#>9D(^7nhI_An2l9$MYe7PdfEdi1rYE8{Ed^H@A|N zlBGO0{K&mBu&m@T9HazH(&0}opRhh zek}eH-#2aJuVa`HVZ~JvU8L(#60Jqi%3vqjYwD;{CDwE5m{KQJd*XKIQkiD3hX_>Fbm*vN7?=FT#MbjCGEHk)?yl|fmx;K6x>+Yq1c{W9fLA^I zxRG$BCNOn^X4b^GVl(@^8H?ML@sU}!dH?Ault|!u)Cvt+`6nS5k4*UFIlV_}ic3gW_X?40)<9U!Cq;_)W{Shp&`pAX^saxFrGeN^G zJE15R2(9W2DI(OxjxsS7NnrR50QSQ~tIW3su>`eArJ)`Zi7NAxOR3m@ zhvV85E>_z?vJ0zGe$T^}-OUEH&F*h9XP;?9E!b(Wqf91|JGRdFG@m zdMerewRAxGfr{E3^yGpKY$uFYoF!Q2$=o(3;!@TQNXCwzr!P*9O}0Gc?rP#FzNa~& zRy>mh=c>eb#!jf@2=|j@343N%tLW6kNv4ire&X&BOPC*Tzs%veICs0Atk!8`=J6`& ztzj5e|E=eA$IVp(;(z2vte`?=;Sw>#tQ-1hYTe^w!}ooYo(#a+GDmC=T{(O7mhsX8 z=0#cz2M^*?_q*z)u9P5I}daxO+&(JWOVyZ1mw(Zqj9=TWgkfs6K4m9YK zmG;TZuhr3pT{8Q&7G}vRb#jADWrL(zcvtIpw!%kEwuaHRecOqcT0VbshwcC1gZdmI zd${<;*^c%4TLYG{gS@A@y1BHPzJ{EKM?gSl_n`vD#+tgq+B)*@{R1rQ1GxV0hWKH# zg;s~CZCv1)f#8yc6YAL>Gul0xbr}SgS3GP$7{5|ezPGQXufkBJ5o2(z=6IK?8a~v% z>0Way{5ALn0I?64)9dsw?Aor<0`1}`Bi%?BMqO1wtF!|M9oWlQy~hc(o}ZQ*^kes= z#o^~fsNbN|_1Y%C3OWW~mZA(g(lkD$;U!`V(Cv)>5fAdccMQpXYWC(!Q}=-+{f7Qhox1 zRjY5y*>{CzdW{p!^vuazmE#)Cu;u6U7#=g~OUny;2x7O4FUt5!%P-P_8%Us4{sZ>2 z3;eOE6ny^Yhl=5tLpk?tr;PV7ztHrPz>Gl;54Zoa$)8__!Z7$=x~U$DD~r=?EpJsJqYkVE zUoqiCxMx)%kZ*`g^{FoIXYoGf)H8j0G|pTS5BhhIEFSorq=xtoZ&#i>YW;TO6M=QD zLPitryb?9coHEGnh%>tHIT0w{P)kb^0e{Mk6Kz=ww>kDt!%CxsyJGij zc3?RaYBR24JXO0^4U~5tG*u8pSJr1xdSH$*3_X<^+L%^mC1o+RkwNPr(iO;IxW#jz z)*B1P_**gAH3ZipJ7+=arDli6(%&WKKnJ@11c z$tE0}20s8zguI-8YA_mnPZGwb>?Two++oiJBDM7QY$+*h_df_@){)c?L(|%?5|Zfl z30XvsVPQzmBCvvj-<3VzPhSrNuw`caT@p04LCRJ7HDYe4ZSzBb zsN<{*fHTue`Uk066q%G2%J3c&<33=bx84|flvU4&pplb zS?$3p41Lr>aN%y?kooZ5792Va!WNv8ta$@884I_pJj=UBTmmivwYpQJ(^|HhaEITd zM>ah}=90zZxe})?<5~=RGQO;O5_k#T*qNL--f;JF$%COZSl7ZX`>*z>m;+pNNMsV= zJO;wD15;qS*iYLu)vBexN4)Nn_MnNj`3Ia0ThXjin7zYei~M$i`5nbZqTM0PThZ1o z+PcI;wf$#+9_4-?a*iT!S+^ufCOE^H5Qc)_kJNpG3zqXCmj{TKgboh=RWl7tF#RGl zoCGQ67(_Yg7gmK?7iGZvv0u{o*TLJ#MeA<$JA8)`hrCvwOFv?$P||q=l$;^*w3XYw z?m0*{JYK6XneJDr&e4^yUSiMR;GbiZU!p;7KF&7hwBh_7z<-Fx&FEW_Yp;nCDiIux$JPTCjw)z^j- z=U-zv4oyH3=odJXS(JSJC1Co%(WBPt7L(7@1zERtmG+c4eX41{h#Ov+u!Io4F}SB8 zrop-rTX%W&HmG@9qd@}BgIDE|8mWuginEL(tAF}t(Q7hzAH6DwW)bz830S=ouHadk-E|V!^{9I`3E71 zj0!k8zDpH)fpks+^Zv=*6U z0AIn*#ch&FtEmX(s+8hk`V++y6K7w{qsZ$>V_3zk^APb>%+=cN&cC`{YQV)D2a%y;Zw#0kYdWeaKptMM@lb8n zL+y$EiGs%vgukgY-a@i_cu?V2vYl&zct+z|@D+5r>7Yk!$-Bag=j(%$Qu(qQ z8Drd|E!RZ3BU!j&*lqb#v16pC#m7{AZOzVvtr&L^3t`wzn2>>8 zl4<`8Pp8r)hyL=&xP+M~6;J*<80dJ{Lf_#-w{%5P+Oyo+PYaeppwb?Ju0qUTl|Fx}2=TL`z z*&fZEM>W}Q`0cN9$hbuebI``ATZgWFz&GUvbQy(7mZq(;6&pl)n4McF4eZk70J~4l zGTFK$#A@}l8Lmac`YqBVgiii9lOvA2EQN6<%6B(u_VM5%IndNM1}|ynoF<9+r;!2h z@@IgEPOtv$Vt=ZHzXoT`cG1AJ2E}Fv>4L|je$B{%SfRoXcEs(9sqqAnZtAis`q9M5 zc)#3;s+I4rXQEFJEi+10s_azt&{~ZgG8$!L0VV_O;z4v0Tdsg_NOVNjHSG*bu7EE{ zZADqy5+M;rOy@M{$Mnm(m;+NpAvC&%n{o5vDLNjF=EWjut&I5+zIClYoff6`jP4Mk8mg|I^t+c zE(Qw~<@r+FdaAIEWWcWAG)O_8h= zJF8<{*Z%h6>m84WFUyCAoV!ubZO2Vx&%a+omk}<0pr5Vs;J%#YkIPorY5>7~&zD6& z53%bcNNpziF&_Zk*WYJDcq6-%81M^KI9Fwlq1F;l9OL~b0td$ua#e0yfqEvl!SYiR zSUekFGeOK9G}cf1il)IJ1Kz~wy<9kL$YFam--#4b?ZXHK?8^A<51&`H(Mth|$=~JP zirT*}u2SdOiGxy_GRs`BU1GrkQ=IEZtuf-lAtkuf;Txt6Fb&14_SjYGBD2VW@tP`j z)i8kGU8a!6o6So*VjTgOFhh*#zL;T*!}#O{9)RzD8X3ke(Ap872QH`&6Hs~*>WeGEdkWEvfSSN3=qAX@3Jl^o#LOE;BFzA)+F8m4Bj8;EE z8ifCI<5=B&eXe>NS{`tKpBnZz);HM+4QpTj)1M6CtoFaF60ffL)U?Y;?7G7P(ZEZZ z;08T_a%&aGt`2ne-jHJ0h*~)5q=_Y8AHrS8`UZQ?)NBqBgx*QH-ZeYpi$>^as!y`u z-|4p8wmsr;M(f!wQDmDgXIl_)n&VdpC&zpO^W3en&n#<4DV$L9m;?HQd~uh5)<3cI zz`Vwq3p6xfQa{2B-y7EaTNyB_hq<|YP$x!p?3mXbX-5|@>n9pVGYPZ~20HoT_wPxR zJ5$1Ut416aTpbfxNp+=AqKWU)29U$)@auev^g{=Cy7m00`uF%3U;wHYa31;o8ITt2 z#;~}n2FSDStk$@#8s-L@#3y3qd%_5g=&DtJ;?{6jkAg?c`=Z`{-X&B!tkU#I?IGj( zsxTO890hfd3BW8> zx09e}&@aPk+Q>=Anw-||si>?z)1pkKAztuUx_2#Oq@-ecr!nd4g-UYE4c#p+iESrfD zYGn97T08f6yUOy;Tf9`Gf)~IlOg#lPFp`|T_u6aky+Q~oh%!SI5f!VVAvp<0lALgI zLIRy4)(Sf6bhK5dml^5>osW+8qd00sai}=XSk!85!OJL2#nw6l(h7oH=KE|glHd2t z=pW^y`RqJ-*IMs--sk@O-gj-=`NQA4=Hkr{y#AmI&VB3!C;i54*IoSb&)#$0r*HYr zpEg&n{?>cGd*Su#U->Uzwz@m^{G+b=@bBLGAMSkFC)a%K_RIhN?_YoT%GW*cU;W4D zz2@tA>2DvM@%hW=5C7Xc-}86F8^3(p4IjJdwxdq?@*561 z|LXttsypud%WqtN_&t|D{MCUMAGGneUogMwwU?~AAdHIvx`tnmg^C!>v{!>@2 z`o_WctUj^3q&xqZojY#5>eA7J4!ZWz7r*kWk3I5rTfY6Qd$zr0)ib{Ojz4(Vf8Bof zzkAdtZ+iJ__x%3c@po=~+e;q$`!BwG)kE%i|AT&W$5#)0^3OKzcgXXuy5->Jtp|Q_ z=ku<53qM|T>;o=3=;lWq`oaqydh3HH7kz!(0nfeR;EP^==t-ZSzk2f{4mkXTcb;(h zE5E#TaN1R$IPRQ}{ozq(eSZGIKe%hhr>>sA@SZ>3^}Rzc``O2?`12FbzwBRp{)1n* z`QQ(J=|?jU`{d2nKJxkdz59}D{`%(My5#q+e)P&aFI#)~KWur?CmwajTfXqFvo81_ zn-6>A```P$YxbLY_JdA*#D=qXzVHF(@EyJ1xpVgYn=ZWNF;BepM<>6#eZ{M4}Z;-n}6`C*X_RP$$$K{k38(P``yiM+Y@fs{h5FIo4;Fi`kuS`D-Zq74X?Rv zv zt^f0o@t5z||L{+J^P-1;_txRdSH1nj=dIo{dcsi;>3#1*3+KG~#AA0He9<$nc<=6S zzU!PvtbW3mHZSh~o^KuYmd{5#|We#y`-g*n z^SB4!{gM}d?gcj<@tVOyFL~ezgOe{i=L7%yeHT3O4ZnTGx_2G+=Nqn_x%;u#9{A2T zK63K-Pk-kbzj48pkNCpD7kp{v?uEIB?s(eAk6XL@GuQq2=5NpZ^eNA|{FN^_;49lM zT)Oxhe{pU9gI|2_JDzg$J&*hDWqWQ zb7#&!`@F@u=b!hSQ#YP>*2Zqr%4659SbOY-O$%qtt=h9;$EwZqTbKCq!pz*xjmI2z z+$krWa99o4vu4k>9ov}3%${vqx9?oD=a`k#OV;qa$g?YFrbCv_I%ef*>rb9pT3Fb6 z*8I}UV0E{;zpCFHHM4nf!?wBI3yWvX^jD8oa?8a{o7bFj{0aBnkiQ+X^30{B9cyN1 zckkZ4dUvyWVR6fBzw5f$-e7hxSjC7{JI@8Wv1irxorm+h=|tvF+k4seovWvFTD@^$ z+wA;lvwc7eKYvi6G#2M~EX^-$pQ#6LIAdYg(lLh}cHdog-0$i8+=hSPJ`JzOuQewx zE^OMhac=RLmCri)q?u=(xd2jPdfSGjsm#zV|W}!KO|9cD#A+ zS6z%v8~4s*$F9Y#Q@Pv37e=+AME7$Fuo7=Q=hL4-f zY@OS*rL=9$%+967`R!X~&fc|QaS8Q3ynioax6ZG-_s8_6v%h%IGHjocP$^bmcCW&& zTQT#`^xr?js%uf;?9DUz#nn^ zn$ng&|LcRwnkvf5UT-DCP^e!r;y&3vA_SjnPpuU`;-$@a#Q(=HMcj7QA9H*E*tC~6 zQ@=LUAKUAX=|47Wa9XZS}9y+i$F2XYRe<^e)qhPw%pO z@3`qSe(|8`bEiXIdPI#mqyCs)JpGsH9ey$W>AL^%rAHpQZry$2v}bW{^NQXK-nCg#Bk%ohX4JM#JF|Ia zO6w2$qjo$q{im7j_tGZAes9p*m&UkuINF!i>&ERQX|TH=_v;~&O^h3lCX*o;x%%(t z&mE6@leQnUQP-d3xXGyPbiZyqjB6+T;kZ5vb^mAZ9^-rup4+r>ze&??nz&zG+le;l zdE8(y)^i7K-`9@YCg(Tkx?$3qL8Ecaa2$L_`^N)4qv=NAMfc^wkN`@wVe-B|7#1`% zukDbYptXI^rR5|JDKz}4i@M=xBy^m~g z$iXn@*Yvtct~tJDVA^n`=l0R+d`7>MtTltaWUUztwZ6?@G}bc)T|d_i?H!3P%}}z2 zWf~?=n`RVA%Ggm%~=EMSs|~ zo--u98#)LV8?>5Gw=C$M2GBh4b&xg1Q$=?JomG_&p zvN^-aST<+a^<@8s-9Ywl*bOu0>JPhdBffM*fH4<5N*u*N%~;Q9Mv|+xnT+&Yf^W?kH>kM` z+lihroUo^7UpZ~tp5g-%fWDs5wiyHW@f5Pf?WpJX9Sx*!?Wk2e)Z)6uzj3dh_MjaP zTFJ{83CwpJkL9D7viUL@NOs%FP;o?y36l)5B`5t++YRK)+HTm2r`^bNCcMvQjC%b^ zUOU45=K79$Bdz5KlbC!NQ3TL4`gp0}->5&59U75t<$4g&$PSH2HuPLVOkX>ZO(ZPo zX?_jSRL-wql|-8i^d9Aab6hzy(JbS5#!h_3V5R&a&Ynx#dM+)gZ9bO;*8N5U$;)Wu z*k?52E5o{9H`e=(2eL0CQeE+iY}eOz%0scHUD}+{Wazm}2p)5Ogcf=(Vk+4{6~q_P zRjql462>zy8?r;>@v=j>WX*+)MDjw4D4R%>DA^tNNAg=^)HiuBCdE$q83{)i@SOa~TiiYsaXx?l>7<7j2%b{%P^>cPJ64(WDNNMnpS!_A%qiONC?Fj(l^a{=y<%s4Xp>NARe>><)IUF zCUtAlT4yHcjChaEXw7kYdM-NSYdf!b8Ma)bv1^B*ekSkNp))B59eyU|phIVJ&AVm%44J+3yi`Beh*G$6 z;%?AWyxZYtbnO6v3BFKbl+EcV3ThlWqxW4#XINe3V3;e#CEbvMM9zieUc4HPWlP8q zy+))ODKA|+lwIrEvER3ansXtHO?@j9D>)d^ozS(Gamr1yCA2G~mmTSmo`Dk(?v7Bkybt@sN-;nAgu56f`QR``!;)oALsUz%$47aY&+3 z(H2cTDjYl>ILFZ|jdz1G$T$L;dN$A~-spbn*-$tfWGxLE6`j16o{jG3p3Q)aB zP_WB)8&5oEf)(*#LWMl9C0&(lOeX15aDp^N^4hNPoC!Piz8yupxVGQxN&e8H^lt;Y z29m#iPxbo&p_$ga-^bwRbNj92k8WA|Eeam0(?C=G9@;Q_3D5`-^11XmbKUyO=nUbc zbOvjcK7|T}>NEpN>sfn+W}Ttin0B0TUHW~h?S07=ZSl0vj2lT82!SP6XpH2l;2SC9 zeF_6ww`!m#Pic2ct|%X*&0%>x=P_ONyx#=L60OPF90gzXSyD5U;=nh|lPZ-VL46ICRG2MwuV6L=)*b7B=fcG$qy7g{GVs8UvFD zoqAL(cq3m1Hb8M0;*vS>0Bk_U)X)Z5^M%%GE$L)su14V9lP-V_&{{Sa&*UkYeb%bD z9}#8n01+Ri4FemXwQPXhqzqwQWdF#Hq?eSRC0BG7Wh1EI$rcmYN-v27Bv;K)^?Ov6 zgf-u{?aM|q?J#Rq15is@zlWy!{h%4)Me`XW)$bXn_#T?$drXOJ1R5hB4>llkQ_PTh zR0PWEQ9)DvzG!KOXe7vX7Tzp#IwV~94Js4_y!-NHQuwvBFAcesWD<9ow!I;@(ppm1 z)3}Z|+4s4aV)fLaO|svEBJ?FMR0Jh2WP_Q*axI0GcrNWm$u8Yc`II40gPb#BFQ0<@ zkWU$6O_B#goIv^%N@=n~!-h;M=ZvDKo(~%wjmC|$&M+7b$uM(VO-3~JVz^cqLujf~ z53nRn+AF$)iXDg4Bg6wjDe;PWgx+^VVLsI;ZU%| zwpYM6ZYDPJ8fqcfO9=?tNTVnk9TXZJZvc=15egpMlXq4M0w>Wgo1>l_CtpOa6@7B<07p==$ZiR4) z4_ZJwdM>4Ay&DUsxxfVRw52GRb7?K(K;$GZEh&zkF;dNK8g_b|>l>x2nhQ;|v`u6# zvTN;lB0oswOSKkYeX_A2tYstcs-8J(TwV{ZQOaH#B>x z11c!;Q6s|Qlq#10<>$0INiD@zw>&qx=roQhA0KxFUfo@Y#h;*X>YKlqyg}E>Ye@}HYraek zZ`@P=vNm5x7eKyg&aAlfmoQuWt8_K>XiOO?c~3V_@dm9s=>nBB<&D6v$*yY9QZTDR!z=`R(1)e6W=J!f=A zb6G}bur88QDyq_R5)kDYAg42T!Ke|mCJ&bJGh@z=mK;<=KChjSaft?^OEkw)SYpqa zEg|APekOenm;}i)376(vu-CkHLa!+OTW!P0^{A3%zFWE4oF5)aHjE0RbZ}B#mf%Z$ z;z!RVHcQ?!JJm74BuF;EBq-(wlc4yS@IW?)SoG6MXm>{ zAzK0_K{Y|PW~iss;b+p9VPPd#L@JUi+6JoCSGb#UKpR_jjf`0JNiYeL@eU}M?nm7_ zZ3&nJ*(-K9c`hVPx|aTG$^l{^dqq`1_6kgb*Ah3Q`;D~MgQ}?NVbIj`hh`b?mgyDI zo>9zz=z1;~1j!`ICAp&RE!kkv)8=&4y?rg7VPBg~bl*$4g2wA<9EDcVc;Tc`0u9-~ zvTJV!G}Wb`(SXbQRWnO8kOa*c4y>!&?`zlgGtC58SNg_Caxv5&zzHS z6g_g>GCD&vnK3^NTz2#5TI&p$gtR%+ZK=t>!X)T^Yyj5$s$rJTT}Ee!sMF@qtPrlg zb_U7jP}xlX!Dbw)()nC;CUq4|Le^iZt(NspXjJv{ek{58La#{ApeSP+ot(TMTe*Q&3{fZ=$*s<50MK4}v7knSevGL9fWy3R~77 zlCo=4&{#Z|l5fUqT&ubiL7n}=6l9b4E5M3qFj_RcchW!`B%f)y%12RN%lruKAWF*n z!RcIc8lAG8RFI|nM0L`Aw&!Jkiz zX`FkIWO?ez6)p9RXk7bEtACk1=)uvfZ`A!VAA}|>6CE@H_v97Dmh>}}O64o6O{Z%~ zs3m_Sr{aAjdimT+nvzDFNjxQ(*ZR@}@;KEk8iKBjsiCQM2u-y^XxiUXJ8$HZG5FFq zjIn$&>Y+KK{F$o(Q(!j17c8CZIEyLU$)shkxS@1`8D|~1Vf(6dfuj=?hc&=>QeHp- zc+SRg1Gi95sitE7(Kk(fqr0Cq6s|=Ol2?dE$|=V^B<6&IFI8Gy+hwf)w%|c>9NRE- zEq&9BrJAvQH>XD^_NL33^)-rN!pznY6^g?!omn3OuA*~OpvmSy6K1xSTzsagB6|SV zKzRE>HGvW(=Ciz#}|3 zp)HOfpFv?N`GRRut}*6_o_r5ZQjr|6(@i{J*(3+x3RI^aQz1&(rRpsi2ZI|YLAJ5Tfva#R_WMc~^o$^ApQ!x@S8QD(KbMp$J)Ov8h zg`NSk#M3cWHsxTf{cl9Oj&q2B70YvJ;#IiTv3!*jG#6^ul1XaUsry7ZT3>Jl@{QmM zC zxe$kGE|gW$Mij0f`3$Z=G7h3cwHuID^81)h#XjH)mYp(k+SE4Tg~+aSKzr2 zho!D^)`4P7Dy6cMKsBUCguC*Wu-Jv3}+OV1t+og$Kn=(6l!Ln(8IcgioxJTjH~!>D(=7sxd-C^7oY&Do~1> zpeb&GCQL6h8p?UUDrk$QvnQry!jN5PvN>ps_H#nhc`eY?H-M(SZUADGn?e(=1)9!l zfu_Del{T|44o$fvH0^$yRvj-d83IF)^+d)A?+8scg0iyC+JGh-0Zldnn(9Lo z;Ms1T?@`S~(bV(jS_0G@M{_6FvdZ4#6`7vL>AZE04Az-R&?u7Sb4dqt-Dnf1T|)z{ z(_jd^9&BgQoC)t!u0UgE?u@}93CsDBs)<+NZZr;4tn~nQqq%@p(;BglK=&hE5MO|x z>00&?=((h7y4HJdKzZmKv_gJl`~rIAoGesTIC)IXgVih+7d}Q07H;@1*m$} zkn1?k`(9gp>|7(VGVO7y-7}d_L&F&Fdp~H3 zMGFy~wP9$KJ@VSxEG?Sq{A5BTggK6Ldc^~pEFNdS02DTB0JN>>;^s3bN~hct?n>^- z{QPc|V>LfY$l?L%jGn>vinRAYsASjbcqBc8Es9#BI(RVOtq$=MO*KJUcRFi>D4WPB zuO%GPGdhB5jZ+_j`{_(4#FSN!YYT#r?~6EV-2mNb-3qLhHV3WLx>5SdIv3Sl?V+Sq zsk8Z@ITkHIh~{h@55+nH%6wlsqRCSl=-G?mT27bCYw=sj`#L88*3QC0K9Z)wfzVAFiYGJcEFzv2>3 z5-_jWV4-K=x5NYXzlm2q!-{B}ld*Gt?0?H^E6K?4MYPu7kZysOIlA_OM0IgNAEM`iby%@8|>I~YztmMxdzrxcCyYU zNE=bdsiw|=7sz@9oFWm(_a)@g{lE)|FZ4e3zL+6h3tk}gt?&ZA*7;?fO_1+_hmudG zYO8gtkRkO0B#G9oQsSHorc-u}oeGMjYU@|>1(=rn7G7F@t4dYL7pmiuwF+}m*1!uW z*C35o{70JNITImh&L#LE2RtErMSMEMaRlWx`59WD@}Y!gSs&+q`W^vYSoH#-X@4Fx zoec@iIt{ePvcZvi;yq`-i!Wp0cLDndzY9(K^Ps7B2hBAo@B&$f!{E3EMFqv{L7h7F zmolc_jqfGs-KZ)n4+SqEodz!;ohA#&9zB3)?dyQ1eI4}$D8-^Ix$;IzuZpw53&>B` z_Y!j5zzg`@070f6ffrEBTE}u~Tt$1KA8=1~A5NT-9&{jDKyT(*Q!JW&$ffrDW z#8c(Fv3~O1-~|+KfESS6r2{EFhf}II(E?Yk1-w9FFbF|x!@vtDpRP}Cq->B~ng{IL zNx3I=&OR6@P=q|!4I`Vn1zte%f_Ia@By3VVNj9B5Tm-{)bkKjY8>okJUCtrMemBld zy+CL%Ki|#g-7rpjE(#i|*qh_!6*qCpr|b}T0mV(IwB(Nxg=!vEZqIdNOOoUiyny7C zZRO%UcmeeTaKFl}zzZn1qBWM-0IaimmC%Io0z9cRr=U5vA|g~A##VU6jzDl#$E3Bb z+Q@`*MCu240mW;WPSxwc3n-4@+X&J@R#$e2YNBE!@B&_6e}jigB;d=CQ@$W0yTRw9 zJZD-MX`lH*sppKKW*#tM(KELKFQB{;^-$hOl;b(mRM-75ooP$J3s}}Vor%TgUbXO)HN3RGmCOb}646`_T$K1sQ9*nmO7gg6)*Qd3 zariAgw>|)ubHQ(E9DYkOgx?acIEX6ssN-CN^o?}$WQPcmB=@u`6+5ys(lUhK(ws4X z8HaJ~pyHwW1dn(?uSoZ!43auXB}o2*lW{Z7sU0CH8_?JhwlD9YwJG<|v=6PeFQiX~ zCM+>Doh<@QIK28QROU0#Nn zzv;sU4*3~qsvl66(tZeN`er9IvZ0(aDy_L-hV&kt_7IV5sU87Mu{@w~VPc@sLXWwi z3YoJrPJWA$n`(^EbS618)fl0v9s&4Dd+?zN`wLAqMrewaYr9a^BcQ1s0ZlkOXxhgI z(pmM0`Xp=GacCI)-Kquhy5GGggLX zou-v9dxeF~J`Pq__f$N6i^h?=c${*Vw(tU} zA8d$~e$-ZOJ%ilE*Xn#*v`sZPXgc2(n)X3LW8+xL5PNh|#@UJNxd6rXwK$8sA4fW+ zjMq05QulH3nPc;5a-F?XyP2|{15Fq&X!_P5G&VoP{OH(Zy$)qVLo|-AoM`%f4=V|j zEXM&I)U^Z`nhSV=0mYbw7tninuQd0w-$y;N z-$P;rP&)a-9v$(8JvwQ3t8J?F@LuVn$i`NaFJ-M7W||AoZavp$f3h>kej2<$*4yjE z5c%oC3+R1mCu=SgWYZoHvMP2Yd&^pGm1$(Js`Rh5Bz%#qffo?3{B6Z)dph5vlHH7@ zNC>qam4xLpNNA+fl^o=p3AD4OUzOLy_RuQVIX2Mna3O!yQqMKV@hc|8VM?cQNYYiR z%JRQxxoj*3Ms^5gl^sH$6~mQa={p+Zb-qCAWt}ge_x1Nb3T`1A zM(JOEwaynvJtzB>oPrmSoYt0*lmqYriW50sKyf1GPj;7Wi=H#RowU2OE2QVN#T9eW zk<(go!oFfIs_2TjKsL&TQMHf_<68!@Vc-R1PZ3kuQ(7#Fv#Ay=@;mp%bwE8l5OIM1;sR!K@tyw*H`ZZntCUI!?fq3cJ<09@MNB>a4UEvgcLDwMbWM>u}okf@!4>z{|)dQ}vPE zrQDRgMT-0+IpJ$bvCZfDz)Oz9z-TUjdo6p1hUT{w`3E+5GcQ_Qe>_h7y`*Do=mwF7y|heFa)aKqk*z7WSpue z7VJ_8>q5z>)WJY(~dWbw_B@8EAO^ydQoh>>7s%t40A$bwX&W6IL3U zH413LBtQcqk#iw73z=k`Fwp>+w6CZzMTs>alGmP5?zhYu)d$RSF7+`m(Uw^wQqIs1 z(r(p;p)IpUw6e4wb@)y`mvasD3=DMGFp>}LH-$!k8uzPn4f5UUT!YYGN(Ek{I@ciQ zLP-`pKw~%OOp{diBiu+{fq2UNa#ZIU=zi5y%4g7CNI4+Q zOaDu`MBg8z0NT%draCNX+o8!$Leuv@D8DEkho&>hp$V5!rJIadYx_sW4A6uHgT{1{ zFSrWv%HPqhugO_9_%?#%5Ad|svVh3>Tmo`k3qV$LsX#HWCFqiz76?+~)N=)xVmryX z28l_a^aYSH?gwN{b0+gktRzVf4c5FCP)ynbIvDZ+XhO!5g{sOJ4w}wahDJj^pG$K) z`3J5b{XWGKoj(Lk-&%mC?-@cp`5bIt3+xhX?< zsLXrFj`ZCsXv&G9X^(bgR+4*K(HRqRk|==mTo3Q5t`kQyCZv1{?w~>d;qRM zac3R0ooiXR0zCs9DH1{{5t zIT*9k_XnZz!Z|-$q6@JtK#8V8HYkNlPTBA**#K9d{1OkEJsTW~_KrZ)*&T(PHlKk!>3#7t zDU;v|%;!4vHs1}g*ZuG_;&T-lb*<`1B=)MkLR0M(nqxh7uIt^X@+)7U6eJ%2NIfyQ zWTDn+a0TK4RZ-dJ0`(?OF{iT6;0m%QOv{i%J%d+dZ?ey{NwjWMe6?=i3Zx%&NTna( z3KXLkz$s%8!lvx+*XP+~JHZv0&m16;@sKg;`XA7Q(}zYgFnL8YTKucVuEx>* z6iweK#kpy35Hzf3UP~CExnRaM7mkn6Tzqy1AbnwGp$Rj~LqXQ(T#Uu3cCz`*p3$5i z#k8!2Pmj9KccaK7-%T+?xeLVt$tj7lUl}jxG30t|i7!xeG=jdy8z)#xsN*l<=mUA_%f! zgj}*=g)5M303ygZtZ)Sy=kMdtY}7gVg@#kkUbq6i2e<;ghj9fYZ`MK5Ez1zw5!07( zY>#SH;0hE!7p@@JsBi^&zrq!ytt(uCuT_5r!z64EG~wZ(Q3{SVhxK|lpi#;>X=Cf% zh{9w$sh>)xsmvwLgRTaJvz!aKg0yvoD@b`MT!DC1r`6^C2&VMD#ud<&)ZUgl217my z?NuGFa0Pk>Mlt1{R9yPO4(;r*l!&W#$nF`{JE@q#xz zQ?_#%Tmed{eb%U@&d-5H^qp(u{c4O;kF<^((Yt{wNPHOMv|kOH{VF97*;pzMs%cS` zSMFCw110~!703?NvE6Bhz!fNu0#~3~OW_JqhR8x?I}2B!xsZsa-6&i^^1g5dX@`K4 zC?_dgLFy$)d)W=51KAC>qNwLkrC0HVkYDqo`lWXRS0KKiHE9nDSD<&pV5Ke;u0Z!g zk$gX$VMWFSKr!T*a+vHmVVG(+;0i1oG}`rCn%3e0m+C!$!6#3_6)1-wYg7zOiCaF3 z)Jk?7T!HcveO@@#gBMP;HVOr){Mej*b1heFs5M6aWa0RL{l0``eF}w0_h?V>s zyCLM?h%n{fXb*ZW7;^Cn_(tjsTly786ofhTcbRV)@L4F=c&IEXhvi!a%3noO$0 zLDM-@&?xidyWxJrh7q;{9LaH1f^{v4l&@8MU!N~azW`0~Jv2ZQ`CNiH$w37~Iga4M z-rtj*4hHmkBVE&Q>Q0r6?WO(&kKC!Z^0*X`Hb8a1T&Ij-%g`yobvf z4{@#Hd#a-P9z8VSxu9uZDl|@3%eioDe8g7-?fMQhG`{kh*EX_~j8hGOW9ftsgQgk) zG}Qp0Y47?NR;8bYMuj}*2UJ||OC2s{2o1~_gmhKsGC|Ycb!fJ`bzHcfL24y=A#{|i zkx>>~!ckOp!|O&JE^P!|?2HNPm_X@1oKGx!?PijV#p=plfh)+m1=s34 zirQKr-N*H09LqSJV+~Db;1oJBdqvQ6c0M%qx1i}8ErqI=41p`i`VixEo+UJ$oe$!e zqD;sxr$>0s?C(k2Ibcs*>Lse5^*XW->ol(`zd!|E_6l4<)=-GdRM&&1GjO0`#`4{W zC^cuKE#)37sThe)x%>y)7QG%M%6cxxH>EAEutYos_MrLUx>J8S(Io2@9EE8=jfu@# z3uP&tbq`H@*P&724gLYgRQy+=RPv9MByr{xZK-_c{RnB2FN95658Q%et+r3=xnM|j zKk9HA2e827gioYgr@iY$f5In16Fw1|`diS1yRL4$;wIELa|6byzXeSg()zMz)(W6$ zpDQ#vdbu7|6%ATWsoA%g2QymT( zu0P++-$!I7s>4B39j+>e@;z+p)w|I>mu%Fwtc>|NuRyZFZerPIjy=+P)PZE_Q%GyG zE=rMxDqiq__Mqgxj_XTaVRij()Zx%|`iG`rL;aIdb zV&wfmE@&<3x@avyEom(Qu4^r+7AxjQautUGP8MGXH^i4Z#y@!vC{?i|9#4LsuLWl9 zl>&vnO-^f3=MzFBI*YZWZSOf#yUukZM9MlknX_uw(1dq{M!_kci_VA#=uFCK0TnbC zbSCAG3_^9$`o4jlOJi5^QccpF9}UN}&m(`|fJ73Mm##%MP0QXG4|UE?^)Gb>?bR9V zoY12h9t{UvQal$^qBxd9X38IxjD6zMrNs^NTjv&6tX(@Z`~3Oy=4RLq)6C4Cvaql; z1FL3EnVCIt`{soimS*OZ6|*NEKl75c&un_noXP}jAGIg+yJxbk_4ucc2hXTOHjeA9 m`{g5Ewqo776+4$UEH3R?oZGyjFdO~;iYGt$na@38#s3997!`g1 literal 1313294 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

=QHBub)uwEJ(9Pu;U5qgW*n|-9;k=-+M3D#> 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

-~vj%@{eKs)ug>1)*|zezG1zkSmf%J;V-L zhdBxsL$)Aj&`fifV+n<;q)ya>?=#jPGX4-5iS$fO8~jXbGU#~$jydQ%Xzr6_c`T~h zeY&jWvfjZmk4U_~u!QV?>iUu0D}|Pb*{E0Af3x0% zRH9;-aW0Zoh(l|ma19@81y{!Z9XSSyrUspSOGU00_W=3?qENh?)GGK!z%LWp#fnD^ zhy9e6LUO_6moPaie!B)t6q+R#!Gg+@1zugi*;&Z2=BW|VF3#~WZRLCSJQJOsPkF$miIsYU_{*B$z`G zvBvU5ccC@s>A{6^5vsz848L^HR*tN&wQh|0iK}u9t0KYIBy4Uu^{KN}HkQ z{T2=Mj{j&2+13@%i!k#a=#$Yoi-9*5Y$q1>J+7PDz%d9~l4>UG!G}41_FPi>*`b6Q zA>&%SJ*5B>?BPE<)P`9rBMbXnPs^(c;JV;L@NM#y-P7vi>^SFKG*qzTJgU)R%nHAJ zy*2@{km=6Li6j6Rm%vHZBJ5hNp?J5bPQ60+!c^62z53s96LJsMt+ceBSA^MT{>wQ_ zgOT=6{T$DyQUoLeEp_v|`G{4Bj^x7Amp8^z$zl1)eTpG}l7A*Ug4)@oMB9a3kvqF9 zO$94fv$;M{&WfNS2Hs!=Ub1(5A-wHZESu$vK0_UE(8CIDo4&rc`KxPHEoM#w~H zR6XEVaS`zf5oDo|td@Bl@ADdO@<1-^2M2Rqy$C8{mO+-#;T?jDKXG1PFW!$&q9!r3 z75jzZ=Cbv22|ExPOSZE6gU?S({E%R_`ZKkmWVvq#Iv^-(M8t%ko*}ngxmI|4aFJK( zIm6xXN5b@Y61jssi*MS#st^cx*^l?wwSdm=xbjp}O1@hxiG7=4s@l$d$h?*^M%uU$ z3ln@tW6L3>gQjy-0FIb-hTJM7)$QdWv*&nDn4^%x4w+!#!k)W3DFAS?LWFazj#f5j zsd2Zd6*pvLZADho{$>Z)c69KpZZNVkdx3JJO|wbM%+XWk#?u#{yx=)q3)+}weO)Qo zvfPw-C-3kqGV)c&a(ApJqT!ga$gv3G9~XaIG0iHx%MtS~fA5Yg9@n>ASOGlL7cuz_b0wwv_Y8nEGqptky%G*@^3lz4Y_H}5FeKc$;7{wr)>m?HHOEG z|2Pvxa{mX)3B+E=8mG(1-s<9E20c1I9mrGnI}f|7l>qFkVk3z*=+TMUH}}5U@bERp z7xt)R^H=U%9jUDnCq?(skIvcA%<<7+A5v)Su0nU<4uGviC#|H)uWY9*c7g9h{{CEd z-RlGD(m(npDPw&Wfkpii&0UtzOk^XdmOShTBOYeFJ*PAD)1Bj=GSdtwT>=_rOc)gP z=kKmQxsm;~72+ot>lp%)X^7rc#WDQFTR6SD3kn>P+MsbUX3d+ zD%FgjEYTblE^lYaF#t0oFYkUuCs>1(%ow5NX+PDY)S@tLUo=r`Q9wgj#}BHebPA}4 zzPAGYU`(Kp%OH=Q;Dvfbk`06ZRk&kSlbnWnf*uIQ90#vdOjEo?l#cvFj`tkPk2cU?FP5AC{Uob0-9&QA^&u#5Ww>92K=W*3Moq%+k;M8 z@T8LEa3SCh{L)9}%=R4J+U$m9on;oL5#D41gAbhq&4_QJN+rH&j58M_zk7&e1v?E% zfIYhes%Bkg*lX)cHP~w?Z{?4~e<1N|8EV64;lnsj-^jE%DX%tnwTt*YIVyVPbm3W| zr+ihs`CSBZuz2mFIP>+qdHwOt<8+6Vd02Ix01B(^<_%v^wh^29ckaRb7kxI;c{XSs zVH+~3VdQ*AJa?l89QoAkRZGryA?Pm;@r`JV<2rc2vsxIZL+t__nvG?{Go)R8kiq#$ zD*VnEz_B54Mry7c%6gO%?V*yDCp49AvMY&~@-8>AHssdeOMbN;%jzu)v%VuoLrR@d z=hUKAe8`mag91FoL_usRc!B?l6pABP5yHWZic`M)Z!6(GD<&e(d_p7zrL@)rDbSm! z3i2o89c0H(rtZK!OcAk0`feCMct15&Pa3^E77A+Wzk>_Dvn&Y$XFjuOaP4k^*Ck#A zmc=oIVq1UO%|bJ<9M<~1cUt(uDxDYbTcUb2ZbZ8n*rkgT%V~Vd#O>IsJuUUZP0^~! zpY0bAAv#XoADQJFwMS)Z+8~&Ll^IMle zl|4N}W9O(=3}{}nV7#Z@s6FOdBTwaztP{CGzxxYy4L`&p;&$Bz-=eVOo1g8KP?@3I+Ic-A*QNcW#Q_UgNQBm5|A zN+imW9=bapK|A4=K^zw{BvXjHtQD#(LacqJ+S zOYexO+?pHNYbYxrx~=Cs;~mrq57_oIV}c27q3^9I{Ssxh2Z}5Z)ya&S@1;!>lB7U% zM84@-4-OhKB=Y}r4Um{&_{xE@~$w(;rUT;K+AKdMwb6 zZ{cM^{ir4LH(N)tvsR~k^SCj1JgR=&xi(1u;Pz$#MCV~qMt9;G46Pbq7+xFvd4*NL z_&G~a0cjZPd_SB>o}Xrm8m3`trfvlUsrPI>F+wLM{G&SVlb4rEjp(+6XM{{Hwg}p| zGKW&>@r>1SSFP;h;a%`Q%(4fte;jI&Neq>#xFjavfbVJ8gY<@$0n(9pielg*)KxRd zXH7EH0IhmbKH+_~Q3wm?sb}PXx74=6_KRAil~3uNRUJe7@P4E8=XdTZia?m>uRsqU zUx=Z~_D*AJyfmox_rN-a{De{^x!XukIG~Z+URikg$!(Pm0=hG4Fi5m-t^IKnpI%j} zSG5pm||+LfwuxLy9D9pl&4Qe} z=<1U>O!|;4h?h{;V>_9lJ7q&?>5srAm&PW>h_`}9zI<4lNLi1PnBg?|y^~imoXTG1 zTUGjz;l&ikTW;M|W{vaHF?@%7GK1quVMoaAx&+A|py;RGTAF~C*~htpyzgh_(g#EV zGm;%Y9jv?OHztfbLfqg~mpliGO|iyn*z&GcfELyqe6lKjQZUjxs|gc3`)7 z`qPIQ-eKp~<>2$xS+S5AMTUDWN-U$%1l)-tP0H3+BzPsJY1im3+5TedN#e$>#Q+9OtjCcXc3UH`O!d+TL@1^|=h-pk zrTxP?#gIvMl>>T@CEUN0jamyhoI)$3P(<3~DX7807w}bL{Uu8*L$w8$A}iMva%Udz z&}ANL#r;G@SIdwSv%w6RhTwmT6SYETg|FHs`^o(!5?oq_3QkyfyFlhL4bsq+LyEpW zz(arJ8>~y+Ta_#=QD1_=&>&1q(m0VWTAmG6*Exwmpe>U~ z_m>(5%Pt2%y;#eKW%r%f1tzcpy^gJF!eU#Gm`ul}c19)SwCN9>Bf7MAL=(p0C*E-1 zm9XV+c_mueC=_Y6u(ELhz5h`sy#i0Rv_tuQ%&w%ieml0b^qlc81gZKop4MRI1n>Og zpT~CDjCn@TNW_FGL-v--H7b`q?U2)T#EpYZCPYqaTDi0lfWl_nbM*xe%#tw~D7~Z7 z%(#a`|MJQV{RF-=6#rf}?J;O4uUnLsk=*3b=S=yr%(6E4C*V7|Uk~ww-8j>*2>$FV z^`Pf~gsDvGWRhA;;Nr5OJ*{%{ayu{6(D0fw!j8No0#YDV&6b6t{L=umRSrVyB>9ZP zUy5+|Dzf6}&kt3$o|rJQU-57TJ+U5B-1d@9KJ6n{r+sZNF~92N557asrDQ~QW!D6* zrz7B4VB(E|Zg`$G7h7jwSG)GmgsVLEGrKey?DvuDOoDnU(lpX1=Wk-q2MZlR>x2pM zr4XPb^>MS2IYOrv2gg$QM4_{@cut?zdEHQhUTPTu%E9qw8>k1b%wMr^%Hy=#@nU-h zTVR7~fLa(m1T|GC%FHY{|EUfxZ|CXzxH!POQdQ>5IOi5b2t1&y9{>83Us!(D(RIB~#`N1Q%LLW8mjC`7}k$iFezwt%|Lgh`6Ed(+dG3F}ki{2XjnMypaC;o_QgZ z#}vS*T1kS3jAA`r8Doc&Ts33jekp1%>P+9$a2fn1_dLcy83 zFg-j7qhK>{2vs!OidxqLjh?w?Eu?pp(*!c$E>DsCMO7BzBXLwQKt+Xii%{CJE09}& zXC1hs-Z|&SrBT-!cSO?AR7>cDiluie3SlK@iD7OEbCh-cftQ{xxvYK}F^f+gn&lv% zWv|OEPYche>kq-$HwfCEx5zClYpXUKI!AZGH448Fl{N!w^T+fasjB(^e4~A3NlcBy zGZqX}U0i`%LwPWT1?j@bTz1&}I>8sIL=NyJma#e>@LO@gQPt>5v&W&oy>ET8sstXE z|MMIeMpm2fkkoIQM@zjsb&zRqf;m-~$d4D*=?uDk2_SUTTAegwMGH zlGS@uqXS`j$g7Z|$yPNx(d+?^|G7RxE)zt@_@eaXYgBL{%W+0dxhAQXl*Fi@i2Icu zjqUpTd;zX-E<7202q1vj+?dykMhl{11k9xi9;d*MU9f3R6`WTS|Dga=uetj>F>M5% z{0)|r#ZZNgXSX9P{p4}OK%rz@t6a2w5Yn4BlCL^)v}-~om%oY*gCrP}0OQfdi-jwg zOJU1g$lx)$iD_0wa)wUfhJF4LG3fd4&(=aK)UdV|KAazNP;tTH$HRhOyj%&&rqYFa~2x*@HTq+sbfh>+~>OvI$9x_h3tPIn2~ zGtJ`&{iYaBQ>LiN=9G*&v`%X2QWmoc z77L6~fht2h=@Ic=69!6NYW+d3! z+9uyVpEeQk^+gSH{xX$!vF6Gia!6hxA%k^*{Vg(WV+M8C#=RKrnlM52^yIdhH4Yu2 z{hb{OC;k`FgHo<_s5k21i}O*GCQfBbe@C~U;p*dZVT3C6-j^5(I=J!@CgwIP;5T$o z6%DH1?RBq4tD`|QQ)*3qd=Wtwu2M@zcdDlNRsmiFQ{V=6>yW5UC(7AQYu^f0?fH&5 z5Sex(>#omdIR?@B6Q!5WC?B0_e4Q+o-f)?_Ke_6SeNjh6MsEvRs<*T&Ur7e?{UKh`|rM`jEa7TWGi_Vo3 zsj;*l1d>pWel}hs{Ivp{uhfZmGAqa8|ao5bkJ9g*^KAI(DhIb_N8XM-Jw4%Dw+`L%H;y&93J;~QcElH7Y9Dv{20=3yu*1vH`Not`ghX73 zHG)!UsC)*pMGf^pVL#xmbwv&JFY^2%`W4`-&+hX2kq%96ZDKbN)+832P}f!7N+p^H zPGoJDIY$AS(uR%|SHSU&h1US19aTeySTf(aPNHvdzo*Fmv550}n3q5y;;EcyW5oU( zdIK2C9oTe%PQ)j+s#M$AFyld8azNko$uuK)RcJ^~GdRF_veU^WHzeSiH|vuQ0)7Af z8Ob5-{hMD?;qqQ|U9xSNe+6ss1z{*VMlxT2@EH40)4y3&W&MdH6z6gKck@Mzvte2+ zbfz15x`4C?73X9cFv!4eZ5}Zgh`5yuU5qHk05U0U(i)Tf;VuU@hx}9K0toQVgN*n; zI#jDa6W{9(RGS%FWTS%zPFWwb=7@o_GO>bonx5Llc#g<{$)rllqqAbmBFK|7K5^yR zffwU*y^kyM$etw*Vqy~D;hNZ*=fw<()qde%1atTW28a+8SlMWcz{3ID`jDf<}WgZkrVt*>SA2p9j}{@*et22 zG4(yMr(5rL8r4%T%nCy5Wh`wD`{)UbZv@TeaWnm(UYS4MIv|R&UtVtD5Odk%-I?Sd z6$R2fi^hbka8g+M(3Vh{&3U;HgnI3$U|Dce#CQ~nzM;POv9))_I$oS`pjADM@6CRcR4Z(AJ%Hgy%n*zod_pMIXdWg z`?1vA)U(8D|Ait|3WC>XRfs(cB-Gm+0ibVgj{|j>nS=N(xxGGm2ixv@w;Bzzsm*-| zISlS&X6vAdn0vt43!ehaLmG(>Jt*r;iRGwy5@b;^MB?s z&Hfs}GS!MtrE(L{m(SMdzc7b^Of`}ZJ4-l=v6-9n1HwHUCt33Nj}&4Cp4fJNao4U9 zzS>^Mt!7+>@Uu;mFEkkz78g!o8qO0xSdF)V&B~wfe`#g^xBT@#t?XA`&i|v8<@$d_ zjA!Ti|BM)~ubW`jW>4aq)1=P|`4Q@KBNxm!^L9-@246oMvm`4?Br0A(aF|JLg0JRSxHC(Y2eF9*~GV- zE5$R?!1r6>AHVotPY(KOqRN_wHr|eaT*aNTL%-0WK0KUc_9h-pl?EM`NPnS5HajDo zKYBe*^k_eOQl%&O#Ncb8U?y((RcrA%=qPGoQ`9z2INvDh64gY7bDiCMW@xsRHLy$CGq>OR zE0SL70;U>CVdgu`b!CgeK`ufNVb!vTQ6Q*+c$SRoplgk2lm;RH>;x&H- zmxr1k_G|tpm$es1se}Y+ag=+=Y8!D5hhitMkB5RCTB*`+t>s!UY^L6978c+))MeV> zYr#M=S~vSG;4W(o5>w#5tj}gC6tKyK$&J%Ga-UHDt7Szxh+iQ>%~%DX{7>J@$s|~d zxI23A@U}$m`_f?h6BQ%#I1%9|lqi5vn;boH}|6d2*nd70Nw_LpV zY9cmj6R9~91q7QF(D&`DgkO78evk)`PCHVtnMAgw39qa-ghrOQ0fBO_DuGNBZpU(~ z6{nX`8-W7;e4s`4qp9J{$c#^8XuTY8i}1d0-^50#jB#zcXeWCO{%V3N zw=86d7G!0J(s=^DK37>W=R@n1tZ3ych6N2{S~r`1@JoIG5uUYFGJ< z*cHAH^&JMyX^_GV%o>DOW^JNAdM)C5D}9U5!)_B<#_(?W?qX5#V_a|<6MsG$u_DC{ zL25RiJsy>)epj_b*nrFp!&)C2_GQW9c-9Yb=LouFtB}IPEHe|33R$(fu7HRme{r=} zD@KA=fY!Z7Yc+o_V51HH&^rdDJaei9uYehkkzfGZHLSPbgeAGsu}#iJ&#-$%_I!Cn z{a@Xb(VR=ja=c*jX%S4-c;5Uuo?i1^p0E*Wk>tWN9;a!(>03th%-orw-yir?wyq=% zBfBI#K@yC4qw0cE23(KhFCEXbGqr>H&doyxn2adWsq}lHz|If@BJNS<~V7} z6}1XQ6GOn6Q8YDkyg6DxVNbYS*_a!2O9njjG7ZaZ5wSo4n#3~{#{evkr)He|?CvM_ z^n$%83SbD1MVAVMbh1)|8dUZe(RHK?6X3*yE$pWX=jl4J3`kHXGdeLqpHRv%BHe<< zNka|020UEg84?Q@RxomWBUr$M)C*k@977CdU^zFO5wi_W2tZ)QzoY0DPn-R-AGS9M zJ}Su~5|sVBaui0<-nO7;!K%0~tq;*#AX2Eval#o`IpIA{pU|->0gz}xFBBgax@lL1 zU-d~j{Ow#Q&n)O&(FMoOCi)w2frT!F&0*_H;tm?&zY$Xz*&T-7T5BizteX>5new|K zDWAr-V{+h9P7{7g;S39qF@t!)q3cV~IJ(Qsqm2rMGDWpTKUj__OM+hET%Wk6U;)>x z&k22X|r?|7{nsYKj*+twYFc5=_uBccJO5JVuKW zZJZ3IX6YlZKj3!6MY_iaph1?~Ew3s4W(Q8H8P0cNg>Ke|vBoDfwzjfAE@DQO+w)Py zx{+(ZSCz~}D2)1j{`dbjDQxFliseQ`w^Aj=V>3%Go{_I4>X|JwqvC!k)OU;BGz&6H zxD(=-krGYJ8cY8x+=`^2fjiYd96sLzmx{!fzYLg@4>+p2iuHtmZ1| z`N_DR5~9mO$39D7sXi7pYhd$2g^)cg7!&P!PSw z9=RY}^MN>lKs%E+>}mp$`rV;%D{zy5q@ zi zK2dzk8b8_5d9ya-TcQW4k?}sy$Z<7#lA3e&Pxo-(#;CWKH{07v0S$ zAF~eXy?JS=ezUnJm>t_soLu}IlELwpC5D9B5?`C=y7lQP^Y!)yBt~;4>=+V4Jc2Ll zmhS%r(0d-tDPkKMi8)sI?LbBUQ|$YHt8yXSMhcP-coJxx=J;;z%tK-45;g*BeFB!z z)HLIN88*s^WFqQR2PKJEVe_G7g0Pspm++TU!e6~qID$ShItz=}G>L0w7U!UV+(*vn zqS(FUTI30ki9B;F{1S?XdZV{YMHT|~wDP4cwt>7nVvSB7^!+eA7A2~9XKb){hv>Gz z`W3oqYVphF1mzVD#kiR8nKs7H^A}}TyQ!5i!ebKp4c=_F#8I<1O{%Q9)Ko2Gg67nR zT|?@-gqsAZ<08=7kIvY17^mI2dfFaY%yZi6aUSY_zh(wE(Rx@bt{~i3hM4d#rO!fm zM>Ro_uI@Jc3Gvm==1|1HBB&2HN)o|-eIVuaWShl6qi>}B$UlPAv6!c#LZyTW!euXq zeT+8bry4XHo%rjBvnEP12OEZ~zF!et6Y+gT8-Sp|za)jxZ(?#1h3qq&qLbGO= z1v`uFSqe4ER}omyE-gQIa(DMLl;02pv@mgpA(Y})DV0if(BICRmOa7-MPK+2BMBiU z7V{=(D`s|VngK$K_CXJ9kMQKzIe@~Ym9xkS!Tx+r@{j~{vE@HTW{n5RmKmMgENDD; z?rr<0HCEZzt?x=-@+T=5^Q0OkBz_TVlW!Kfl?NS4)^es{WC>os`>py&ns_#T> z?Qi}-qYZN8FFMS`8x7d%)I%hd)1cjJLfC5bADl^Oy4?lo1FYn4uQp0fz1}hDd7|`^ z+p971G~8m=MD#o85n)%&(C>$GZrM*E7nh&HZw>%4_Mp6Txyep}FDAOVy+qpLvrtq@SJERmBFoG{Ud@Yslv0&rj2|5463V+MAr{TZFy`yEG%&cVQ;RG_|*I zVybs|GUK{Ssl&?Sm{VKj5*m<00$b19yerXZ+HI8*LQ(5CqVY2H+6!j5tw)Mmx}r1% ztx#97QX*uEuR_O7=sGc5+Wl@t|#|%NR7}os^NSJa&M6N zR?hjGGivfm{moaQ zvgP?x?XiI#JD^l1Ot?aY97X__#)_1Ix>!_#lyJCLL&ea27FmOL4gfGsENr7l8;J)i$Xl61n#I6RI9e{vjZJCB$&Lz z>Htx%99>irw^xr7@hcB~)#P2nXgikGmBWAxiLD--TqjB!<7VK~8H-uGp7^l?C%LuO zWboAu&jff*+XpVwn(1dr?{3W3<3w&8en&6VCz7?yVMvg+vP~9&VCUyek+8@J(8==JBxtJ^gjkQv zo%eZ;`ee6NT?rupe5jdKf(gr^0i>OsPVR*T5mNzw1Cj!$5Ae9RF=_~+*C%3J0w7H% z1_pjz5&U)$1pQ{CUpcm@O8t<1f0TPHda?z)zUd=;fPfLfBDq1lp5 z5T1%nwD<(K z8IHN~tE|HT_D%|`ZA<}~R$YIqytfua81acXikH(&x4t2W#JvdYpm(C-I@0vjm147FYWk7 zfC+M4_~fP@>i5Yn;~*=+uw+EUG$vwnH79$;wDs6OA&2Ek)5^2g_$&YtG*;srW9P)w zIt{U4nQwgTnl)6Mtp%J_Mmzr4=ryuEV>;8YrGW5Yb%bsLzSGk=OA-7J}9@@%X&5reK8;czhRr^$n(O zSx`R64852@l9M0G!0EMA#YadOkP;t1aU^NYEI?yDC0yFZ!aS{&oAieKo9^sQ3z8N-ppIPedwYCpgMgsGSGCDL zT?lAQ-IXMejr~QCiV8b4OQtR<)j;V^p7zRESrtj2KbX@4h0#GIMs0pLJgk*-IhvUp z>{c!%l~$Cj5*h1IXor^Yh>^EKv|nZr26y-jl7@U;S?7m%?z95xa$UUA&gz+%{p~3= zX|$Y8s?f)!FAANY$gNWE*^WDf*0XREWz-N=uyJD}tt>*8z43jOXa<@TB?&oShl+8Q zAdz;jreCI&(kB!eS`L&e)XqcG8tADb4JC{z&;aZp_jo1`s@77LL|x|+gAtc}=t z@UU76Ol};0*vit(VY9T-R_os(O~V7Kz*_H}G&N4rSMn@J@7Yw#)31jmjW+1j;@FHq z4<6b(K3$lq;KIjFEDw>VtP%51%o|9a4J4ljrlK3xF&&B3E%A_F`+D)*6J!#J2dApu|+MzKg3iMX+Ww%+fVC6V;JGUQB*V3+2hMO>;O_Pb?%Z7>^~?i%e>} zv;JSyy?flJRaN(2TB%bW9?!s|-X2TQ?|iTGb={A_4^8oiI4BATIKUl_zzi^hfuv@o zrj}wFW@!bYVS;BRGqp54k&&Sgnt9fg>;U2U@O!U)t?6vmVpS9L&kHh!9>W^Mp|KlrO`iY;q{K`}AUL11r2Y>PwN4@orU-N@cU-huF zpYfGH{iBzE@Z8hR`rL2-Gne~k^v}E7m7hQBsvrN-w+{LGqo4H1AA9hwmGdS zJHGP!f4krDU;ek7|L#41c+(T^ap(`ueecum^06Pk{)8|5*3EBui{_EX9rmH~ZoKMY zU-;C~SAY5wN4@J;_x$3kj`_oX|F`G7^y82E_QyYT@Ldl7)kp7l@B=^hrj0{CbXKRMZe>`zr4kL4>|O@Uk;I-qwe#sSKQ)WH@S8Fh)bVR{qUa-8~)+EgP#2NXWr$s z177&t>yAF-*}wAQt8aGjho1A213r579~^pG`OaG(clhn^^v&HzzTt^4zv;n8KH)D< zKjU+sec~a%`LuVo|Lee~J^L^I@SmUYp?8fphif1BsdKqG{K6$qc=_v}ee)akd);gP z=4F3%!PSd{KYRD9ue-_igO0x255B+Ukhi|WuMU^};C*P zc}KnV4-feH>wo!I{^O_K`LUay{1dl2@K!(d-qSWN_?i2C{gprbxqm$FPZnQ$4{eB_ z{O=$5*flS_$qAo1;oJK?;FCwaynE#Lzx?E9fA&3>eslX-V=(Yt-+kAukGuMN@BgcZ z{K^-<_b*?(+hGs8)AhH$%b7pC=WXA6+Ig@0)b9IUvHPNH&VS2AH+{~3-248&{n2+_ z_li$F{?7L}dH>J9;NCZR(Is#B>=|D@==V-~#?4-F|LUe|Ke+mWzxt*B^UhDc@`JyB zr}usLj5BuM{c(T(s#{&f>%eDj_o`jnzjEVuzw_Ofeg2ORyTfspZF$CySG@kUciVBn z*M9HU|M8O_IQ@)QTyWiyH-E+t|LLVKdEY(Q!}U{s|GDR4P~Z0lXT0^75Bbu+J@o9K z+xh9!Px;5YedH&@q@3v@6+{P?04VyTz=h+-?{H`-#h$k z&)c^1!Z$thg15=L>(}4*gO}`iz`-BA?KAg($rX#mM^0QEaKD%Ae85fK zcFg+^`NB1SUfpB=9S1(=-cNn(#dm&7_xAhT^qT(h=id8{hyT(Y{`FTc|LN!b>Hl!@?>zFe=N~KPyGH@KXK+Idrtq{bDw*TSMK%VhaLL*U;WyppTE=XFFow3r@Z*+OK$mh z_q(*+^6FzhaObx_@!fxU>kqy2xEFrnfIEETF>n4v@zirae!|!9{HiM+wEg=p`suen z@tE6u`Op9QYoF_W`tOc8|AfoS$KLPQ#pU-q<&yVo-1o>Eet5(SKC%A9_x;9Qo8P#m zc=?lF{K(fl@fpt=KJ$p*dHTj9_j}*bA9>qlr@Z;9t51B{!8d;Y(s%#UOW%1qmhO{R zT)g|fSAX-%8!tQItOvg6pch^L$KU&z`+xhmA54GtwDTVOgO`2hO_$v>*sUkM;Kp~K zbIw2AaMdZ>KKt@_*8g(E=~w;c{*Sx(`+xlPr(CxEkvIFxLk~RR?oU7c9>4QPPyFN! z=iRV=&SMT*`>hXMeaoX>{O;42KYaR+*LPgJ;GXZQF6bWcuIuk|w^PmnqIWF5^#2`j z`%}){@2wyH!d?4!KK_7v{{Bbqalw@reRRj}BcA`J%a=nF1gd&Pkd>&`vz#s^<{lP&+}8z;T~b>DgalYZr__uhQRqpo=4nGgKn+xGn4 zJHGSIuYcprtKRa1_dMvppE&zVe_Wnl9DSPyz3B9RIqHo&fA*RG`yrS8(b+d#vh|+t z{`gzZf9d6aaO3k&|LUFJ{K4xFs2_jP!58eh*MTp*{gZ~4u~o^Zp{FZlQs4?FbVZ-3<%UUJ2MTvA{6<@z0Of6wzSI_}8d zy3IlNe8Q0z-r>UMeCt~qw|Ldxm!GZgdg}uY{m#pef8KHD+y}w%BQE>P^M393j(XUy zeQMi3-}XMQ{poi-=9wQmXuq%h*gcOs=-Ur??`gmO(I5F ze8SH{uD9QA$G>0k_h0?q4S#U#mydef?>_3g`+wu!H@n?apYVZ0&UwNouHW^$ul}0@ z_uK!Ec3=7nH@@aG_uBvN&+RUL)9aq_+*96t()z8Bee{R_^l|t9>i&QC(|20@@IlpQ z4!dm2VV9hB%SY|^m8W#S`rwamd;iv_JgPhY#H(I%rw_dMsGt3lC%y8G|9Y2;uDSX# z_t}2Y19$z|h4=d8)>pski*Ns%=k0&~Z4Pn`{t$Kww!y!{i)r{DGi-MN2u$1kt#_}eeM?!ZsK?v)QZ=WpM1ySKgO#zS6s(f9x1 zanHE%O~3ZV_r366?(@W7yY{|s`|cr&<*_gLvEyF))#2}6|KPV>e9Dz?|LpmPX=K5*;dXYM-jh>bnRY(4xJ9=f*maT{mv z;g3J$>^+Abv1j|9jkT)t|K_2)?7#2q|9jh(gAUrV^^l!AckS7IOfilWO_(V9@n1vu zS81QJZA%$ShHWX!@pt2b<@n!~<9}DS<6JlXYh0xqSET#8NT%{gYX~tEWag}CVr5RUg##Nee zm1bO}8CPk>Rhn^?c3h<$S82yp+HsY3T%{dXX~$LCag}ylr5#rZ<=M7v84I#)>Bd$3 z$K^k#Zd|1sSLw!8x^b0mT%{ksr60egA6MzeZ|TQx>Bm+2ag}~tr5{)6$5n=Lm0?_E z7*`p_RfchuVO(VxR~g1thH;f)T%}l!%M{CTonkpIR4m7pisiUeu^iVbmg8c@a$K!g zj>{EGU$5}>3STd%bhd3NMqv*B_Vu)T3tzAB^|YM}U(Yvm;XAtUEnSrUeZHv+-_?b0 z>!S4aprjF^@SR=w)-HT+7rwa*-`$08@51+Y;Tyd09bWhrFMN*|zR3&U<%Mta!uNUM z8@=$IUieloe6JV2*$dz8g>U!5_j}Cw@SR`y z)-QbT7ryxm-~EMe|HAiw;RGn00fkeba1Insg2Gu)I1LKtLE%IwoC$?fp>Qq~PKLtS zP&ge5=R@IyD4Y?6Q=)KA6i$l5Sy4DG3g<=P#3-B@g;S$&ZWKHH|2 zAEomH+c2J+&X3ahQ93_L=SS)MD4idr^P_Zrl+KUR`B6GQO6N!E{3x9trSqe7ev~@! zrxSnVZ|VFfb?EEV*Rk*XD4idr^P_Zrl+KUR`B6GQO6N!E{3x9trSqe7ew5CS()m$3 zKT79E>HH|2AEoo7bbgf1kJ9;3IzLM1N9p`1ogbz1qjY|h&X3ahQ93_L=SS)MD4idr z^P_Zrl+KUR`B6GQO6N!E{3x9trSoH~;E4ftew5CS()m$3KT79E>HH|2AEoo7bbgf1 zkJ9;3IzLM1N9p`1ogbz1qjY|h&X3ahQ93_L=SS)MD4idr^P_Zrl+KUR`B6GQO6N!A z{HUBCmGh%=epJqn%K1?_KPu-(<@~6eAC>c?a(-0KkIMN`IX^1rN9FveoFA3*qjG*! z&X3CZQMvu7oFA3*qjG*!&JSd#f!~$$qmpMN*GRt6IUy@2WaWgc zoRF0hvT{OJPRPm$SvesqCuHS>telXw6S8(f)=tRU30XTKYbRvwgsh#AwG*;-Le@^m z+6h@ZA!{dO?S!nIkhK%Cc0$%p$l3{6J0WW)WbK5koshK?vUWn&PRQB`Svw(XCuHq} zteudx6S8)LRXZVTCuHq}teudx6SCH&MYk4RTXb)kE-s@3TkGngyNfO_y1h6dYbRvw zgsh#AwQscAw^;3jteudx6S8(f)=tRU30XTKYbRvwgsh#AwG*;-Le@^m+6h@ZA!{dO z?S!n|VAXE0YByN58?4$5R_z9>c7s*B!K$4fwezEPe$>v7+WApCKWgVk?fj^nAGPzN zc7D{(kJ|ZBJ3ngYNA3KmogcOHqjrAO&X30V(KtUE=SSoGXq+F7^P_QoG|rF4`O!E( z8s|sj{AipXjq{^%el*UH#`)1WKN{yp)aeg$;kH-1YI6oTaN8|ixoF9$z zqj7#T&X30V(KtUE=SSoGXq+F7^P_QoG|rF4`O!E(8s|sj{Ak=@HO`O5`O!E(8s|sj z{AipXjq{^%el*UH#`)1WKN{yp)aeg$;kH-1YI6oTaN8|ixoF9$zqj7#T z&X30V(KtUE=SSoGXq+F7^P_QoG|rF4`O!E(8s|sj{AipXjq{^%el*UH#`)1WKN{yp z)aeg$;kH+~ycq$n1#`)1WKN{yp-=b) zAFcDFb$+zYkJkCoIzL+HN9+7(ogc09qji3?&X3ml(K-=b)AFcDFb$+zYkJkCoIzL+HN9+7( zogc09qji3?&X3ml(K-=b)AFcDFb$+zYkJkCoIzL+HN9+7(ogc09qjP?A&X3Od(K$al=SS!K z=$s#&^P_Wqbk2{?`O!H)I=3I4+mFuq(K$al=SS!K=$s#&^P_Wqbk2{?`O!H)I_F2{ z{OFt?o%5q}ess={&iTmCu#2_?VY5(leBk|_D<5? zN!mL}d0Gm_xObBFPSV~<+B->mCu#2_?VY5(leBk|_P(WhCu#2_?VY5(leBk|_D<5? zN!mL}dnak{B;_${{7xrn?ehki!!TB*bKL+Q=;QSbzA7h`RZCeKC z$Kd=JoF9YpV{m>9&X2+QF*rX4=f~ju7@Qx2^J8#+49<_i`7t;@2It4%{1}`cgY#o> zehki!!TB*bKL+Q=;QSbzAA|E_aDEKVkHPsdI6nsG$Kd=JoF9YpV{m>9&JXh31Mdds z$Kd=JoF9YpV{m>9&X2+QF*rX4=LbEe#`T;ZgY#o>ehki!!TB*bKL+Q=;QSbzAA|E_ zaDEKVkHPsdI6nsG$Kd=JoF9YpV{m>9&X2+QF*rX4=f~ju7@Qx2^J8#+49<_i`7t;@ z2It4%{1}`cgY#o>ehki!!TG_fsGdcgA&lr@)H%Y4o<^M|jOcOHdBTXEN1Z9Wvub7M z3L{$CtqCJq+4;hVR(8fPqLrO9yxeMKXAL7-*?Gf=R(9qvqLrOHjA&(N4{yO*+4;kW zR(1w4qLrOPjA&(N5hGgJdBp3oR(2*aqLrOXjA&(N6C+yL`NW7;c1H1Dt(BcqjA&(N z6(d^NdBuoUc4jf6m7QC>fNN!E7b9BP`NfDYm7RNxXk}*~BU;({ z$B0&T1~Q_R#X-mu4y}T{u3z}d>?Do&%KV%(;wyuKMto)dKYT6$*W?>R7Osa)q=ACCnf>Kq@b826qANx5>ZSlib+N>=_n>4#iXQ|q!g2uViHqK zYKlotG3hBLLB*t~m?RaGreYFROsa}WRx#-+CSk>-teB(~leS_KS4`@PNnSDOD<*-( zq_CJI7L&$e5?M?ti%DiN=`1Fp#iX>Dq!yFbViH?SYKuv3G3hNP!NsJwm?RgI=3)|E zOsb1Xb}{KLCgH`TyqKgHllEc~Urg!?$uHoO9|96!Fyd5|1cMRZbrNAP;)j4_7>xKK zARz_>)yg^qq{U#$4*{t$81dspdJIM+Fw!9)Nd}D9$}+^I46$Sx{26_-3~?z#T*?qj zo53HJ7)pj%@(d0Yp&>&og$7glW*K6MG?)?^GQ`qpa5(v4E2Rb_LPLgFVhyH*vkbBH z8cgY%Wr!u&;MmhQ%MeSo!IZvPhFHQ4ru5A+#L{kXeCnHJh$Y`(N;u09OTodEeu6T@ z5^-=0>nA8fEFA|^LPLgFQVynsh77UP983uf8Da@KIO&Ck46!sFObHDcV#zv~5*jkZ zQg#q+5*jkZ5_d2qG-QaS?_f%3$Pi27LA=V*kjR4(|7OWN81a=Q^kBr%kko?_|7M9j zFko6)9F*RJDaTog4@MjfX+9Y7ZQ^Hx!uoNLo2@N^J5``dU z6wY#nr3+z7Xvi6sG=wRkA!k_X5T=BNoM8z>F!+GP5C>~L737v%MMBrf+MVNmK~HRgemO}*+J<-n9?`P4oVurl=g<~pwuDw+WHB~ z4oV=xlzxJ;gVKmFrJtZUD47U8yU-8^r4(UGXo!Oni!dcL#6jsr@co5`I4H>oQ$j-= zlxl=2p&<@RIKq^pA?XOW3jbz_M;P&yB_CnL(U5?I5&vdMNEp$|;-FL{+*cfD=|~uH zG^8Y9#J^cu5=I9?~fwF%0)>?IE24(!($%G;|6`62p|x&?z8Q4EJ=Q zp;JJ@7^Z}VP626Sm=YQ~1tgDQ%F&QOh6sRvvm`Q%_{tK=Fyd%PCc}t-vxG8?Xl0!O z(#jB9aGa%W+`SEah#=@AvED=NHxQVR@NyX;S5vyW}O1k&M>8wbqYv6 zL(oHL=oFBGhADlsP63H%m=YQ~1*D@Pc%pCCDIh5gQ^HxNfYdZh2@P3d32K-U&hmz( zsUbciG~^9SR>PFgkT)!44O2ox-mt_q#Ce2WPYZlu!PFeNl}BbD@q(3a59 zja2Fzri6xWq!QpTB{Xy+l?I0pnWG^U4kP}}(%~@TD@%#Ph@&Ab4kP}}QsWSF)5^M$ zN|3{p<19%IBaVhdIgI!>OP0fk<1Aqg(L6^((i}##vTmeO=P;#j){RsG9j3IhZlu!a z5GNEGx{*qz!<4>RH&Q8em=YSgkxHyXyiwmQZ&-RAri8O@q>}6~B{bv>OSMDXQaH;S zcHzzzri6y<;HcLjiBCarexa4cK}lx_glc7RQ0f_`w6ZuT0S!~aSsavxhAH7J4oXHt ztW`LRgHqBkC7i`UiD{S;&f=i-G{kI$vp6V84O7Bd9F(etDWM?_N?1eeS7?ZX($+8~ zG{iy4YnT!m;-C~ZOgS3T*br6rZZj=pT3H;F_J%2avp6XE4O3cK9FziwK)TQn2PMK` zO5ZFFN{7Rg&=3bD#UUWCZx#o8aZqv`{)|=@2c^hiN-K+l66Fx#*UI9cbU94vCnyd| zn!}WE76+xyVM;$iaZmys9ub7II4F${Q^HvsluU;y;VceHsl#K0a25w8)?rF$h=bDW zFeNm^K}mLa)DRltpj10d2@P>j!X2iBhBzqg4pTxy9F%;AcN3u@4obnpl+X|dCE{U9 zXo!Q-@$mlQXh_M!h<~%RJdF6tQu8q4Xh_e)h<~#bJv``WWpPlV9;O^;$$A)ZG$ia{ z#J^e69v+4qXNh|laWo|FVMHs7gHre~rEeAoCGz2=Nh^zk()lnYG{iwkeVEcWi-S`8 zFeNm^K?#0%%+fc@7nSCRDd8+%RI(qYgob=kDSvnr6VCEQCH`SbXvi0p{)Z`{Av-7u z5RYp@Lv~OqAf|+d?4X1|ObHFyL1}@Q5*o6Dk^}L6Cp2USr3hk5Xvhw_C}`jNxspHj zxu1^`p&!-#oTs)C$nk#8Z^s|hzV~y9lX#@m&qJ12`Xr|G^N=N$M2Qzo{XArerBY%_ zKMz@A36+@A&qJ12S|z5&p9fE*wlMzpe@0*q*7x5SKS zWj_TN(aL@b@I0)Q-4ZjRmHiZ8L@WC#z=&3MOU#H?_EUiOX07a}03%x2Eiofn*-rsR zw6dQ9jA&)I#5`bYWj_TN(aL@bFrt;+5;LNe{S;tCEBh(Hi?~*HOU#H?_EUfnt?Z`& zBU;%lF(X>pPXV6TwX&ZAjA&)I#EfWVKLr@k%5I4n(aLU#d9&Bb?hP}dm7Rl(Xl1v= zjA&)&AR}7YIfw{AE4w9TL@PT78PUqlK}NK)TVh6hWr>wg75K{N=z^q(5nmaVnnrwO zG+-L>l~Gt}L@SGf5-U+ULMw}ds;a`2Ru%^(R$@vki-U+F{QpZFlvoKRguYoElvs%= zeX}^|VkL?WiGw`#`pV*}V8sZ@DP5xhrgVG8i+7Qm- zATJvF!@^k{B)soa;vi9UjR{+aN5sLYY>Tu)rL;o)LMw}d(h4!9mBm46g%Aemo5ewCh4{C=SsW~72c;F_ z&uC@YL1~5fueGu`D6J4vT3H;FR){H~Ar49_#FWqw2c;E4%p{y;2dBC&(hBisw6g4= zv_ed2WpPkiA^yf%SsavBh$;O9#X)I>n9@&B9F$fFt0J{zt_Fy zkK6yrkIcPR*0sCWdVbo+y=LF)?g$WY9*Mim30b8t;Cej&?z9b5>xtSodQxTF(ou~3P`Pl@KE2Z zQ$T7Zri8Oj0jZUk5*j)Mq*mfk63#jWq*h`|Xy_D>T8Sy4p;JI=B@Q>Cp;JI=C8mUi zP64Tvm=YQ~1*BGDN@(a5kXi|?rO?nRAhi-xLPMv3)JjYV4V?l~D{&4B4V?l~D={TB zbP7nV#FWsGC6-!=^IT}i5=*Vbl+chRmRgA^p&?5wwGvZ~hSW-^O8uLqR$|0gmRgAs zM?-2QM*N$lR$@de%Mweigbi|>rB-6Z(U4k+5&veXl^AiHrB=d(IT}(cF`|`aiKSLz zO5ZGRSZXDvw6eTmsg)4Y3Ju*zrB-4}-z;xfY9*$GhHj)%D`8~y&GLq&R$@vx%Nv$j zi7BBWODwe#Q^Hx6SZXEgxX_R#mRgA^p&?5wwGvZ8LzY--CHw)QAxkW^5>rA$mRM>f zri6wpvD8YGj1wBN#8N9UB{XD-rB-4}Xvh*vt;CejkR_H{3D-qv$P!Df#FWsGH!QUh zQ$j=Du+&PpKte-yP--Qngof;()JjYV4cS4dl_-?wXh^NZh<~%xN{sl*QY$gyXh^NZ zh<~%xO8914S$0rrC8iu_sg)RUG^AEy#J^c;B}N=)sg>~h91W?J7}3hIgHkIorEiuU zlv;@?tt>kzwGssbg@){))Jjb0n`H;3R$@wM$PP-agkP#}mK~H@i7DYMJ1DghQ$j;_ zP>D78x58O=P{nbW5*o6Ds*l5z(2yNei5#YchU}oK33LORdC+qan2tBmT`&D{+s~$~pz4R$|I=mRgAsM?-2QM*N$lR$|0)mRgBgoQ{Un zN{nb_odQxTF{N+TDIm2HQ(9T4fYeId{e*^20jZUk(l_fAkXnf;p`lYiY9;QF`evO1 zQY$efoOKFFt;Cej&?z9b5`{;FvrYl2m6#G5It8RwVoGS}6p&hpDWRcLKx!qfyFx?W zu+&OS2@QF}QY$efG~^9St;DriXviCuT8Sy4A#Ye}C8mUiykV)8D7Pv!CYJN=yk2dBajG5xx=5@`j~WVoGSp8xtS*+Hq5m=YSYgHkIIj?_2H4oa=WlyH_ERQVvL zgof;(DhM$poMi`9M2Lu|(2yNe9U-QKhU}n92{9!!WCvAEh}zjgLv~OFg_sf=vV*EA z#FWsG9aLE%Ll+chJRDB_)gof;(N(`Y`5E`r{3k8F6j{OgdFT|7UWzHLtW!XGDW>%E&?z9j z6jSQ$T7Zri6x00jZV9{T3QJ1*BGDN@(a5kXnf;p`lYiY9*#d zG%!pQYOU#H?c1z4ll2&$0%!pQYZ>Ol7D?0}n(aO$2o~E?2TVh7EvU89T zt?V3RL@T=`W<)DH2YKt#%FaPXw6a@bMzpeXkP)ry9Arc*yCvrFOe;GF8PUqlK}NK) zTVh7EvU89Tt?V4+HBKwLC1yk`I|muj%FaPXw6dSRjA&)&AkTYR**VCFR(4Cwh*owE zGNP58gN$fpx5T^`YGvmjBU;%x$cR>UOU#H?b`CP4m7Rk;Kx$>T#EfWV=O80m**VCF zR(4Cwh_5WQ5-*y*veZh9_{vf%G2$yrt;C40EVU9NT3H;FT8Xz(XE#}xK~2Gkvs-E< zMx5PJD>35iR%s;)L-@Ht=@nl`9F$s#Kckh!L8+CP(l?8PQY-OxtCht;sg;=0H;aQ( zD>0>S76+wPVoGR;gHkK;JS;TCL8+CP63*hFDjP8+G{ix2haCiQkQ7~w=$pkssg-z> z);EiTQY$f~Zx#p1BlHEuK~*;5Nn1FJgHkIoC7i`Usg-#D);EiT=kaBaVjDN~jYY4XKqF(aPeW)Jjb0o5exoCnI#cE%-0QLDUf%5gOv4DjOk* z&^L>Ns%*rRRu%_&JN5--2YI5^h`w2NP--Q_7W!t{L8+CP(l?8PJYe~P;-K15QCUVm zLD@ldq+&`q%MPk36}Ja{vp7f$+y5_Q2c=fx4-1Xa4(@v|QX$s8_agqdeg2Q^UZnQ| zzZdD%V4r&t>J|OnbPA|T7E}7W=@d|-ET;66)+s6>*5sJ0SQ`evO1s;$I-Ei`lrsJ0SQ!daGB zwUw9>&a%X+t;AOd4V?n2t;Cej&?%tWN=yk2odT+@#FWs`DWKX)$g+fnP65?cVoGSp z8&+*4ri6yPVbxXwFrgt1sZ6&6JhB&C&N&qi3#KEaSmefl88LcdDSZXDv zw6eTmsg=;3X=Pbrsg;-#&hmz(R$@vx%Mwei#FXPKwGw;8(U4k+5nowqB}N=)sg)S< zZEfBmNVVS_!G0qan2tBU)J;lv;@?eX}?y zwGvZWSsavF30epZaZqX{ru5C?pwvoC2@P>jY9;8UZx#ooR$@vxi-S@tF(ov_L8+CP z63*hF)Jo_Lg@!mNwGvZ8LmZS^i7BBW4oa=WNgy=DL8+CP5*p&5)JjYV4RKIvB~BEf zAr4Bd#FWqw2c=eGN@$3KQY$efG{ixvm5^8p4c$nkR$@wM$PP-a#FWsG9h6#$!%t|) z4oa=Wl+chJlv;@?p&>gcwGxM@qan2tBmT`&D>33LORdC+qan2tBmT`&E1@CP%Cdt} zD>3CbORdC+qan2tBmT`&D>33YORdCt?`TM^#E4dw9h6#$DSfl-pwvoCX=T|#sggcwGvZ8Lv~PVC8mUi?4Z<27*C-gJ1BuUQ$j;_P;UcF2@TmnX@!ss3k}&pX@!^) z8nT1Z3Na-#WCx`c!k7yU*+FTAm=YSYgVG8yB{XCQr4?dIXvhvqD}=uwG-L;*6=F(g z$PP*?#FWsG9h6oGm9(QFtq>#r&C&`n;wwul#E7HeTA_XJAvo(J9Q)iu#v(^QvU`Xg zCjB0w&L;cbL!?&1-O}GpmRM>frW`e?m2k=YCoQ!SBmVP{T8Rl9FJC8X*4dFT{SZ6&6(vQ7cjR$@v&51j(4t%TF4Z`LWG+Dc66C#_RJwUwCCH|rEo zZ6&6BcS@~6>MVrB*_OuWyzmmRgA^eX}gF)Jjb0n`Mcm zR$@xuEK4l45}vbug0jR?D>0?LAxkW^5>xsK$`VVhgaHEVU9-LPM5VY9*$GhAgqvN=!K#QY&#s@Nbq{i4k8}Y9&S-4XKqF@o$z| zi4mvui zmRM>f@=b(>EV0x|ObHEHVyTsw5*o6^QYdJO`QY$efG-QdTR$@wM$P!Df#EnvD z$P!Df#FWsGC6-!=DWM@tEVU9-LPM5VY9&%^goZ4!)JjYV4OwETm6#G5vcys=arqS* zvcys=F(ou)iKSLzN@&OuORdBu+R>0&i4p&1sg)Sq%# z*N)kupX>O4CmMNE4r-OjA0|(Egxqs3w{0N;JpQGLCOTAOP&f0V8j-4C3w`_g%#-8mb z@#oiwzl_N4v#|Y_`>ee6A!qM7^oTv%_iSw0dc+}XzqtL>-5UoT_=5xwspK4j<4 zU3<1{-L`S!p0#6W*uAhuKO7MYJH%>l#M+nK4oL~*4-N$C+$9d z`_7Y&KXv1zlQ+&}WY<||p185~%#HCEySJXcW8=gPpFV!~8O8A@^2c}Z*WTED{O&zF zSZ8a!z2Dw$G}YEqcbt08>AQBGwDt7uXYM)v;fqtxSud9BJo)iYeP90K_F3j29&y$Q zd(Jt1W9y@K>^x;J3igAKpwKKpnFDHJz8c|?g*D^i3|b)m$N`06of_e*Q7}raM4@gL z*2;@0&%)lsF+K|fQW>Dou@}08y!>qU!|-2@>8=0yd=v~i8al{83?`eSfycRZ-sM)q zU^Y6k8~O$5d5~bauxA!3%rDFh!;f50*?#_K14$FWP_BmIzna$nyc;+Qxz0>SLjyqd1D}o zwQA@V{KN0-1rgI3b3)EjwZ7U|29JN!txye|1KiyvD^ZmEzRAZ*)a80zt=kn z9_e@sTCh7ZIS%^uvRp4$pNJu(6UD$1$R;-6H(42-Y}f!961Wic6%q1qeYQ}FU86Y> zs$pTzP{dakM49G)A{5*rSK5UTOy7JTxQoOaBj>_i7m2vp0*%g$F*wrWODUEi#4uN5 zz-56KdL&K@Q{C`VwxF=&j3IdV|HMrU;oD6yK*>>zYB)iU8XhT4OwA;%j)$Bpl2c z1KS4mdbhqhju76CRwKa9w-+cuz$^9wQTL1?2uw(XAnGKN5wNf{6%y?irnEwlzp(5^ z9bpO~Jwhwq*|BbaX<`(M|pfD9E3dFL?VgH^*X^QM1zS0 zk#P$EVaZlNT|;Ir(d3HX6IJf!>>q>^p25?A;Aq2hWY7G2X6b7TSIQv5(6@#d^NJiems#n?d*^*+USIgq| z9Q0~wo>I{HZn_ifZ*9*kJhxeM8q=#;9RPC|(W%bZKzdY_JbUiXt3xS4<&Sff7~U z^Uvz%mY{Fn6b3Q~2_f;<_UIysdkahP$irK(OJrhrF{t}DhoR)9#b4T%sM*ONM5JP* z7!%}yGdLzj8!`XW(LxFx|Ezv?2^lS$gU};q=Qge25@pdW+?Gvj33(EY@>WKdLPp)D zAP`hpAjJtg!D>EJ=Ll7Xy+mUuXZ`#(Mwe1miy@V1a}+GNY|(amW8jZcxr2vTkFgDN z_MQy|G+HTfm5{_0fxu5iMVtLp3>S!U&JcvCLgJ}t**cp;AXNs}YRmDaK7hp*!ltOJ znfr{WQaBh_M-)Qh-sTWA&_2)~1p*#6skJ;iaV8eru;;u9Mjj9mGUKrNOpHE8n+$^v zSm6h6cL%)3U?+09I}jc;k8w2$(I8`uQ(!flZ2JdU|A3O)vK#V{^_S}@@|=*Xx+xnb z2wcTYtrQ$(Ajm|;orpp7AzB!a&uc)h3=xiiPi5SE)T$~D$s%anOB2`PoAo>_h z0UGYi+!2CtY`8p*{z8Z=&HC*r#Fg-s6W<|2YWk)}0_BNNq1d|v+HG>y8{DPQR+K0? zGq^*igqlNPj3@+FY*rbu)K%gIj^F2z5z9_MrL_7fHWVaS4T7uLo3ci&glWhlA(r>( zdc9s9WDUg_HU|N%rb%}vG7w<03ITQ|WI=}1XSh%fW>Xjng3G4Jka>U{NR{|KXF~=` z<~q$&iaLvez+z~Y?jiX^;6+QDf z4-yfGdWtw1?0?LFr~6SY9^o3l&%!oh8j}(`IO`%|6sr0!tcc%q9GapZuPT${uv~BW z-|Dz(DE77aX~0IH4woz=y#`Qr=fZ^2LW(QH2cf`H6o9}4caYF*h1`}r93=dsX1!jW z?iXrHZH@s2AqJPS+hu`v2ceWq7Vc46T1TjB7X<)sU^)$ZQvG;g$>K01p(E*@5Q>Sd zL_jPc>CIXq<-r232uZhuP)2QY2>3fUW)tNV5MH&uJh^xUsh5OM$8l2>`k-L9H&*`k zln{an6j3&dKtQ3KbGoC&Ptqbtp?KbE1pJPG76kz#af3XfsfiFo-NTN@)ldcm7s-k| z_8n>@%YnCSpKi|LtdQ?nBIjTIxDtw%uG|G4PZkYwxh5sLBBYH?fmVBR9dG4Hud(%^&=y{jQ0 zN@NZBgn?x;P`rM|&&^>Mph`;#C5OtWn__?`KjgfzA&jiNvidJ7+fYDpXv zrs2;bd|)836`Iv*<*_~MN)S{wi`UlH1e;krG=f6UxzMi`BaOXjH${Q05ORA=tB!|= zN_WZx&!QN$Z3gy20w1CRMJobfj|r~GDr*a;+3#~GwA!YrRL73Q(S2ZT{uENAnSE7KMSwR;`8!_-q`-G|Lz*l~dvsAVis}4O>k`>qwS| zi^OGt${0mLWekF4F$ZCS!SzTbF*dw|Ox$H)NKEJ{Lhq_geMN)8ngmWaClyxA;^t&9 zn0|e=CaGA_x1~UZgrt{@dTOhZ*%ZA$kHdPK z5LyfE>NejAHJN=h`m@(aYeYw@agv|;?SQ$Q^9x*%WTXkSNCb=-2s*iQi?)VdNclM$ z=w`L3*bsDPN4h)b6TvZ{iWbR^2_coxD|FMXP+1a-5rX)lHJ+}xJ~Uh>=>N`}{G3k( zF@nxs35~rFGex%p8ZGO)h%_lAf7XQ8XwtiqbN}oWrhTE>J22(Rd^6&WJAp0jHu#{;hh5 zWyn%N#FAv@{1%~&Kt$DwRIk?glJ6DU4CppmE*chHSQ2Fm9r4JFF=VJBkp&W|5BDqD zywLc|8h+W!TuWT4uvoNpHQ=0Vt63vq7fUP2YFDb&8zSE5r64QDI-HmTRge@^nB%|( zscVwQ=e!*z5E7GvP_xH)VZAJ<96+EU)bHm^<=NG&1=`(o zyjoZ+-b!|tq{XzRV*ri5nX|DF2+2uOwEv832NN@qJ|BSSIV7xK;*vzd07NnC?GSj8 zRw?5N6a4hm{BKo> z>ZW+B7ebCGS$dt0cF98J7EZ@<&f=!=R+S1tH2i3nsii&7HcRu7*5JapFUS?^`uQJ^ z7H>-OpySP6$i62R!aQg^XEc7+Msz^`l1q>wgx%7P$9Th+!W7^pV6n=S6l%q#CTN)v z3^gK#dUZQtYkL;45=I9rw_tBn=0Z+7H*OaFoKMH_Ude7qs!eCjL1Q2oDa;{UmpcFTgX>O!tS`whQ&Vq;@ z%686vq&QKe)$pQHTNDN^lz!c=FqIQ4a=C8XhDfY+8SfWk8=$d-q9AVIn6PRkcJsDQ zdvkuooc2R%NHX3s#7_mMTQqLQ*Q*f*-Y^Ns(Ry~ZFBxwfQ*ka3 zf(<8SMKQCQ%EEZ#b1LScN@pFl7@;GAP|qsdIG4PNjJ5j!Dy!3^qX849V! ziOMeL9HQS#2+qeUp-Kh2J%R&4S;SvFTiOe@z3^LZzSZoQyLx`y3yBr2;yq+6$U^2l zRuU`-TsBLhlKk5;4%=bQjM8jDPu9yE{Q zHK`cHAF^c~RMSh=A(u?_R1~5w*$tJ<=rG7n9{-4v@lzZY=Xp8*SG(l;;PI9+jV0RJ z*+_+onuu+~W2SXl%O$@I7}BoR@vbrx4EEQz&4BF7iWudRl23u`f+nP^FH$%}5eq@o z6}P~GkIo5``h=(KTsV;`2#xobv4<(UP|8X)6bKJ5`&m>tODvmTE~E8($lK&xB-q9s zs^c$Tpb9N4`h>-7n>oi|$I6c+(xhahm+|H@LP6P<1h9C@ukjso$hJ(zAnG>?1Dy)C z6Re?zg_-7WDU&gXK3!Aef{-)8cy5(FUW4+8DkM+CtP4L*Y)FwhS#QCJeb7#*yn2tZ zVX*9~EUNb;?}Vt|Snx_oY%wGUupy@O!nuc5LWl_n(pry|yhO(~0*{m(uN5jwKr%M8 z;PpO_!e$1;uwJeXER^_yDKL0Z0t(^Mx1N(RkD|E1kXG@KV%RYltP(0cC?`%#?{isj zf@k~G%@BRFwhY@oBvp<>e1)1PO~uTs+ynUp?KQj#ZjHaH9w(MI06s8PYAb$Gosk@T!z=Y&Jut=FsTl~8IR ziUU7)6NOLSqgBYYN4h+`N6Vri!ce8)5ceRdjmulaUJnv&Y+<4Jd^|{Ara+=t8D${L zI=Un1Uyp^{yCbOBF!=L*7WWC>=n`B9Zb*^i0Y?de&*&;xRvFUha%q+CObHlOVn6E=pCE!k5W`!xCGJAW#X?QJ!}$C~ z@~Oe5c+(kyV8NykUg1J%-QBI64W-m)?=&C>PP$&ld(KQK;4$JNOk2UO*L^c**GW&- zbdy2QlVBm@U1w;cK>c`&FkvI9B0F)yM6vCh#Vn(c!+hy9p<5YmJj2UeOELYu_iO^w zjRmGNxo6`&rQNe}kRxX#0>@=(Iu?v0W%Sm#X^}@`7f$xW{6*1p@=bFY;=N}mVN?uc z3QV4~^76Csq?5Ydm+|&96D1ULAqlmmc4RIwmwLUa)|=J!y{uqgdkmx1J# zi8k0|PFXxmq!o#!JW~n=Iu`7vZSG_~$3oHzRlM_zHbMn43S1^_1gm(~&$=#wzf7cV zg=h$JLcw1qIt(W%Wx2deq+$>aKw9RQz02uHD5;@rMb7d#67U?8*bwhJBMeN>Wu5vA zgWx&L{e^0D98)ofMjnNMWf0ap(g*{qirNhGf4NWI2~nT1LhoDid^7c1!F9H)csBi3 zEY_A*1Jl9=5m%waeQv3Xs7Q2$2)N~8qU!UAM6)02VZBKRxpCmfaNrE6LY+518^{tc zi&#dCUpAif?B9Vqu*~l`=@;D;d z4T+C+@g_8r>oB=bR+J(aflsRA5N|?L5_2v?6|1r3waO6WASgq`V_nGNdWhL)WN9A#=;$TR+RmHo|2m^`Utc-RO>WOC| zZArx-?mhZGkOI^&=&dwtF7NlLC`5fn?F1=6dEli!E-1|1c^qGCQ!$9Uj_98$2u#Fz zOcp80!Y@qtm5J)FPmMtH;u7wbp5e)!vJ+gQtfHIs z>*L=Io1^5M=x1G)#1uW`7$;sK;*Dp_T`ajWl;jUtbA_5{Q_sIkQtCJ{XDsD#6w>M> z(<~uIiD@rj8pax+LF zgA!ITdUbsoib_W}1FuuoyKtg}MHgsMk_jaeou+ux8LRzj7*N55lCU^W_E^ics<1ML z^WF=A5vd?VJ;w;AI(+JEFfp82~wi3wpzh62En%i8n?74Q@rO)%>}Zx zM@qR1lx47*@|m2Cru5@EqFSAL&x1qMMsRajViO3lQNwObJ+F{8ndxlnJoZCUB?dg8 z7!DPeXl_%Jov28oAv&Lpov?fnYtEJ6G?4rexn5WHK!sjd2D7pUDqQbe45k5LuQRKQ z$fM|u0zrC6kE~?^f+BB(pK=1C28=r4njLRIQvn3!PS+?|fLBfZ&yF}0ubkxS(x+_B z^6(&Tv0D0#1?3Z7CgM$K1VjNKq0S?E;M!Wf0?07`hHVB!&-JQL5P`* z>Z(Ko$VDroGa~IRpIsk2DuH1McP3&~*8bUkpARu;{JZ+WF5Za7c7wrSw7_Mc+9Us_ z-9kRJNR;`++t36>2La286eLzr-zghXB-X82(SpWgEr3wsJ`Na5leG*~*+vz^9+~qU z%i~W;ua@!dGt)-!E0(>~1y?ekD^?l`aX->`f^OSZzscy3%WXTA4pASHWC{$U{hfKj zrr$4-2NI4!rtO0HB?Sm7 zUMDPHpyBn-&WdK%^-BCqpmt3{563dzamMVSC05wU$o^lW`eEOi$i<{Svp*S2d!0yp zs)@Iq;r*H)I&YR%h=Iy?n9G4{1i$CLSMR`SC}OYX7+lGCW@sk2VP^l zoOP5TSO`E!@*3kkX9|lr!2>%R%fN>bglx{nvY`cdlkj>QZ#rWXAjU@Yoo24)F(QcU zdAy`SJm*mIz&j1if+USbAL4*|6q7eBc zEWlZS9Td1Dnap4?Vd$LWOcM-=6^tlJ6a%8PNd`-^ zwqefuVdO<(d0=~iU4a4(3&U~#t=(dWa8I_W}D#e2`#PB09@3tcGoM>Z8wplq&H zsVGE!NTHw?I_{bTUctxWVUKP@opi6_2|CIK!c3ispLwCC&XH-M2lJy*$~X1= zm2PGqg4%7RD7>QaR_s)uWO@iRfI~^1VMPMEmDV>PC=NQ~&7iZ91^|H3B>)liAuWH3 ztHb0&R#O^LE=B@NiM3pvSO)#0JA#|_KoXuY!%E{?D9Vn9e0Bs%Skz4^J0j{uTJ))= zWT-{ssmZXnQU#Cr#N=j74G+DfMBwPn7uyUR0ils2dtuDQCJJl&2h)*BE_NCSQUB3E zAkMS~5vXcL?kyXmGlSWYaoJ~a&rV9Cs<uMs+ogmV(Si>2DkRjWY`bQ1X7s{55*`{3%v?#0fa&_);6>mTz7?gzFkX1(q znryO7se;(8Hz|i?)QQ9?=iSQA28zDfgD%cvxMtu;b@lvrLt4iPB5JV%asq+ZYb$0` zAcW)vH=klQsVGFk_LdhTN0TkWyZ?sQv(F&$wKpl zX1h7ZUF=R}lHd;CsY^H(;(cfep6W0oCh$V?xK=C?yO{0^Q_;=+W@#v-K2VSi5pkTj zMbmR-FA7%M$ewGmfgv%wu%h@#6b2^+4M&Dh?v3A@#v_YjkhUFas|K0iGQ=Cv=zyG9 z;btO_{Xjf6B^|$tH=*I#l9X$rZ(h5`{#}!BlS`3~)LuMQya$b7P|22}t$Dh0Cpv># zMpnltrDTiMXNsK+o`)Xtj}e0u7Ml&Vu0s56_E#!E1R2-HtJ=rjzav51K1I?~JP8N+;^1t9TO{KnU3*D4$AHvb@=K zH1y*=pU~v2pR&h{+r+-`RlEfaAW$86Z$cGDqFi#4ApdFmcFr+|5SRyq)V}ak`;46p zqJ*WPQ_X2jKv?i)=w~nVF(4BA!dvOrh~Genp)S0rzTGbgF?hylE*DHZht=gNDx^Fy z6i6*=E&tex?31dDI0e^tC0 z4JScphi*bIi$Gd_mGytlrJOdG?4-*jMTsI?K`7{gZ8BLi9C=)@lQD?;kHSC!`DrKQ z5s*&>A>N8c_06VGG^n%`_R1y{ouulBH=@x-5C+-^d0>#d52BIBl1oF4e^7elk}Q{BS*|zyFF^oG9%+mYNPm0NJ$h|m zL!q^DHQ8*1R7Eg#Cn7jl5i?W^6Anlvd6u!?XHg@$PV*Q!{Sz=y+?6z}<2}pnig7vpW`j`s_L{(B# z>f+sJ3`+N3@W@?W*wpUgao2^-x;AW9S@;C!;sPOKtHC0 zc1%^g?~ICuixZ`3-|Vo+B!`IIkSceJcb>6CvVnXn`H@x+G(2VJ(r~0q^CRP}XC@TJ z{Xm$-Q5g5b?4=y3AjJJh-wG2a9E4mrkqSaQ`sg6&Il`(Vho(fi1m2QvRTM0nEakSM z+7j}yw;goQmW`b!?K$YKw-AVaHQEZ|7-T2FP3uP?$8&GP zpI_UcrA}7M8*q)qp#}X~O9PK?_7xwJx4}M4d)zsvZ)`ne=gwVwwrt(DapIn}V}wEX zUohzAjzQ3kC1bG9VX`>}EiW8B-PJ=^X^w-orJUSy&gmDUUMd-ds4fKA2*M~%1m6i& zO?aO$e;zxb=4z6r3yD|fHx!5lA(Fxm#~r724Z$3KkAAQ%=ls|a2w^a!rEA8E^9zQS z%V}q2ks9DYZ%2YZ&id>%W5Cc<>rH~dI$ocjhcWUL_u&LxY`D$jLy6S#n(+$#6$fFD zo~m7PB=9^`StyUT1#L*_p7GrzC@GB-X_?&d3jGycq7|j@sH#QP z>!gz*?L|CZp}(TeLrII&$yQS5fx{siTUjSc#^Uw)Clus0Em4_ZXU|bqvT4?v zghD{8YV?l=Vv4QkSjZx_l6*8q^&wm!$l&J1(~P5~snS8Xq!<$9z;l?hx_sOUX&vt> z-nJujutE-a32Plbb3xsol58X*EFt35&bmFU1=*+?n9|Rvj@RcW1)S2`eQ+7AeGdmE zH6FtKfjW9KcAQ6-F|7+GO--Zw03B(AJ7qSpvf>uxBh}&esA^|j+(KkXZPrl7tMgOC z0+m=X0U<(=rDpl7Gu{mo5Q&AzaRsA00L!y4zTEfmeub&ii3J9qa`d_pw^-KUz>d1_TZgk}>|`OZ-Lz z#+Xg&X!3izTh7^B>X@V~JwUAXo$3`8`j;Gy)Cx2pNJiOfGV=iy`7m0zA{D_N#Vu2e5TH|ik$vAQv zQaXdu2RmgW=v2@~2%dB^|5E|asU$b6TrI4n5q1m+9H*uA67Q@=ob{3Bgjf>5H%}T3 z-R$k80fFPR+LMkK=PxKV+~I87iCB~DZ58xPUu>QYEaxM1Is`@e;OO2QAg{JyCg8#k-%PCM*?22 z4HGMJFlWOM?omLH7=z}pi|ho8CDg>0$l%8}pavK;U_-RQYR*~ar|?Qzfr~m`iNBpB$}BE3S!OJUz|3B z)xN0`u9N(&VkjWopnnm6aUNvY41`>Qrib^+YR;up1w+}?>(nk*l9y{<8&Xh!1R z&IHbaU?^vPCMeR9l5JndYw$NzL~97eScHK}i3FSYH;E%b>oU3#k}HeMVuR+XK~T78Mxea(hVK2`dWlD{sm@1?o`- z`EMS#hhz+*57h*m(JoA$v&V4 zGXEsVx#A`GNsggunMI$+iUWo9>Is`6i;4ruUSqrnzxA~12tTVN3thapNAvqkO0rNw zH7UKA8o{y9N@0~~EXjI|tQ4fxg&fmtIDylY;5)~w@EZsa3lzpwb2}-P7In)c)jXXN z(O{!sP^x+8TtnPGlT`C^J*|C}HDQSa8~NFXUXLAAuuVYkn$Y1dj|a*sQRGQNTnYvd zqTCk)?$jg-%>7Oc2&rwd>v$o4iYe2@&ax*tA;>O68sAWvhu>S&b?!KDqfB$0$RCdE zhJpmbh7B`0j~I)P50HoVh=oY%S5wE!@EZzC)3Tp}U3cos&$Q_fc2z#r} z0DkgLW`^TP9ULlXpVYbDU7{YlPU4$<;cQ4N<=l43^gXPM3(D+jVEZ z&*T!3m<$jvyDt!Qg+RX06JN;X3b9-E?Zg3shmU8j0W z`&Qon??voP^i4X*UP2IlUZQcxhY*QfN$PzY4E%Q-6m*pHTrPQv2q^-26hNlsWl}Xi z3Jpa$;rStoKwG9n8Ojf25rMYEh{Q;I7j+(|JQ;VJ42LC3hD_Wha7c^3(v~W6LRQ3@ zE6xvDOH;?wF+7*Up-sC=Axns0P!ecD^E_dI(vO@W%aX~(V@NI91;g! z?Q;n>rS%0iNps9b2n2aefKYKHWaBj@g&pG!WvrNG9ZDIF8V*v(P`8=BCyOJ&v@D55 zKI0u_LS5zI@hFo=UGP*s#C=ES!*rW+K4h{NQZb18ji`34+`@FR=7woqaC3Q; zs@JIzU$5>BP1Zy7Xml9tjnWc~%ZjWy3`8z(l(>v(83FNjGG#@MRLBVM(@}+9^Ee$r zA*r*F)jN;aNh`>N6=^8Kfrf#EF~A`3NXV&Z=1_2;ZGW-d&P8wQsXIOOzm>)+djXxnPj!GE!(LhQYWCY%`QZ<9!!cy&~cb zGZ3(BwhR7e+aT&NIuYnBL4MFg1nTNjw`$Hr82d0LQz05^6bAo!8(6i0d2fU8B9#n5 zJkCf1=yIO}vEp%(MvS?f28oZ<)Rc*w2DHiu80a_<2DzLEsUSq1MS-9LVFDqS6Cn+S zCf-G6Lcxy&p^(d&kP1TdjXKmB;q9Rk2xQDzrd%c56pwYZrQ7(6tC|ihRbZu}HGPydXVi5NieJ99K zsI?Pj?=g-Shg1-v9-}sbtb;(v#5zy~Kjji_!kh>j3ZAB|TXjv^td(^wi6gY=J8Wk( z!yKx4Ze}0CPDpDQ)5IIbm~N-y_H4~J2T~=eg~)3xzqJatvxeY*g29S@F$9i!vOp>t zitz+RmLLTz@e;@>?}|B8-q~y$jH1T0Oc*O<9^pdCt0VPRkof`u7+9DUYFI<}nc$_C9hX~tEkFkli_ z+$4O9OgPadRX%Ux&03`hXa`bqkZIn#6}E83^S0`{0(F(2vKjbf->ly62^xil%Oz^OHkx6lmLy#<@&rSx&XJ7z`Ei z6zgAKRd4_ua7ErjM==IFAKHHQ&Vu1Ete5TTLat4`PfTU`vOrbU)(OFk<#;~Y_MkuO zjGv%7-N0xdvx)kv@Ogi2tZTHmfv$?4zu13<~t_VEVLe3vE5*G z>8RN`x=!o{UL_mAFxRPS=kGe1lrPb3h&PR~pMJ}MXmKWM&RftApd)CXi63pOpx8#0 zVlSg^WYL*ui3=4tSY2$l6Zyc9m@3!Ao5oCGM|ePklqu|JLHYUH%fzT-ykiWV7(*Jd zg`4ph*%4N1!!8t-YCh+qA$U%NpHq6JH1U2hHGf$vcaCv1FOmdHjAtfZ6S|c&1fq_k z5HL5vtmBA4Cg(vi1kopIwbu(qk~+xTr#Kr&BU#S*N;yfm()K~rZ=68TaX>2h(%$Lq zI4I_f!gw5{l3~RQwV#Rh5@RpZIukJyBu&o#29bOyMxDl*ZYaxv(~wiq8ev!ifR1Lo zc^F5kOQg{u8gs1K{x}Jfb{}XC7NoUEw@fw0+V-~%{ zHroqAp#p{~A-+;YV~*T4d6UOJ5^J)i?hOx9HM_y$E5ocGX(vu_Ko+ZeEjIBsF{Wkg z&??v+icD(${`8s)cd9Hj3veRofQ)yE8C5HUSL_Bw{I$1ah+J$$S{c(O-Xms0fNfw^ zs02YKzH=%DQLmBASo9ft&1BVcGyvNeUz)#}Oa&q8HC8msvN}8V=1@_!Y61b*nFsqB zdyT<$M&p_qag8^Lna)N0#-&9R{9Me$Z%jQFqkdz>yNk688t;xU#MfaWxLeys@@PamB6nU3qRZe$l9-z2S z&)8?&dzZF)3ad36@eVPDgO%s2EyltVZi9}ew@h-=ax`=Ho%iC9RQSFja1=3=!T~7| zee2l|c6Ts&pVc_d0*180_YGwTV=!1DI%q*Y-J(OhF$El{K-3R&riDUnX#u8qlNfSl za1aPM5@_sA#RVCtKgI6}$JTR}t_X#sIxE)oBf1wx6evVOO%dFu64xO-Lm=^+^#iq| zkk&A$iT8-HJwOAMi4cHbIg?E`gs6-W2{G4rml%gZo|El`tWrnY#0;SoU1CbSWg^ak zVNy6|l0nN>NPCc3(Itj+aC8#vVa7Vp5_ltvA{VCt7}Cr{yh}{sFvw@HhO9k3j5F~W zQ2n;+b%M?_-Y2FY8>FLFXN~7LKp$&EYhr0$rZ5|`ezP|_QKr4qqT!5~N?|aa3tX)l zdzs4TTqrFJy}CqQg98u)!rNvAAPBiw&u;T4YfCS!=j=a@*NMcS8+E2)AP|GDL(F-s zvtVLJf{@wlhP3KzO}tl(30ngQCTt_2+>)%9*ovTMnss5W@F*WiZK*03Triz?|~k?h1D}gzEF2m#jocqbPb_L^At!YUL9zOcZ!)% zsK^&2Q7Q<*iA7TGd{MUd7MG?|}+Q1Nf-xz0EgKPsP7L5TW{7EmF^LR^VEBzXV=AtQ~WrI;t& zCV6y>_|EnY)3XUIebQR?3J1GTRNb5vuY2DkQX_8h9x(+w!BmcuiLi*{(tdCE>N#Jf z#%py_ajJNG7=r*a5#sP2x5xtdbIWm=M;xAZYU>F#`{LbUvkt56TqPh zLWL6VxwWc9Sch)bDYO#~JU>(k&krkF!w^HDha!J(vINNxt2hxt`Hd`!U9%lhD;LIl z!&tx30E5b1lOSechD^#`gF|9_$tK<*W-8LR*o*bN12q;4!3P z5cM9dW@AZtbQOTFphYNQIQNtYcbYiSDB-xebYZ)qRScyStVEf7CLSm)qcjtjscfm| z*{+VWwOk8ghk}D3kI7Q&$sNn)WTch)dbM0#o~&KbEyj-p1)X&^<`HyGI~Cd$yr(SQC>&@zF8&-}maw1sye|u~QS>+TZ zv$FUh)xQtcG$r$$;?UrCQXNba^RKe<@7nJ_Y<48zL7Hg@T5!(s` z8f&k4U7=yKe%u|8+GaAbJu`lU@vV?jZ={Vkim{F$gxU7effhnM%~{#+JlK#VWozS| zV*FfmPpKp|#Qeo9JSENpi>s^@aJ4J?#Q142or;vdZ|LHl$v#Lt4p#Ju5eB?RTFY6T zh`C%KQ$dIpZB%y@xk(cUv%f%wH<(lmq7TzGE<#Pj1xrggHXh}1lp3y51x*1cI5K0q zFp58I*XRyUW{A3rwi%YZ#!+Tu*?4aK zfV`s!ZshFU#jqK07s}Pe1=@J87y*Iszv($Atr|^_tKHF5t}kbNJWix&c$REdKTEdp zW-&ENR%@;}oeq{4g5!|M>0s3>OUs#DokiTP>J~#KTLRNEnASbU?j5-p%(6~us@9@a zOgJN_?LcIqLfM(mc1ZFX;>}`ef=dJUNe)SBGjTRhh@|I2naLpuCz3AJ@n$gsVLBj5 z1~T139tR{Cr1cGI#;i!jYPThjAp&0iqexH`NLZxbUJh^$b~6&Sc-lMO)Q70Zj6vBxmMkd{;5BH@i# z2r+_=Vs@nxA&NY%R8%ufGZ0aIQ1iQ^Gp+q=9ytJ;1>AJ4PO=VF^o80$K({kpA1V~keMkgt;F(+>h%?p2dYzzOB6Jc#p+vA@(WMg2PK&ohix4^@H2fZ`(aa$y zW-~s7>qBCr$u{08hDuA}DFVNsw%7TRBN4^I8dX=QcspkhMm{Xl0xR)GF`=m>!e-kE z{N$;zVU3jRBALZburOO%lgT#TD5l_WY!U1j4mIJm65$SFo{Vs)($YcNc%ztt%9V{> z9}&0KsJ;+;E=ZlUWT9ENh49OysovQVtMAnDPBA4lT|n8NyJzxTm@r;vmzrP64QfP3Y10+W`Sm-56{%(OIn|9Y8(%E8U_T9 zmpN>Sq}VG@a1j*ftY`HJ>A-8o0d=F5JSI7)=mRUa2as zRt0+B5@AywS1K?_3bVx9#t4JpDVU$+7-ZrpByWVM(@07=ebg#D1(3b75lF2caG!`m z*2Vl!1wcT*!au8PEwm&%M65)?b*e((!QHYW%EYRRh6!7w-XpmnjYeJ10xtD%lWRE4x`gSqliBkI1f1$eD|`jxht#*f@#ZlWgEaZGxsb@iDfBzmEGv<_%{n9>oXBOOsffCehy}5ic8P=> zmMNqdLnfEXL{$^_gvbd=ojRT&?0J@|G$w{dl^&z!)r-*H+tI%I@1cBRh&3$4BpyY8TuocqIglNRkPC|U-HH<^mS&L#+Ue>A@ zUz(@AZpLRq@SOpH#xDGmkS-MO7ZaKUjh88M?P-|PvJEVChS%)n{0r9wAc(n@30d~> zo-s8^#MYb9f^1d<-FU(CWw>6?p`ztx5(e#ulJ*c~LVt*s7*gbTXqMD5<*^bQ*U2b+w53!B-gt0dWw94VuXjsUqWa=!#gc#rh(8c@&FPVBq4Cp zZ-2k#;pRo%KHIj{%>v8plUr48C+sH-qL3kQkX2@~r6M zW^gBv+J29{LYdostvmyRh6*eXqxUIo?E*jwj$ z6_sO=8aOURHOxsBW`}2W&B=`I?~~wKaEMB}sDaXfT;Ljl^Z??+=8v2bax>J@2RU^5 z`J>`w*dyE(Hdq$!jN8K?OEmcys?Q|vqQ?4c7LtxDbPKeh69=Vcf+TG=*R`RcldmAN zEi52V$QaLo>GQ_Ml4|OzQ%kwGS~_}S?2E^Ux=lK3nl&6;9&X(TX@(8DCxox41eOxM zhKvrT#D%SNS+Ix>2ehwC5*Bz6pMjFW7m+a=tW-=3-IduOOI(O4Y#j&yII&SPN9iZo z*-YhcHt^(0&SvPULT59+j7)_DyV6n)BlLMo79(irL={~kPr|JpC75zCVHg}O$Z4P* zl8AnQ&`vuMxVZKeWy}zELsXH}fYUI^4Uq5vUt{ zNf{u)$alB9<3-oGyH3hDxlQB{v70hqQATFCn8ngefeJ;D6LRQ-@<}i!hLQ<(HK8po zTY9gf=)N%CM3*N`^Cp--M#hhH&yE%LRV1ivD$Nz(?$MY&k1K>*3Qma~LkJeWGmH>4 z9&&VG;t-`novMoRSZ7}9cl=|hzw~8gXh?_ygXmB@sug$?vpA+v(=TZNM}uAECvbC1 z&#QzI{$3q95L>%V^(l^ zd&~;75sewxTp`Jm2)lwy+*3Ffq4;7PO(^8Nq95W@OP@yyM_4%>d4D-_`pPouqjyda z$AjReMiO+19z>*S5*LvZ8Vl^mqy%F+ENC+73OftYAp^cC`kj+92w?dvtjqMJBXhSV zxiw6%*l^7VEbkzWo;ff)sNkaON_A8LZ*a@U;SIco3-SUpM3VhA-VnjHoTzR(0rKEQ z+yED}#79(uePxjxTM`=}J{7ZpTRUQdgA>aSct6)2bD0q+tjV#{j~&BCaL1132qG{l z#6~kh5=sWPwVQt4Z){IrY6d%~bkvNgg7-5ssJpuB)H9YgWRVBRxfR{1Ednm(6 z%nKM|1PD?>C6O4 z5N}}{xaH%pLEqC2BP7|=g{|PuAI%DuC(eWAy;{vQtLxZva0|$1;{2yQU1z||vZw>* z6(RyXeZ3hogJFS<3pIxx8AgEDEU=aE1!rUgWe1iLfdAlwW%fn^nPVxz=y8LO;Qinc zjxMKEFdMLwxS6>ziSLM>6gvqPbjN3a*6rli{N4y-VXFfH#x7YFwzz`4A>ayb)!1Eu z`ASo!GgnBnu#G!|JA8beKYe6}v-HrIBunbnIG7;ZwRLv}Yo!Vdu@}?~Nd_%?55Xtl z7LMHt)<v2^696>6-B^5tf_2~3%;TOdgf z#l3Z?aQ2mE%mf|ZrW^EINRBLpbJPef)Ug}EQiDB(EV&_S1HWQq0l{&^hzDXRk@8TE za6oztO3a+T#EeLaWQGCwe9VyMJ^Y9v{Iand!X8EUhb(UbJ8UdvYM`5ha>HvPh>U{4 z*|1TC8Mf=!sJ5Z0E@vY0fb9!n#4+qBihr#_k6rFmyji6d7XW zf?G6-7F@4kM1^jJJYgYb1b^6w5M3pW3|HWgOacOFTtRC&7`(Rk3|^-%H6xKFDejEm z2sy>wf@O-!F_J{+rc~-`UW({e82-?$kj5Xd11=FjJNj}n<_#k_bUWk;4pB4wY3%rI z-M%#Z{`}kD{`<@MXCanvAmYCW6~BCZ`{~=derecQ{q6Ck;l%#ezvxEa zDv2||#U}`|lY_L1ZiRNGU}q*522~#B?jjU?eT{}%n9}f_(aZ!Wg$+1b-B^OdtOFeC6NP%RN1Ip=!Ui8BaF zIWvRgbEz4&g4?=}C&8u*ka>jyMey1~!`-QGm%9;^d!WX8zvy_Q)zG8kp_@&v6)id* zFkO^mdW?107u>cTfew~j8!w>02nhQZP-JOdL`tZO-nAagGI-U!RDp9DMxhVNHJ7A`YH`(1S_D;2ssMqs11CPhGBzV zI1U@+C>+B^a9ehQLc7O0zz}oZd+IU`ZP0_A=ry(C-wz)s`U(x^3|20fGh`_j!rt(w zj{V>M@%t}7{p+v4{`SY0pT7O_pMQM(JKms!tYhcu1j@YO4N%3Q2>SnDE;<>gAN1&pEp4Apa4=YOiqYMzEkz zS9rl-l*@-x|qV?nL6YSJlXTIdu#4&oI z>FY9#n1C_b)YW1_j;U+#G5E3!g%gVkvnCWtEPS?)4Kyh`<0QGE26+s8L57tM>SDOa z>YD_KI4mq!NY;FyhbT?NiOU4N1>iBUBUN9KK@fmVfdEISXoVN)bTEv`;t4UG=+jqZ zXr93ChO0A#PytDaa+^=Y$Ytt6sg2(8gwS-!7i1VVfIznj5A)c7e(E}A&I_%9!UUnK z^~;3@t9f4!ly*b3UXt$#=oAcG`W{AwL<7*=Qj5=}F+0wMCA*%_V)87!H6H3kTY#p7`@ zQMsff&q*F?$$Y-lP|jX4X#>%MOylGag@OSg0x&H~ z92vT5{DB1;KM{X$m(~2*m#rv4Dn8IrEr_=$KBTY%q#hyuCtr(U*noy=r)U~BNHTVf zNnf766a%|yjxMg&P*Fu;1gMsI7+G2>Tp`p_`8o{43aS_g2g3?f3<+2>qn_Z_j;jvP zC@)(`5SwlhuWXi72|r*gB)FC1uz_9y^vc5qNkT%5G4pyO!ob+QGF#D%W!1EeLDK@X zJWWVALLoud*G?0m%ftolR6U0qiX)r5rbv(~6mI>z=P-C)RFfkC*T~Dr(M@evC^TNa27_p{H=axzKTEv~+VLG8y(Hsj zj1ZYSUcLl_903GV3yli^X^mnR^yAZn#w!>a-7WR7LYuFMPr6&7ji4v)C7+NG^#p(7 zSS8Q&G)xB#<%(K*f%8Chkp$DteAL z=$4~}MlM3Aq1kN<>^S!xqJ>60A@mw>b*E&kg&A{8iU^TVF~|ms@b5{sv{-?2Hh$hu z*{*hfsKdj5`In#m8QoY(3I|DnH9fTx2>5Xg0Yl_6^Avs@NyYrZoi&aWq3F1N&k!j( zDH235IJygKMz(-87_0R*N^zoaJE*{rpp63T8*>D=Zq#$wITLt-)>+173M{bAbN9d1(@vp_|~uK9(?j4TfO^x{2lp zW`rc35L?MyzWxGGHu^~|=sa@@>Cy}_M0nhjJN$Xq*28yB;dG-tF?E4WpotWXsW8w-v6K%#bO zSZIRH1A>KbUPH4%=b+h<@?$-VCnVvlgRpM>tnYEpf~UN(gOY@C&{$N+a#9_A5N_eP z!_dW}Xe~nv3u5TPZi&J|croS6FYMfnZBP*$^wJg5g)wj>eeP$Dfn)H3?($U_c3@3V z^;wXtln=K)B_Z&Wx}|;st4g#byZ~W#us|sYgX*a?adJH*a~ar5uxxP*u}TqJ-6fOf zo0+_TJzVGUnLGS-=KYKTd*GLTPcMD>VhqCyj9qv9fZGt1KSBS&t zzH!3DBB-ZiE(aS46g@|snFm|3QRD+5&R?Acdai&KiWty&2Es3pGl`MFz}++tB7OJ5ZY3%&-!utH}o8&*g{^BPPL z9EN#Ofji>8-P=EmT{@^^#?WFV+q&5+rX!Xl|12``&!|t`5jPuHk~ACSNRnJqhAIVE zz5|XhVnR1UmY5K>f;)aRSFo|0J%=P?x3Cf1qLH#_*SO87pP`}v&>{|o4B9D-S7Oh> ztr>?6dQ2ELND>pGMsQ0;GlIngGeVk}5VV3XA2Y1b>x5y29CbqUJ^1o5I;_8_r$%7h zXe2=dW(kZjPW%g;x0fTk5iBq=sc8ZUNdjZk27bY4Hn3u;J3^9)0%ygc?7-9(`xvS{K_LO+y( zxR3E#iL9Y~-Iyi-PAOq^f=KKdd0&pm2#Mjb@l)TD_g$QaI%%?8+B(oU_SI~;0sS$H z$+4OhnIkLe5?N9E(lNshx`|-{u`yfO!2lSq}=qIq2WXt0C!?rANPkbLN@IT49 zwGO2+3>F%hBeS;a47>}seAI#1+1Rgp;RK53qT)~qvCfeGTjEfpJZh5HB&oDRH%Vw(wC4KkD=Gj78#Q)BEpf;9Xpy4tZXzR zq$wMtF~Kbx8QXnDbA$Zwz`muWayI$73~*;WA$%qDPD2JrXOX(pGglZQGFd9eF#P?E zhzx7EUG;&@9Yx~@IyOy?SXt0u(AI_kS-2}dr#mED?8i3 z>QeoR{^4D%evA{3eB)FidX5dA2aVGdKW=()Gg`TuJT5A z)C&u2?pHWS#*0`%almq#DM?U0z28r1`Q+I_u%cQyo(1rQ z6XvQk&mzWmdi$a>o0ipbf<9{55O!ZNTp}y1YOCG{Nnck+c3AlBkKmvyds>h*!GQ=F z3zELJjM-rX2l{T>sw}bLfRyHQ52Q?AS7s$8=%P}u0zB!*S}7u-9cZP))C?3I8m(!= z$xAy1E@8J>HiMoPY zKPo1Ih=&n-WgCM~DRc}MxY-H_aIf>%(L9LY@XZ&M0U6)6*h&&RN|h^HD~>QSJ&EW@ z`u0U-RwCgoW^8a@M8pqR0s+0uVMufLoNx=zCPp!4S)EaX%Q%AI!g1*&x5*yoVkm2%%joYCO z;np3Sr16Ge1^q@_Mwe3D5m*InLpQvFiZa1wf(kmenRp{F1Ka6IT$DHTEq28$iJT#- zDRPtgyjOAai-$k_36eO*O1llFBb6nB^QJdCGc08oTeTo5*QhYHoS-I?1j%SCK}F=a zLtSCXZ{tUXuTZ|$gYly_R#|dp*bHt7DNK)48KESQN-MSF3dV;7ig~W7>*$XCTX_b?AJ{pI-Or@#N#-~aLZZ@>Qh)9-)%pFjTN|7oK)c2vQP=?);9gflei8;7a(f0(%&3=4sRA1WkBM$yWTrAvXdN~4CjfE~ zHPCUlhWnmkLGdb6sKJh%qTjy2OgDg?OmqXxb20JC0BuVZwSYT&>=w{$somP%bd;Jz z&T?w|(gHtZ0=IOmx)5N81W?yIMAzm4v z4_87bGrp=!WrT9jaO;lp1kO{LyA{ouG>boGE*JZ>kyT5THWv830AXgUj6WZ|*E6s``zSP3257E z)e^IFF#z-n)lF2wPIS~cglr(3Yr!jogOv?9xrK{h>U%;JPGKXst7K>ks8t&H+{p;;(%j~RUp?YntV*&p?%)r=%vSDH~7=CBtn6$np zuDX3~8TzH7gVP;061WUMh>RyYDL%u2ddz$j(5j{AGR_y4F(cGU7s>vCJBhNinlZ;o zZA?t$rUY^dXN&+H2`{2Dd56NqJYgeL9#afmU$s$BwG=giTQXvVtI_&VBylllj*tbx zgJ?ihgY5RDWmroWwos#~W*u{$?J^=qxj<7LVM-U_jPoYgPje-wwR|5f^xNVzeUwHE zq>1;m3%}z#6Z7*H$8vocGzHsRs+FRaUa8T zQaJ@#99HN}rMW_qt}^TjZuwXQ`ES7Ihu%P$gzSYqI34(FW& zAuE9NeV`k(Q?NDBRVU)@VnG?(SHF>%>2?rr*UFx{@J2*T15eL(&Cp%@iSl5?uz%J7 z=J%b*%=xUQI`1h*m4G>feNMrd9|Uxv+3Y7UJ_ZBcv zpP;`S2FwHMd-)a`k|$__H_|eX6Bzexxd8Y!`YsLP=U>snbsDM@-ssA7BUmta{2)mz zh+4p{7p1(nkq)h1)UD7>aL7_GGJua!`1yh|MF@pIv7Gi3sWhNAPJ?2wcVAIvW#w?U zLu$GO0XG;kEMjh>cvzR2;R;5|6CAE^9_^$DGlSX%Mhe{s^I%V%mrhY89__~aBxg)1bYxM zBlz_qXTySXje?PV#JTEoii$BZdn7BrFf2c16FWvV$2GLf|nH@gySq%F|UH-I310Y&zQll9{a1X zyO>g7F4Os77R3Z|Wr%_WL%0LS%Ci2%Rv@5RJAm!teh}gTK7#lb<^G**CxufW04u$>@b1`H7+s=^d*6}jma0=fysO9)vi>;oWR z3JbesYC4)$If#rVw4cKYZW*b{X@mL~>H=N_4gv7>66Yxiv|KqHqVhWG`lk_H!*3(yuh#I>b}5%v76?}>Uagj-fV zDMQT2x|ShQp3H03)^f}+l70CsG?pe~L^}*$VMf3Gol*376bch8w^Ef58p6C+nZY02 zvQjQ^b6jesXjFEXKmnrX&`pr#IfQNCmXN~+y>in2>d=5TPoEil4{rTvW*AYSJ42SJ5OoG$US<V!=EmjMYh!0z*47a zv%z~&JVt6qAS+(cWQL%8uACf6LqM$J7yj?m?1d0L|O3d4GD&D3rU3n@Ub6IxxxEN z%xs5dcnBkFh%M%S8jx9kmLoEm^+jhIF9AjO@CcZ23)duRJOWhnXE`I2SzmZYSrXaK z?=b>atE$h6l~V}Myy~y|)8J_OlU^Y3G<@Y5&EPJTlq_)?S2WbfeNbE0`IBet)((M6 zx1cmN(`E#gT{6mYYZ3=`Jb@Krl(<;IU2^MNGhzAuCTOm9T!SdNvdx=4N`_s*ttMBv z306f!sN|`lSZgYWBJ<0A_B91Dup1ayWl}r5zaGFB+*+~#d6IIYxKL5Hq^~){ieId5!wxKS!B1g^``TuXjx9^z4BV1HLlwyqZY5a|AmVtS zWr>SYS`6~2V5bR?S8(ott2+%=ky@n!WPcRg9Fim`Dv1m;;6&Xwq23B-C?RKXN0Nmr zL%^~htn2Q|kfKmM-t4Uik$@nkZ!4HOLYckiPJY*?{;chE%fwmSO5JgQkKTAb|$PFdn|1F;YpmYJ0o=cP`aybGs4Q= zN|0+af=;=o&}MnnagE!XXufdAlls~$?_kfRhE$AX7DM_FK4_nMW^c|A)UaC@U!!KD zh`t(F)!ssY1herfev2h1? zM5$l@rQV8})FpvLr?&CU3{S$%_xyn=BtbC1O3;2h?b?rig8V21;2`4QPAn=r zi^^q6lIR#@UdIM*0jY0&^6d>$F4@Nmvt@yl$GBv%f?GkF6%0#lR!D-S4jaL(AI%8Q zp^2Fw%b`iw2JUiu*q|SpNDB6V3?j{KLD&rbDDvR$jPnDf#x|d}m1drMn{sZfG$YVJ zl)jw~Tda{Nd}4~uXn+pvs!>!PMgv4C!Ofn5?d}`l8oc76IT2ctSv|)V;tWf3Y@tOr zrZ_{Ua0iko+&!k?+yXLsiU)L2X}*QAPH}F*DHg+*Ep&O}E7qv!m-ij9<_MGDkoruG=mVS5rToG5hEm$BIk81j}epVTqdH(62#>l+%a z$higlHcsLg@1haFK)|f`!0l3+Xer<%K-x&!aO=vQOnEY!9uET;<--w_=3&Gqa=xC; z3U-ocN>PbS#m5R0j(}pC!W?DhNZHRV;xI8^&PHa?CR6kIMT8d|FRsvpfHov`WVl3% zml1ApX@=mUdA|ggYM|2VgHsHMfo5Om{YVYr3))B)@&;O1h?O@3DTELa(B>mpsX8-e zPOFP(%jN6YmxxY#CF5-h2U)Dy%ai@@BtT0*Y zcFaeXNv&W&u?EW*wy`5ca|ZUhX>vszBj`)pjH;mz9pGGT5ST3U>S*octJ|0r%o7k5 zKLHdbi3?#HxD&~)oVynIJs7{3da$N&=gbxKgD8)xim%Jq`=hB34J*=Ry za>MiVamLX*DMY#KI9cj~(JOfC4fpl^4e{BG{ z*93#NXa()d+?W|gTgm*DP?@H!tXkc@-%mgR=S>-!$UObGRgcn`mv3Zw5FFSL7XlQ5 zP{Mh3uv>l`!b0qDNCYXz6gJ2$3Q1bT1~!E?8zdRShK=CXj^+rqurodFEKOzD3U2La zR%lfc^BB`sZ_?`XAM%Hd;4ZO^<)nyQmi-JEdTlP*S_%|3@m8YWl!RE9L5F%V!E8Y3 z%v7GOR5yZR#Fc>70+`Qd00+1FayG*XebQ9aQXzMeB`65(6f=WcIhq;Ttv0g=U8Y{# z)TqS*-7)ctu+zh!72Lh`MvZiGQ)8%c?rFd}HDE&ud4eCfv08mGn+;+U5IyZ`pup$= zcLB#|d?sMdAsPDW_^?OFP&);qvKJxE2rK?@Ft9cwNLlxq8pmK4m_aMJwWDoi?1;ej zGD(gI!fP2pex0Rl7s3N3T|sn6PgqAaw3ye-3{ZB18qE;wQVMH2VA45Bq-VTI3#pAqwD^_2_ru%};nM7Q1a6^#;zCh&|1cdQ znPtFo1?y+Xn*uhl0gKrn$$%wl1Yg0XbVslOi!wtyOiqH44_m>l8qEr$l>!%jGoDM* zN`;N!7L5fao2q;u?l1L|kbY@@pTr1krGgccFJA*KAjc7WZL1a#?chi~OS!;NS@|r1 zn-LE9-RRiOV2QGnLPN#uF$Es{_}uv`vXJqmYs9k-#mjgSwA7T1iJnBIdaRL!jIUgy zI$;63F$}TqFqrT;$E+zxH!fs?S1@Rh!V?&gVFwhx3j>^@k$`X<=xd~#@-=JPJP9+5 z$iT$}|13#lxT4}-P_uk@hHvZkrOg_?{pBxTe);w1-~RUBUl>08X_@;Cy|*v?@b7&4 z>D#*UANRjKzO=sVfB&0q?5(uxC+xK6q1zKqdMU;Vw_K)9x`RQMO{q?s$zWnsD7kiV45W7~` zx6eH?yTHBQSV1r1kUka(*j~he6}EMX8O{qUJfKpF(-_cvWkB=B4tf&@xZ*lq!~y%k zRc3w>!wRa>1CuOY>cQT`A(ALGlR8SFwt1 zZ(=jF1z`M5_(~7M8$@rqADOsKa#WCBjNX76*+o9WGyohdxJTGG1 zSORX5N}6j-uYxp7Sj~VX4vOz1OU)3s!=L*1f9@_fs)U=XBukeYoyEowEJwO5pV4&b z3p+?J3_Zf{@qx5SH<2ekM9=$IU)90vFyceEL!$T)p7i>{4rB-J`xZm4N)2<=+s0KtRiZhX#AIRs_X#2o3`fSCd~+@p6@n*f$1*(6;R_i1cC zB8v}x&jB-xG|@2lcak(w!^3W7N}d&ZP_Xc5K_N?c42K1G2suI{i*l$`KFkUyY>VYs zCmuAkxqR3NZvALR7_sp{!vNGkme?4>oclTs70h{oncQ+@#UV~M6ghHakmLbMKrbW= z8}!G3JRD;tY?9+KgpJ_hhRg^S7tIl}#Ko``T;R~Kf(=^wbI1`A!ZvVAMgc`O%B7Rr zJgHC@c+=3b;IlaNrdZ9~%iq>tj$i(7D_bB#QSg+n;m}yIxcNVckQ=urVz-ds0b7c% z*Bm?B7iMTFvP3b)zJ`NYK{FLt9=Z)+gJC;0LzVEt1Z@x>H-TF*icl7jX-3fCN#S&W z-Qz2`dp={5V~Tae3oCRMAl;xydO1LHu_Aov6wfQy2Mg>6sunmqD3Eb$@YIThI>nam zg(Y-@p~w(yqz<=k)GN4v9Ja_nw}HlR>#4IuhM?{f{9nE=JXUxjgV~@(hGYKF5Ve7? z-B6b0>kgPopEzZ zt`Za`O#pekwa;e^c^)&@(N6&>nS#$X6dByPquJqL@^<3|W(Q=*fdNhdczfXuy?*)u zL%4;c2u}?+45!B6@0A*bmq4MeA@xZ0i7kcC>-svu2r76&-wdo%nosWfa!Te1zARS%IdUeGxM0ro6&osF9^WD)bYL*)4x#JEM_oz%D~L_!c`%tYJ7|ry?~Eppbf_;b6BE zOktEbVcvuifT0ZJt9J%(2=1`nXgKiO7eT%1he8ovVXQw`YGw(NVbRR>jfz9JLxZ_c zYRC>;*R&>dpF?N)MR2f+vK$vWC~n z4b#0kujQO#R{h2um=y#YZ(q(~J09^bdBA#bp-tWp0cDmls>TMCtg=0SK+_&vXrET2 zRnDZ2m7dU13~=W_L%s5chW+@YsE*Xs#aDCKL}#KKTn!wkGd3tJ7Hz##6j5OS4EoWo zGjuB)Y&4O=8dgwn*!P)hsJ_vQ4r;~E;RH$ZhC?eHim9#AYAadfs3m{E9o$8?9zn%Y zswIVj5w5-o-lUjCVTGvF3$%i+J9IlJQ=tAd?2si>{E#KwTGBj1v!{)bu6cx7?mEY& z`$e{R03f&o{9;>xV66E14puzqX)Npa;vq+yRX@+q>wUvr7Ppk#`0n+jwIoV1mZV^S z*&<8f5QG9jFzQAIAj3O}(%!?UA$bp6Y0o~JWmF#p5EozAp(1I4n(mV-G@I3KlVT&s zdtlQadJl~+PrQ2n@-ILA^SQn*YgWE{1AYJ!w0UHbQaY#`S%`K{>12*DhSS{6d2*e(%kHD;5cZ|`x(>Cl$p$iPJ(UsS<>;xHwy16jWJzDx!K`3C zm02N6PlX*~gX!W+J0PsXfaw`TH^NY0K@Y7*2Ex!FeR=VP9fl3;Ak}P;;~+I`1h;H7 zBXD{JFD9ik(7+2&ga*`g4$<)!J%mkSL-*OmS9TayFpi_{3R-2(&x;uK1Yg(zCVxW0 zQzW*KV4KS<9Kt&pVvnQ#LIVWU`t5do?Isdt8uX;_3IvR@-i_7L=k>>&9} zu*2HfZS_IHsbLgEcb0C7?tD4uY8*Amam zI2gkhCT|t}NYD-+2Y;S(h>5znLhk}jya>B=@l_sbj}e1USi1BmvF70u2JrWZ54{U_7P)VX;QE_af{?=h=5C_M?q?Ufw9zu*GE_-2&vze|5(sdj~ztf4zSN zVQ^emI&Y$y4FJn8MABgcv0=@9d8fz_q(3TeL~Aetc)-?27FOJ51bA>rnNtLY7mKSF z7{W$y=a0KsT^KhK9~P>HVFR6-P&;hJFv@qvMmTnjX_RFDS@7xx@@Rxiqi zo-x^3EuWE_{o+g4=rmadu-=lW`Lvoj78E!{tbX#PYeWQg01+DDK`M}VP?lM&-bGaM zP8z^Y@nG{<>#b}ao1?da4lCAM?Jh!c7{SI)eJDMVZpo|g&^8odE4X#!XtTQ&Mw>kk zSk|)wk0Ry^zJ`ri!6GEu9kkbq4QWDT&Dd;y_`*j)*`1}8@V2?a2&+k5~}L{dQH0K%9%FoRmB7(%{ity*x5Mg?~SIZV)F z0C3~@8O z5wQUz56%lVA7ri&bwb^!3g$$Z+~#0F~G3=;*&S7xwR z5w?R{J(g~V9wLVw<_VE6>|n1VYzDu2+$7ZwmnIts4u)#Bb42pae2q_{~=xJhDVVX4Y!U(-)2zr8FJ_7r! zjNsdAV2Lq!&r+OVvXcm|C7|W-JOQ9v(>v@3m|F)x<~YE_1Tj6Q;tSUp4D>{VVP_n@ z5goR+%w|imP66q7@gSbaSZ}rxf#T5@u0gV=y$Hod(Ztz{$fB1Ve-plPjd{a!HLCO`P-Gwdzx7fP!>bEbek#3)JA4!56Ht?)sDw7{A$=L2`(7iY)P> zkFa(sI%M!AYv92mJBqyGxgwIf`o*?8$)@rL(u7+;7H&iuzzr+VhjX*skS>xUxQ{6U z!wYX{H3L?-KTD)&@YQN8Fq}Fa!EY28E&-$AE#RzV;*nDywZwLGmp^~((edm{35$*s zEhS#~f<=a)72LJ8xx!e|S&qoEq>E+=U!-PeqW0TkiN)%KD32AQSGT@I&9H-&4BZY{ zDu%EX{MjSTloWDDXn3rUCo+W1;LaW^I6r1=^pxQIB+f$el#a%Yn$_hVd2Dgr^>}Zhaua|M2J zsg_8SE5gD}o3B!%@Ca2V-(2{wm8{8U{34G(R5$@^fcb3mWom{Ytc+rPD{ZqpWfXMz zV15FKpA9i+Cug8e^Ysv8XwRHlSBR)SN#=~+k!J`KWG2#vKB}um#1(EYSmm8KU+8k;D+ulS3{r7L-5*c2HHtiOIxyvvn3VNc^=?9XpR= z<@lE0K5$`ZVzGhXk(3b3>6V!pT{>q&+z9Sm(p*8Ipn?uHSJ;@s+*3<3v~e7;zz}YF z^QCK6j%h9lQZR5hsHEBA0e;eF#0c*BdrV5F0+mS#u%>Rlc+E;kI8{M&Z^-HjMrr;` zWr*;Y+E=eZ4G$1G0YMN4VSqSGMaicv_3AwZ6zbL9q6f)!V^F;vfdP!KU!xGg{}$hz ztK>$ojc;&yruAkHON7qUH($F(P`Cwf2wbV~p5YJZF(_Z?q0|iSP>{DO5qT?>$WGc6 zB5G;FSs^A{!dQUQWirHxAtF^2`Oq_jP%lJ=5g~Xd+Gizs7RTrQ843>WtxW@h(T25B z5{Trs?O&I+=}%EgMmc0RU$myMyIkQ>v|@5&9>sL7Rg>E_b3xs!$+#W-Y7#;?WX^a4 z0*~K~35`{cp2Sk8M?44(6(fY(e8n090f*|^Ux_>Q_*liA$#OSKj9j7(7TYN&K!TR8 zHzkwx7AvoUI+f3YQ%{eF!X0A0g|An0>nT86pa?0aFe@Z!ws{lb*1}h;x#6=AV}S+9 zwJ?29MMcY95^$R+JQPu92JWv!|LD97%bNBwENix|dwxOjDA6=u%dAaBE4LOlDBu;{zI*Iznpk zVeW0-v+0kU!JkWN(T_7&a4=_RX>*&on0!WZK{^L<8#y5oy;-Occ@nCjpzTL7_$ykF zX=*54Zpt4UwRrb+^o7%~!u5Y<9X@+Z@oiPyX46Ko+#7$Ki) z5#NO}=Piv?43oqqqLtJ;6&seA{R~a@gRF*2L6vPkVCALKt2VC4S>vN6;uVygqaRgS~zMxHklKldFEsvn# zqQvB+g-y)s+PE^qezu z&Gs#C+z#%5(!9Y2l(I(war$g8rtdFshuB_^*@{y;aD(^tYH0GJrz096{ia z477T2S8#7|x)toO-K?O=R00OpxDnhsQt9!65Mt~#!Gb&sUxGsd42bU1E`B1oYG3na zg{S^cgb%u0JfN1#5*dPA-5XZ~PKv17WAd$c)_CkRKlbhp${FV|Gc>A`uLg(pzTl0y zg2hLU{dJs|B|Ju5!PmR#Mi}AoF+!H;7`B01J3=b-b=U4n98r`BD+(WbyE1?zYU(Q_ zLzUBChUg`IwHpfyw!N~j@NBPUzQ*brP+@qzFL<-U=^!kaRgbK3w9!mcJjTGY+z&V@ zV!VmYAh(Lpp9VV%gIwGd{Oa*37e+JngvTVkR5&`iwd05iENV_LU?VDMK~bh>K0!n_ z_#WIDq;5+dzIr&QDKT+`?FR3nfy;HzyryYfA5pqA?(?0XU`DXWXhz5q8lzV5m2QR= z`h;azfkJX_>4FaO2%EtjJenD-bYxY51d>*GIXdgG75v(9VQUL?+MdLU6BOxLB&im( zwFKu8z(sOR<%9;db&S{^BxD=8!Qn4nhdgEg zd=}85A!qQ5N3((&s`VMawkz%&q`S-KzJKl!<{Mw*2FBfE5wr!(RIysD#0r?8_d zANC~Dj31-dsnr*`F?ZO||Js3ptPl^cJfscxxKf13px!o=vEH*__ZS{Wu*R}GLYBTV><8}P(c%Fdd8Elxen4oXpJIKvPCs-D zyMa4-MC1gY|2~LE(g4mxea->n(0v&Y@jU<$7gEP5n#2|7HO&(mWwe-5555JA5oKe;x4p=n|U}=|gAQTHdp{JEkT) zdN#IfBqALpYDHdDkP$?z`^=h(PK&Te5{V~HvSWisrY8|2@GdX}C;HTo+EapfI;ZX* zT02QExqsHcsHyrwHex0?PI002_kbCmF?5y(5xzC7zK{+449^)=-`)?Zf;2_Lx-oRy z0<3VALm{VVkR>FtZX$4~2eQLtRgot`M9DL&uVynW0S=hX1AQ>$36wmzw5wAqx%42h zo{VD807{AGZvPVtHC5=Obu952-JsWcd-Oj);TUgxb zx=hs-+yx2Z15%k0rA)XXy+=_bAl$z9b4OE1m{qE9NouL8;R`>JTI!9YO&d+y_@z%I zlT2Tt1Iag9HqeM_&C5HWhLXAwYIu#)Lq$}EHvvC1MO1*fqC!#P6c5EL(fe-rR2vgk zo>Z}MKT!kRne~mP&9krRjTSd$Fo5O3Nr#_>TS>|pZV^tK6-)#!O^8IDfOa#0Z-746 zdlnn*CYJ*rU>RsKxHXef0ca_ZAL5psIcc`4TD@klhVn&ih9Pui#m;6Jf?YT4IWrqV z?WXJ?RA2z&;LF-*M6K!p9+zhh0rB<%8jkfo^bON@S+WBc*^${CU((4J!w!x8iuIM* zfrr9L#mxRN9tO6EU|xX#dWK+^d4Zh=`*KOAfd&6cHAyeN!Ztjy9olMp3lb$(U(#ly zqY_}asY6mnq5LEo_7Bz8*D^Z{j7Jf>z2s~8u7Kth)deUYlp=L{cQbe3Gf2t1%xneo zg%C|a#m(IZ@mPT&Bp@F)Io%3Hr!`Zn;k85&UFET)b&WtHL0Zs*VBA7j0P>RMIYjv~ zC3Wc9;Bf?{M#%MW1jbL8O>;?P*C!5*Plkwkf-hsUX=%mE)A9q9!VAMl@&iP8GmodA zm~g}n&Dc7GTtTxYfT6<`hzU!ADmv^6?rOUN2ff&4!*_OxNK(2C7nkf8<7q51@q=i- zAa18%FTrbf0>j1}78umXlZ{dq;f^eMlDTxSmtYIy+ClWv>j&|1*+~+DLu%L{-PAgD zbNR#uZFHHr1bQl8!e-+^h-SMr*%I~PK@=v`EX9PPpZHxow};e^vBK$%e$63X?Ym2? z-yb_OxP={Xjv|`q5G!oJ|vk4=+=(7-fyzc|PuUC>t6 z5445PYvyT-MTQ_%C_7o#8Ft2RthI(Spgmt0Ok{BeMMzRbgbJ|w+BFsxW~9Ptq>WH3JZGBqlWME^&oytzDGp=qFx$dM=? zC2{Hf1)?LrVW+KJS9MZ20s89VGGQ~p(_llzB7Z+-E*VrYAb1!n9esHkjw~Czsg+IO zfz|`aG$nTuZ#ZJAEY(-0VJpi9pC`~WgorUujt*Lmm|Op{?IrMqXkc*5NAZWRIxZqS z-rVtn;^4+aFC@dxwn>0uhlql<(4AgrpkQM9+*Wn!Df*+xV+WS_>lUDSqJ4Jl04t_U zMW*05pQr%&+0N#Qp6b45j*zWpAYTSUq+35C;zprJez52O1sL>ay@H&&zkZ@a4MzvJ zg6y@^lPLgp^x7#&rih;E`^q$6YoWG!_7LhN5PRg3hDfPyV&>MeGY5w&NMDv_Jz=k5 zOZ7C^tCp6G;|U7KIz6VPT@*#HihNC)0{P#8H?(mJ9cHAA4VuvW>W{I=S5v_W%?H@nm5iBS=(IkqRiLf%HE9aP^zo;iVoK`HSgK8r(!pQ)*~H3#1L)~Sqi^<`b(=Dq%D6npg2R?W#-*PpFG9mgIh!j z|0+62n?h=3bI@mJnL-8$bStA2r=^f`8BfD<=EfRRoS8feE;eT_BA;i$+A@5K>nqck zJJXza>XMPrTG7u{sd~uqwzr`sI$r_p!Aa^NKqW}lmDe4VwC266-5DjAdfhCZ7 zIs;BT92tCdnvHek0T)njt}HNs4qi|KoxYG1zC}NgTV!9J#=K#G#jS400Tz$DgIh&P zBAOfI55OGTD#ik}*_0amWBQ8eyKSrxMTGL@occ)uBn#ao2?IuH36ik2;-ZFQ!)rOF zN6pI)3>_jPS@w~h9r{k+V&o1X*sV!aju;tT<1%%3jGz@hS5zYLs1SV!JubeVB&d1f zW3Xy)ciFblxAUZ9g;&;ksQ@dH4(BxAVfn;_%|jL;^sf6}qzW${9dZSPzrAW< z1=pk-fsrC$DiO(1j$TT5r;Y2|Eg)pJ`|33ICR8f|d$Yih+oHlq5oU!XXSeaV;Nu{>E2w}*4HwuO8eaU#QsSnan0*Etw}M+c3hE>& zV2h5X|C%H^234V%D|%6ZnRC;tDxxo1xHlG)u?#%(4SuV`7X*&_qP*Vx8(=Nmea0WUbT zm3oRMF=Li?gPY>=7{FTyA1=hZZ-eC|fEIKo4){F8)v^;Ifb~H7HZ{(xB?-2*ET;l^ zx7}B$!55kx8b(?e4o&8DRl}NwYnq9xCWFZb*~E8WqsHC@ox=`U@CUErEI@kz2U9jo zzp;(K3b%SRE9e-T#f2FGrMRz0nSSOLoy70HG>!Gu$`QIq*R>r?%5A`KZ!>SF`s6V- zW${&MtdDqdgmn>F8tZ5q;S16X8+7_??x>EvUmT{Gy@t);7LR5I+f11=B-u<68eu?OVLQeu5eXh^9AuVY(S!O%oo_)2YtaU z9?b|(a5NJn3660axKqbrgB~1*6_QLH<7RNDj%EfHQ|H2lpScr=Hx^UEbCj9;i04Eg z{2<)AQCBhwj>drb=p3@8Ou~R!Vv?tH23}8T=VX>Bn?jMME?{~x@P_Mx3Kg0XP@w_F z=30UvaDj;*mY|>5pmWG%2^MuFAS+yZ}s2T$QD{rD{}XJLty%nGfhz&n24Z zt6=gyc`})$m;@%DCV9pKCTMSG9Jv9Mzv(7Opd6Qhfdux$WIj!pP}+zFOka@3EMZ|{ zWBS7WHcOaLh)}4AOlBD+crNk4<*m`u?IHXnN&2j4&;3S@<}tz3W_1H(>9WEWa7)U5 zGhxsUvq6>~D{KU}nzX|a%Mzk|lM+XgED<(>yUC`50Mt-Nu3Q<;uS=RnQ$-||Lx@6q z_w{H*-*Zw}SY#|kavb`c;ouY_#70oQ9E};FkD$5D66 zb~H1LegY(DN!4rzAA~=TBq{)P`rc@Ba>*zoO$EJt?w^s4%9o;%FUSg3Jr_j6Z^$9j zG}+wMMb7F(=jPPTD?6&ng44{P(MKnlz_r3Dq|US>$Tr@@w{`o{kU!u4@|Q2a{QC26fBWw*>;7kz zmTxefeG%K)^6~AbZ|nM{fyVT=$Cn0>_+S5~uj3%y_48zwr0f{S^|wF1{PgXY|NP_Q z--$ci?G|1@cZMD%kJ+8UUPOEa#I|#!pno=m1R_b+<_{aN!Uau(EdIcNyCS+CT739o z4)!uezU(g}O}<>2b4NB|zL>)>gFOs0!|V^k$_4^u2!rj*IiRG=vWNJ<6@plSHmasg zP&2mXbFf9dN|{gLz`MBchb!a|ZWBK&aYE&f92l%>8=#s4HupJbXx|!wtAF@g-qXkzH3c0l(nK&H`YYfo$`g8S242BjOEiF)7l~m7s_?B2XeoM``0J zB={_XjEUQdmMfx{-iKRA4jc3W5_u1hWm($nunqhgvird(BD)`CDI%j*@UTcT-+(C$woGD3ONTiDuj*T?tdz5l7s_7K<}$oj^8=dIcLw3JtV{zqP~`c*}y8T>M` zXNu8I^h}YXojAx55MmsWLBks@2j0X+gtXZcJd@1q(~O>2mDkT2pskl?tdl6p%5Z_D z@WGJbL_~61m^|^06e_@(&D>tL5E&%m-Pz4LRsc`*H969R$%-pR>gA%c`il}==C`Ana2pPTB~;c|A`EJM1_9$ zbsU5%ym>Pxn7W0!QT*hE(g96&Tw(gkiE(srG!II_^M!7PgYuWTXva34eXUK zJd^n!FoRn_>U$rpn;%lQWUm@HZIwsNV+6Nqh+Dz0AYrawbR;t}*#AYKAfSzpwFUbQVJPUiLERx83G%Mf@-nL?5NjUmcWX|_X# zaI49J$_tcJvIEqE)??-|!?}ExGjiGLD?2Dlw%K&cnl#(6hhUnC62>ck(7OnCnZB?? zD-}S7${AL4Ep+i+c^pZ+0S?X(#KV_$*qFfHLIA3qCRA?fwOMGla=K1Em~Q2CQ14~G z-72!`E`@_e*f4-Qf$-o!V_`g>rOQ;St;XVgqBwkMhe|@YF@UCUH_VHb>m@lyjv1UT zT!YFEhp+5Vxp2Nt90GFMN|#xY%EL+c%))z{VGj2yKpONZbjx=5+73D#&@*UrIGU>} z8h6Me=IJY5SwU$+k0LgLItYMHk2%c1(r#Jq?`B|9UuTe(ePx9fA+bSdX>~Am4j4g^ z;bJ~-zK|t0V1`h>Jm?%fGk_Fc=&f{1vjUB*4IoRvL=K5F_=(3)78t@bF&Z~|mf)yr zh1p-0xacl-h7KF&z5(l1+JWM+LZG26Db91$w3y3kItO3v)Kdp@ z2Z(Cd2vaRs$B-c?Dl28gFhq`loQj7{C1vDk_<9fOC*%_9vc&CgmS|m8nSs#!WeG9f zMpT~u@MRx%5KkLvLtXYtMZL4$gu{)`DHd63vw}sXFZ-|;hW6Ul^zF?~r*%mnrvRX= zQ@cb96WAe`HGJ6zRHtdVWe4G=HHz9Bx0GN(%M@JyFQ10&01oJ6G1X zYRy)6hz-PK#db*?f)lijtPz+%_}UL<2908v-+!1HvW#M|Lkuy>C7J6E{Q6I(`g#`| ze{LH)3}7=!;-uU`KY_C&8igueMEUL~rhozpcazT4bV zM+Y#3&)#zhMXR4kl%Pg*rV(a{w#NWIYldhQ_S6GgyWoZ}4oiew!kt!HL3ObFUdS<4 zP-Pj^{Q$E1;MC0X2@VtK%LJgj(BHIUgOhp0)HFCPMliFb(})BJzp8v}Fcw?m;35Zn z@`_0fa*_Vd5d^AkaAI3(*h*v) z--zepoG%lD?peL6dJ@HXr7SeIFZq0&==hLk)b5NdoBHSuMEqHPLhNsWz$J?m^fAR z^#$q=5%Gt^7k{V}PSRMixL}Z=ByjM@lBV-4FyU1v_0 z-5JnCiD*b2^aoBSF`S{t$Keb);v)+SW^s`w8#ttEh8`O1n7=zij$3~AHi)Z5wACmC zBWBPszZ?fBIT4foBF8a5cG!>4J>(AVJknl;B}{u2X%Z&y0(^aBb>mAxSdcuPgZ}4L zWVz?pI`bx51V#<4Cj-$zhwMU#S>cd{L{aZz``jZFIKG`@2(7d!CaIo#H7x)P)kd0X zI*KI*Ancie+w~2oj1fGH4R;E@IBXEDpnO3H6!^Hl(irk#z}f3YgUKi+N!G*;EVx5K z(w#(_9Y)sFRT>N4ELpRB?$_rYnLzoP5Tngx^d@5R(Pqxhnf3rkqfO=;NeHlUoF&p- zJ#qyr)0RyrAh!PV%N2xe;Fgg{6rxy7TDUmD;mUbEQ7IsnDFc(X60NEFnL|zjY`r}V}l?@ z*cY)GVjZ1TmdY8;3c^B;K{PvwOFxlGG(Eu^e|T66p4F|riY$}p_`C3RY>*;o@8TIm z7oZG6nP-_qV};5nQ9>nYCc;K=>qj$!H4|oqG|fcV3~mWI%+Q;OVTNf& z41O~awt`ziGF>V+w`eDY^rFi<&7qk;p5UHPyqdVP6sDt=C@`AQfs-UOc2{y_HPfJO zJ5p~utyPjM?XVSm6&vkn7gB?c1t_9F%nDlRA9Hh?w5E!7B1UckmruYMr0!Pfu7(*H zL*G-YRM=7`cr4({*zEBAltv2(^p6F$nW0@1)tN`I<_v2UrXx~1`7$=6knWw8m5@m~ ztMXZIipT(@!`HAu*oqR8)L?5m1x*n`<)|Q!;3U?Uuo*_^<7k@egFDnL6=c{5ZUt#Z zunKbRR!CDphRxtski!f%jy5wSsUX8va4X0S*y|#|Bj5S}Bdwc#aU==pb55ToqV%B$ zaA&7xFe4cH(TtE~=ESIlTp3A~zJ3kF%G`{ognVXiVic7IU>-Zf)^%^>YfcU-8^-T> z5(QAlj!0^bCn0j>=p8oQg7YfO8?0j3)L<~QoTp-Vbyue5G8!Iz`I=z^3y=LpC z4czs$*~|S1ZYrM zIbp#pVQWjXM2@xP581)3A2+GUKUtwkXd8n|3P?0&K;etcN}#XwrE6?)X_#pA??Aqh zC30Y*F~Y7LZiH{!m#^7idu!+Cw!uZZL!jU!a|X_cc8-Yx{@@GP$PAY@R2|h|q+e;m zLvWoB;eZz;yEBYeAxc02-?F@lvT|V;aR#@9G-nuD6Of09@DgQBrQ*96cnNNre9$QivI>A9YceS12KNk~) zRRyO+NgAu@=F*p~88+x?qEI#=0!iA)KvNO6fm=bE4Q$+EevoC{61IXnfgD!ob69zT zq;=V7~vQHw10Ah+ojqDJdH|UPiE1n7iIF632zZ_DzNghTF z-9fzbjd1ikD%89X-ER7*;_}JSUW%y$fbjeF^7M6U3TzCr)W{YLX-_RLvt)}Pv_s#? zc@i761RGF}O~T@04wm9sBShpZKf}>!7%h&zqi2XJb`CJa+YS{bz;Zax5IKW*Ui{+c zF##OXEhQni3hggXU$kc9@brQ#7cyxOKDYydcPq8zcJt4tzQcZwtH8|jS70DZD)hX; z>`=*&uxw%n4%BHZAUlI?K7}EIx*X&}&k(f|5A+$Hm0<96xBere^%NQGp zb0ODe$rxcPxcAu`?*NR;@CBx`Hv`=?zCamO8Aa8|Eb5Iy4XgbNm$6VOt)9~4Ir4Oy z;ZZd;f;W%qrWgYV)s&>qEM){v5#HjwQ>%Gmr8>-_kqqLo%Z_L)gpqR}Besq+93$MS z(jujGoE9mwbev%;xU)$Hw33X*1^SSdJG>j&9)->nqmira~lvBUx1XZV7uON)}O3uk(#+ z4T>DKEi3x+0>I#cFkG4zqsC}0PG7c$F&qpp8pVN`Mb1kcqKlSVloR_D9HOsQo6nq9 z^QcO;sG)S>){{Vs*ki)%0i(eIj9LT_S64b)w;4!fUztKhHN0p+2#Bv-gB~|D>#S{b zWdemJesQVu7H;gmXP(=4E1aJJT>VLf>~xykv^9?9_9{4B=9Y&h{E3_UGXEL}XE;6s zH&~}HTGM?&qo*4<%qrCy;p=sqI5qS7LwOT^5x!{63Pf_{Sgp-yaRRZ9qr}YTOHX9x zC-HFJFry9D)4phpzWBjCfnEi|46C@&^ht{9^KbM-9J#UE+yZ>7vDK;`h zOBDTGV3+%tIz|qM2vre=1y5iF7$<)K z_kn6ML=Wwmq;Q9zg7)c))`(EI8KGwjS>d3NY=`VKPzJuLo}Oe8cLleI)UAy$lSD)& zv5zlc1$E!Nm6>xzE1#qtO+_8iaypr`Id4LjCIDe}+Nxc4&>1_O<}Nt(HZuWGn@}&& zV%pcPIobDb3@t}X(Yq^gDkcbzHEIQ4yoNgA)abTSMMLHNp=Cvr6W61d8Xxzh%laAX zDSYu7vx4;$W`(EIPEbZh2M&~mPFw)`Q>7a|nhFRdcP`YjG!^kT;SMChfo;kQY-@JX z#i}Dk8qL~ziDK!K8-ey&(PejPv^5@h8`v*}R&Y!-njehg6kGG-RK}}gX-`v}68ewDp z5lm~cqK%d>Y$FE{g0v6pE3nCf_^E+?nR`^-IxXhZp@F@xY9k}C@6(93c>||uOraDm zGB`un25w2&9bp6p8Vrb5dtp8O0EQ45++X>MHg=S@QChBoluM>S@sWCBD^pk2BULC< z1Tgl#s*NRzCoo`$o+#QTZ=2a49y>(4Zn`KZT~>g^^yQ;)xY*gixm#g7U<8q)a9}96 z1w_U5y*x|w1HE>dMys(yw|ci@6iyJ<`3q}qk%F1RFC__!V4!-w#0(S;$UU`uxlSQ% z^J-50Jc}y``*FVB`fn{EXccC{CUFN;fw~+%N?_iVaoxMe2|CeM^ zjUj@|-8cF+AmTKmWgsO)xmkg$ERO!CE~r}>i5WDD31m%Q-(~|myMvI?Dz89`v(bZ} zyd{+n<(heKF%Eb`YDXuJ-ANX0d!q)scNOAjKgDs-x{9guX5Zh$as^Wr&kBTsQ%<10 zF5kZBZuJ>WWmxQ%C73aNaU1gmYb?wc+QeZ7mGM^z^BFjVS9!pJJzvoNhV$Nru0KDB z4PVbTZ(wkLP8}bgxMv1Y&uNU z2&%8sm_;{!;;Gi_ox zF=((5Urhjfg5%eV8(@#1zO*=3Yx z>9oWPPRIqDCF;yCV^oo0S&!3Ky7_*esLdvYH3ONOJSr!l2%R`cd^bhat~=xuZaKM8 zLhS9@2H2o%1|2d#Y_fPeF}SNNM+G1(=2iC5#sI zHE*~j;0#UUNZoM@9mi*?t2C#YwB14c15wa*h{q7d5VT8sy`_#E3_({^P4)CuZ{z?s ziK5*a=wW~ZIs2_a3O*Rk<)Y=auX_V(l>HTZ3$9KImH5tpO4Q|w6-=lm4wro(6`RBO zsyD+3omEdpxEN1mx!%Pc)OZFG2EDWwAJDlW94K#`@&%jG;tMd+&hHCRv>c0wH&$3# zgzpd=2raf#WgY;8c2;{7C<)APf0hV$JI_2|25}0mK&W;=bEo(=Az7^3%(Ivt8TRdS zkCca`Pw(b484XlXVB0#0egMa87^P= zX4Q!S%e#7Tlf{C&A?&r$%#kNFp^X(!Vr%e1gV}*R!8Z3}RV~Q^FiD=k4#Dc_@^x?I z4~KBn797UuNs{1zL&PX(FJJg(Jd8er8V@6hoBnhR*J?Ax z3`#G9zXA}L%U8cyb)qXsz2yrWp#jQgPZzDAR-oK?7+Zh^=JNG#%n&0)bVFn*A7ly5 z;{w=GU08WfqrrZ(zv`6~lqe#WN>oXOrg4|AfJ2+f*VaO_n_)1)B*_^%LkgX3UcLm5 z3;~yI+gnHiXQikTy?2(qg}7j1!v_f-c@Lu-V&n{%VNSZuEIH$d?kz4~0%w(cB9CXh zj0;dX*PDp~M0js;`9e5WSdR2*h2`|^*2tf+mEMKmy7M%48jOpnY-4>8tsaahw41H0 zC@BVW@jAlS!qH^*fWlqd-j&+RERll+gu4qS2>e7UOQtK1ODJ95*h(usu^j*fIYG^} z!TnNZ#)4=>xkm9~+Ti=q)09z#EE;?3p*C5_^NF20)+8g{Ntpt7ZG>StM&GdWi**UVV^4wh@}ro7_) z#$Tc)jwy7#tnjFdlZAaS4sw%+_)HR6roTX83=unLzKXl+HZxdZJjlL=E?)u1j9`xfKiJ%$W zI?|lMl)~&qWZCP5pM*P_Uv$l@Idu|UtP%r%T)ym$8Nmin8^z=yrHdQ` zDBw`B=;-U-m=%T+Gp?Y9Gm#Q=& zBx$F_MVn@a7d68h(Hk?vaE8YWNiyadwt_o}l#s{%Cm409v{?j`9OD)uL#%l61#ip< zBQ8|2WU;?WQb~uc;FgdU9c}U^%@@Um9Ig<54}JyN-@^!wyoXJz%p^0X_=|8clJ+8O z=2ZHNND>_5R&WQ91%%|%;s8Syu5$~(z)SShIpP9OBE*h$@yMO3sldz*d!ij)v9|lV zsEAGis1Ki}UT=hM%nTh+UpAJw%o%drUEvF`TSUk$E???K3cGp(MbyiPhDmI>0)znS z12gjsbhF3*`P;Gl^>4>dfB&z)|Ks=He*O8U-~akQfBZ*VWk-u9U+Bhcu+l#*CqA%2 zTMzVUy(OMG0|Cw}8~m6N+{$qn0j*&XzQ_@7E?HL|#1wnmmp1q@Be>h^Mo-UXw}20= z;s`@#cUPw1NcEciF))g+jGOkz5}ykqCq;;6d1cNrf&%M^le?T8K|yxVPvA!R@?~y@ zI}i{q;ilo7)g_{+6(K2~0N(QIQ3&{k4KAi`SKwq_`pKR@P%d6P2l#axQ&<&&0W5;@ zk7J$vT|~@ERxk1uUAj9?D}oY%$HHphdYW&$`L z3JdPs(NZO#9t4J|qKiF@PP7=oFDWDWcV! zf{^`{5fmE%M#pGXFJI3FX)6$ZoF(_FX#?iQxHd`A6g^b;m2Aug_8c@+*vJk^oZJ&c=;e#qC^jxch0@dH27|@I z4NZ{w%uP(Aq~HXO4P8F>plk^(kRV-`W=SI+J3xfNrZC78ith>#uJV$w2(&|Mn~ zOUoFV&m%HDag69@h(=0x0(sI@Z%iO9Z^}Cs4)K&Xmnqh^FC(Re%Ag&5Asg{yZiv2F z-YjhIZniZoajrARhbM7{%Lf_%J*;59JRG;Mx`GmX6wye=| zXJZ$BvPP2J8SXNC1sf5!D0Si%7e=^%w_F?;X`{PKlsbW+yPpK7*sc!|JQ1O6m2LzqI=*R9bg zQX)o0tTjT28vu^g_RL|SJ42?*a#^IG%%eb;0{9zVAgd&K6p$$(dl#~U zTSi)%fL)n;L%hM#gv5lWphSEX96B^Mb~fP4)=)P~8dGJHPO>ZAm8kpYDVtu|KyO0$ zHwDQE_RE*8saoi=_ZFl<#N?HFvLS@UceT#I{r1KVVTgmTTT@AptC~uNkvG+X-UOE= zd?t||^VhpUH5fs^u2FEUa-SOj1-XzaWjb>7)Qnkw&@X^?ovE0OG9>m52nkM z++E?s2+jhks@d_un#l$in)C{u8Ti@<-1GvxFpNa*PNx%m1oJV4WUdz)WQ8d0{Ov2( zw46ZyX$^%G*H@~_r>&Y{b@R#)t*t82)2iE-t|`Jnf(j9}75a88_wfVZ!Z}_=bSiuM zx;175T_fMB9nA<-*u=^ShLNv)q18@tE4US8QF{qWN|m#?L<@sSu=Fi7oagM>Qln}8 z1nvh^3OEr`kS`$e3}We{Dxnelk8aX6m`zYg%LqXLkbK=Ug!=538%PnNk!B97t2?ER zawTwxD1q?pi`JBG2RQ_#HFoH!B9B7^MQd(fv_@b2){E%L;`=#8h7u~untS_tH8O(0 z5b_4;wKQAkV~XfdW9Rv|FIHnV7{!wrfrPnCF`9psFkA5y_5@$4M()42!J|rP;v=J{ zTi183mY%(%&>lCMCw!qA_O)DEs&Ifuo7!<>%y6_qB14{#9T>etl&o%Fs75;qG5^^> zcHrpgFzY>gzC4)F2S!g2rF%7l?P=N=l9Mdk)8vSbSl}}NAisTen##iRxTupDGaD5UhZT&l0#`V-(9c0w z5x0WB!tM{kjxWs@C?7K1BFC-Z4j^mOb0Z3LegcN{r{JCT8@fCw2p zsJMMe8kqs8^mr9*JE3uO25uv)P`1w!at3z-Ijqp{Fj2c?@`!I_VD!NX`}w&?%4lDZ zM!);ElCUQMg!~raQws79J&ExA(buHeS8%cAdq1H~CO2nk+6kE=a8GixjCJY;yQ{Gx zQuielpvyc(J%J^jiOsvOu=#oWOcr7;Ulyn1+XwQ(=$H}^9icSHB?Ue`ZT)7 zl_ex6zqWf+*7`KuD~waty{VEt|dw){*f zXRaU~bmWf=tGTZ89inLMDW#Hoy@n~Y%a{Wh_^O0Z1)=>-^m_m1z%*b8)l>&GlgM)1 zry!Gv6Vz=6q{Ayie0PZ`B#u)7#}XDQg?G5ADlJsbV~JNeqgjGb8ACi!9ywOP4IVzI zHk{*DSxX!q$l8W;DW+1C){)BEm#q;GRsl>p#xYGJSll;hrgQgaiEQff$c;+Xp+i$`8@b5GQ0Q1YfMhwX0pDB9MF|3^ef1i%fl7p%>cVUw)}!m1 zAxpRoMc4@LmK#Eh#))QVsBw^)0{X#o2zC=>|42;eFd*Hb=^Qfy3aCWo({Eq9#yr7b zOu5F%vejw4l3{*tXkNZx^Zup1SlGI1Q#7CvWAB1=wR7$f&Eb@m8D_|Zzg;R z8?%FjMzceb&?sC(P~PkIRcu7Je$qP`CQg-t6jam9S)%X47qKxj*yPd7kYw^0HiA2O zG$YuUWf&m|;3sSazj*8~!g8fqVV+$1YH9fZ$ir4}Ye%(@uqU@#kqFY>g>Fl51&CB9 z*yg_~7@o~-uyo`bIR(S8K`$MPu5kgtB`F=FplM&b#*8qY1E?o12qwCxc;yKXx+Urf zZs{l^)bj91OE6pI5-NDGz%hNB`89MKgoqJuU$Dj;K`^(fX{!#D6%v3$P7U^Ipq8i` z{Au3!ZQZ^!7K?9x`OBAIe*O8kzy0?YmT-TX#eLh(>kB^wk#9eJTh}j*07ZX$d;wt3 zKmGH+>FYae2QJ15BMJt%TKSOL=FSZGfMkvnx0xgE6LO=g9$(ynOFm}%m9H)E-2GA6 z|E=6?03Sl;Vg~s2t8nw&Xp}I^9#6>0J;M%~+3?DS9TqTTQ^5Z;KfitLlj1(b3?v0NrGDLK0;Y&JfEWWwL;z9J_c!8*;QOgq?V=em|L5J=LJxz8$ zNRlPPj^LM#j|r@+uJA{RJ}XUE9k#-s#uERR;<;O)cTvL%Il8Er4LF`yypDQ>hL`e4 zzj@ttixLB^lj9^XOf8D`v=6IRew-@?xYRI&TR56F{YmG<1-K-4flq-mr={LzMh>cY z#txyQO^o1fd4>^ygIq8QK_>wifa6W-#xivu)oKOI5IM-g3U1wKRyfEI&!c#J;UGsG z`^>)3s^=eY26xc|9%>;xe85iqm_dajr%T)>P0q+XfC&aGWV(ZN1ymmLnw2s;7%T!71(>V zQ~}Ul?32XE9GN0W-;WL43K9Wt7BSxohf+XKLP@Acahx+x+qJAlIyZauj% zfRpkAv^bD%9v>S8!~z1nja}wfNMrmKtEzor2cfN3^_IQWG=FFrV+!)k|ChSA*|j7& zk~H`66n8tDh~sAFe-{LSS~f7N?3z_A3M4Zds2U9Jyv{`Xegx*p9UW5lkn2ZwKmXCK05Mqq?Zf^fwxlvs4#BVH_@nn@UzvNbIHw zjN8}BQz0K5*7isxNg$T=<0|!}L*03BaFa=W?^P-=gh^ScRY+BZH)f_V86#bTgqu>@ z#pr1@sij?vNfPV$y>Jss4J>OPS|P_uuUyDvVhgFbER|{o~4uJsgKXkA5Dyvts;QtteST z(XO8fiQuj?NVq8_k+xS9ijps^++|cR>hLW62Z>e7B`NY=_4>UUZ4mo`kH+#H~f(6EL zAMz~NJfU#)WrH!ncXU{qMs~zO_GwPBK8y{vuv3w~D@zwHcoN*45ih7d&fR$=5T}Ov zBANrWCBb1UsV5bkIZQ7yILD-m;#8J~!-gWvCIa=QF*jW&i9w`RLb%#a?X&Hl?Y;3X zk-^OzNw1+i=~6JMqEUyV0aPf?0}@hu26K-`mcuv>WkbDxdoSM)5N_^>*;;44=s@fp z2pt5KnX?>-<=}E0k=FDZLx+KcLWC5VWh2;-yPqgT;1Nc24>ak0O9y34C&O$TV42~I z#<~o;6AW&ip3SkY{)w|>z3M<5-qEIO9xRSS2SJj z^Z|wD7g`4UEa^jh^~iU1SYUwBzWNRXGmE`%-As~4Ej7Acz>=YtL$Gqvkn*s%^PcO@ z@^+#RNlP}8L@>p7fH+C09@ObTh~sgb@|Aha?vHG-YP&I{3y827)zY z?IftY$kC8}EVixb5QM>PM&2-tsBptDM^p%32tRj(`KBG+ODB>=BM77OC|YPoH)MN5 zTyQf-nP{@3Zr%BXBwDo=O*&5ZyDm<^Hc6XFa`27TkuP|PGQO6LOEa+cS5EK2G= z+!Y$|0D55q6ll5l{tkDm!TOPmZ(e9dPV!JhTMaNiz0*J>l+e)~9vE99Sv}1=5qCBq zKDZgA$UQqOb1SJT(&f9T(^IHsvUL@FFx?FD27&2aK}}c-nzSal6Ra!er;kTLcw_D` zPJ-PDI|(9c}yew z6!Z98$)9Q+0=eZV@dr?Cd!<}kNvY@elsc+t2Og67h&d@Y3 zW^fJhjoHH(+u4(8W4mdd%xE^<%^T$G@P|Kq`|rAmR#MoLdBZTrlNkoV&mrkCy~Ohv zH$wXaYv*}Pkwa2sSN(}p+K1bTEHt6do;zHj;a;SeBbnA71?5$XJQkFUPVyii5LfQW zgP34N{_Gm+Pm(+c3H=Ej2{)ZIByQ>t%Xcf1^s`WZK;dAw=7@A(eE%BR0#(Fb?kE98 zk^%87?*XBO;X-*) zRbI^VU<9edhu@QgJTDyX=K@}N5fN+>rsx)?4ioU4^Ki#VZbg+(1+=i29uHdgXKFFY zYh>{X>cqXVv7saVVw<4x+t*Vmzlsg2M?>PGY#9Qi&kA%(@SRHK#Za2x7%1H@=rmrAuGw$iW*%cp7yy>0uEZw2y^ZcjB{P zmm(S(+$2&kob+SuC{(sG@kX5ED1;ZUd@~!n4o!0OZ09y9?~*+=Ent-lVpvb{vA>q_ z3*qLFjog5~Y(rdCDMN$r!L^{GO5zLph{2UIXj&1Bjcy8QaiOtlg96w`#m8h-Y-F;_ z#mFBQ&;tv}fuRiPJrR;gBy-krSdsE*3X2eilta}NRrcHSP>=%)tXwU=txcnZ+G*D@ zyds$-D0BrNev&glkw|l^yWR&`&@x6B;1_l-w zDoy&vn)^I~A$TGD{BeYZ8-)=TW&RIF7>oPL9wx%R3o932yqxLN2O#YPCtzW&L1JeL3k_s>tACB+eGD zwZo{J&GN?0hR)*)kwk-muTCSbqfk>Am-tC^R5WvW<7C4cwo0gqSSBNK81#&{(Ql}X zG^x20ABLVfZZUNi8M`^Lkw$_{@Or+i8==)2CUty<|CB;;`{^tU5D;#N=r*t?;=;fd zMLm88gJeT_Ddpi(mh@%B_PL&)Tc88G_!c$`G8NK|y#oW?2Am2;e9U|$uJ{)>g}yv* z2E$tjE|}&eelq)|2)aSa4A{F9{O(VML_&jnQX2z1^2%vK1A|N0EdgHa;@j3RwC#t% z?gYabaJaJRdS=)~eTog&fF}M@xIIWb&N}IGRlRjkPJ8{353({W! zaC&oJn_YQ?rBe@y(4h3VZS~IWHT<=75i2}Rr2n@#lYS0^A$2H)dfF&JA z!A&6>WBe6^%zh!rHXFt*1&yDTWuumPSfii-2C`L;Y!3LgHTFuX(X`Wb5E_yI!vIjs z`NW+_gF!nrfyDvevqlSbtuLF_-iKV-(OwZJ*I_tQ*jjl{L218|nwPubBy_g8!2vQ&?bHsD{n$wJZ zB6O4bBC0B^30_Kwfc0~Yuw334)fCuXOfNOrFA$`pz+Q-sRq8IO>+60PP_l{1WiGx= zjfl~_k30(3BhfwoXiCoUNJKgFzDJE=U~hzBFv%Mc2Ek1oZxDDAq}d^&?20AFESof8 z6x_}u>|y!~;{Yh>~4Q-G9@?34LZvVDo&7^f=dFvEsb!{hPLLCXk{{#gV4dp9S&g2EN*sj!hs>! zZuDJgZWtuyMX@2LfgNp=*4RO9spTnpU?`vM#MLh_o8$R&LPOz2#Djz0!X=1WXvUx5 zF^eE-33A+!iT6;TZaBCNcW~&Z;X+}O;84YSjPT=qTN+uGZGdG=mP%n&*t45Mz$aQt z_?|R|ft`k`y%GFAXHm2Yqu{2GHwvS)zEPN@l7PYy%c6Zt8iT?5Z3e?6{kAv?ZU$LM zmtPDiEi53^A}xYR!a{U|3RN2Kk)VXos_l@AE-X^LG_agQE}G;qm_NR8p}A0yIPOBf zr7)6_jr1hqxUX=Vg<8=$3szQIR9MC^VwR{7tt$^dd4#I4siQESh=QV(cy6;an^-9f zJ`sNQXfQCVJ-G|oD#M_N5=*&M;!&-YR6!8j#-l`wbQbJJ*jbq6Mud-oyDVcU-0$SL z5p$%^VGR7l5d#6qHbDVi4s_(PAwr&k&s5s4B}0p$)j#uc|F8e`?XUm#*MI!|Z@>Qh z?eBm3kKg|FfAr>SI4Hcao*_+mAtkMs;?Q!X+$B)|lgQOsGpG z65og)j0EVt?f(|^~-+3Mh^&F`xBc7gANM1qJb-SppEkaf_YIazklSIh) zbO!pOQ&(1e+0<&h4+K(H1sG<@8lswqzV(byKos?(uzC&!9+~LE?2f~rF4Uf3IP>&e zT5t%8)fC@(#&AH40YL`TYFHQ1aAsk>w5C`xeuo1_w-FEM^2T}QWH?DKZBHf~V7@<} zC#fico#^t$d1jz+$A*EzEU_Vsf=heEfvBH_yP_}@W~nH`Ft}MH<^hs_{r+AOA{wLK zdEU#|7{4Ik=e?JrEnrSokS=N{Hb@Tjka!R5hScTd7~;#|HLzgI3_65@TwS4mRBc~x zAB6=w=;DcU>4Cyp*rj_9!E2%K6xgaPjNY8Uu)v8XP5O=lSE3?B&~bQWz7rQ8II7oEeES&^ zOof0OhN6XI=C})uC>l_SL&F6_R3=&Srp{?7bQ>0K0opXj#;MTMa$P>;(RI|!hrw2C zW24*R8_*PV7Z$iYq|IA}BVXWrsT&DV_`u;a@nIM+1Q)I;6WzlA#ciY2U?EaMe=ju9 zuF#M{_0bQJQJLi88_*yV-kF~Y2`8&Yt438~gaQwDCGlzKSp$hvFoX0BXja@dPJGIc zxk5aA>3X;?6x3B;ylRHU5&txhL88Y{BIy&x)=rE>5&G-vcNN8ow420gCP|+-578j$ zW|1Pq%E;>ulDlB&L6TdN`C9ZK84nI_8i^$nGu!P(fzpz8EbV#niPxSvMqV@Ac;4V< zk!m#UBntLaG^P~NHtSEtl0m5DO>hXOk#9_Bn94x;`+gia9%6ryIh@;`d5n7v$O*1R)Zr7q2roPo3>GTQd8;uiegYU;Ue#60 zTvlA^bQ}hMp#~}}FQ&Z|LZOP~!Ar59>9c8)$U+CIY=Pk5yU^gh!fb{w&hF(YYs*3U znI&~#diNPZ!OtV9tz~F-9|t8+NJ#5A9B1Z6Tn~{!6Pn_i&|qK4>Ii+7F}|x5Ri_#b z%xl=%Pnt>5eK{@B%uNwlO7>cSol5FR#$+Gqu{2I3Iv7=gp~~4wsH>_g3J(>T@*jcoah-=rnhqk$;$>I^RugQG zxkw=TSVWW^nPf%41Lg5a5=r3@OC;a;&>(U4V%y9*adsZ`Smf}bc?uBYu~?$^EA$oJ z<1o4}286nEeJ>XvmegxT9f%gQUnw+FoaoT-pxNnCv2Ml=qKxdct*$sZ$1=et%ZU;& z0Or!WIM9OJV0Rb;N^J<*l>L@Wux1q01|`%*K!8BroAUx;@BK*895S;Uto0IFyen^P zXvR@6nygS@5U#v(ioLt=S#Z-xsaokm7;(Jq3J^W5oMIIC-;PFo=~r(-QD)P5ktGQo9Ue?k#Ig@7g6sOw{Ji(lFtGR1Fwh`tg7-2E zf}26!AdJu;8ubh9f>}aC7zHqN`M zOBN`qHJr1hxIVbJ-hA5anaD2zmxiWHbouI>>9`Vu(-C zVG5(*JJH-Ij24r)kZqzCQyd02b2J!iBx5kZ^qFEL6TK3={N9yd6jPzR+`1(gR25ta z=FlyH`WRfHdE-T+ffTDmHm@$4*NYWBhb-nR<~@SNPUt9@c+0{J0?zy->Mba$x6if) zW>UTpjR`{9S*XZlZZv8qISbLQtM5az5Eb{X%rF{`bYG969qbwAQPG6Scc3v0EL+e| zVUQYatARn>F4b*gT42dLME&Gw?UUD7cBEp>SxMYGYnKj>3UTDoS4o+DuPJ zp?4OdAovC}1s#Sa>JHlhT{9Zpj=n?1(0+YoUXvIgED&EKyjPY5y^IMMBvW$djTPx^ zl;0-F8`&Zg_cicLzA>R;gOF=*&*k(yLfM!lIz|aiZ#-xW1S_R@9`SRWW+oLz!A~3q z3ipi|C`@uA!XUW)#ISuJI^KaR^(77t^S^&71zP8g(oc0izVL_t^3%7UmgP&Ahtt1q zU%Cp?fBKtmyc-amCgu9r7O^PhFfQFJeMVw51D$yPx&Gz+)A9O0ax>A=*$v-6|Ksn! zeEX+gfBoBUU%vhH%U^!`_!rn9o5US93E;fi2fW%-i`^Sy5A5b-&%|I0p7bmNukY!G zT)D~XEbJR+0On}J25)8IU}y5Tr%=~?It*j&B-YL)t#Z!74FaNyG=3XDUNmiX)_2=0 z!;0b)1{c3RTB-Au0w8P~6gd_&@2IfB8cQK5`G~>~!tlGm``_b2TcMD%xxOt_q>u~*-;K`nT|8Hp?3khQZAp z<=#@s87+a<7W+XWJx2n4#5Pp*1)Za?s#^s&N81Dg5yCLS*3yBrgzzSJRBfkyx*)xR@(i0H9WETjbHXb(NE)1+^tK_=x1aIW> z(&rh9%Hwl=eXfChWpyUKTY>g6UKJk-i1IhGTd~2eUQ_rRJspN0lu)dwt~)qX{G8y| z6CH>WOnIsX9LzoyE$~gtomfn#;EAHtq^vDpF)2zN@ z!+jR6>iRwklT_Eyv*7zR7zma*Rgq|sv!5n&3d7(gkaCU2nSrdCs+!wlSAuGa!ZLrW zWees`zF)(A7)As3J`7X5l~LMUedpGIIvN8Gt18!uKs$}=Nvg{2Gc@XuD~ab<=_IM>uQ8iIyGCdg7fp2Uq+i_59dgHH5Q%pof1JOzKaiEgcXwHrUt+xfu z<$fGexIFoJbQ}i111YmBo(4_k7!9idDHQOOrqIrODaNF3DbS`$fHwtE);$g@1PRJ1 z^wDr|0SG2FviU(xk^i1Qhiox%+4{z;fnWbQj>EV-?bATB&%E4-LfnaT4^WV zD~oKopUZwR!v4ASjZ*{Hg5kuC14D0=(HPoIf*w-rI5D7G2RoFl?y@ z+5I>~J)Y!7;oJ%i%h#e2?uZT==^n^%Z!eAY(dl_WLa80kwWZ ziV*quWcITVN_;7bmPzdPVh={eKg*GG2WSj;leaO#0-Yc zr_t|^zazf6O~A)lRX*;8_qok zbYMe47lbB5f`aN1g&%~$?LOWxkUU8P=!OAJpkv8UP(8uWpNQ~b@Ld`1uyEaoJ1Wp& z$gyDf1j2O`qG7?eWiSxzMi>gy+=ws?Zt{4;FmA*R!z4E%jDovDBP)(wZRkFo({u)L ztX@o!FXRsk3};R(&H}`@H_i;?jE(uO+RiUj)fNJT92zzb$DV>B{uCLm@QBG<`z(=> zB}tI?TGD>+9FCDUEe*z(8c=EW1)gx|c~fu$o9U|uf{Ab23JpY`rAriKsivMVu(J@W ztx=kYoP?1)+CiA)sSKY4H+j5g*Q2Dw9T!ms&90L)7~Y+P;Qn=m zREV}0V)hJ{Hkl;u9$E`kS1kv=W}5U+_cd!&~qU5BGH^=*)mK2IgEmvH{K|W zz-UJyPhfmH3cZ8~gWzV3rII9b1nwR@0u86;_G#kc69~PQ4324iYX&%>yo{Pic11II zU~C)nagLA>lu)e5b9rIWFc|OS4Z;-XAdG>VF4{S`!y=5og`JsM!eST&H($I_7|q6t zF~OldOKwMsX5&*p=ut6@ftxMfQSpih#e(7-5h0o}`;H8Tf!&58(l0CLv)qO-3U0b6 z6y)HsDK4J4a_~{S_+73983vP2x8a8@k*n{?a32Mm!i@VcNv0G&32wSr5Pe;s`i?B2 za3FLrCdm?_?KKw2oh-pHuwH_JFv&rPg7C(X!4ASml?;SAQsyuQZn}6Mgi%3q6Y%7? z_Jw9(FojWY6UIh9a1^#ksh<~8B!-inzS0;_=2(Vkf8O`D{`|lGt2p@eI)qVh6UK&y z-^pxZC1ETGJ2k}ZBm%rLk2zCNzn@T02^lS;4>w`7lK{_&69Mg;b^DX;iHZnrilq4o zf}S*o&w<}v948@s z8{Gb)VQ|MqiBPb3J4;*)qu?ftHwxoESX|6=AD)6jA3udba5F{;7;Y$?+T&1}&L1e9 z&ha=zD~HpS}wRq=QN1$|JeU{TO9%DDZqnUSP{Cw9zXl6q$lVZ{JF0*%zv2 zRgy@Qu?B;}B?TlOX)>koeSLZ)ub5C`y2s5ok`V|ST?Z*_oVY3!B+KNrq>Q1K^hz5g zD&*LFE188CqA${KSWM|KU;@0bgWu>p9{CsQfnli#|Gr;-Xug{a-yN{lWLMKU}nGOgrIM@#=ua2ld8`03-R#=DZ{D@Ljf+Yt6_a9c?y z6P$*p%*n>HjWv)^aB!#84K>#Sf$zqFmi*eapy$9LD%ZyM^MXU;7Bq|!ZWs`uIZ-v# zaad9$4*qe7%C%7=x-gJ69=s+7!`e>=gBf6`DHOqd7@|sL`c*r_>Xpix7sB$Vl?U^H z{H*+mq#|)_=r1N3`7xP%eybh|i~0*_H}#hS`a;jG*)P0aip*(be=vC_f-eP0fpZv0 z6WR%vPcXpaSzx|bSx)J_Ozqby6!0$&qu>s#4F!2~Vpx2`(AG-ZC3D))D=TmaX_e7U za?b+m!_tAZ_{dfaNfVl7${G^Hg{KU1sMdwUqi-o=XTjp5T?xgyV=;pUIai|uKR>PH^EV5YGM1qgrVo*|F+??hMzyZN3n zb{g(G!D%?5H1wWJ>iBq7*-=4D+Us6`)USVKjjEpV5?Mc%ii<|it7R#ZKSajmz;TW7 zfM|zQ;K`(|DUIfDW9M{|rgU&xN7WnxVN9y9sJuMyCz(`*QE(GTI|}N*!Sovkp*C(x ziW~7%QjKCFq&A^lKqrSb2=1}%0AY@?ZTJ|txua@nM87Q{$cs&V7uA$0C~i;3;L4wa zYsAdsy>l=yuwyVVnBx>sM+L8f+j!J~Zegff(eRY_kg(vDo`EDOedK@d<+njLb6kxpnz~sLL37(W9$QKO6VH| z3JEo&Cqdze(KwoK9%E^<(O#EASA~gfp8N2&iV8{*HQzmEVMFHoS&*t_SrQ$EClsz4 zDoAXS^QsdW7j}pDlb*XmBMSkvV9~7OEbvggGDL+Z5XNANrfZDKA(4L%2kb&hzXJuY z2uev7Bs9KYzKIHWW~+=Fh@fa+^X+40C9Nq zN@{>Doe7QXay*ld7ozvs!Wg(|1V3q%H7j5G8rhqveV@_R z3=P+(d}*}b!#y_NG-mB6C_gGZj)npnf4D1Ups_rm&>u;%#+dD3^Ic=8mdZ(kUPM3O zk{2UfSU&P-yfVkdtDN3~<0#)U1`rNpJ{$Z#Ii%bmkSVY!lc0cjjEsgt@GSU_F~ESP zvG|RY)Ck~@vrv(QUuxz=;kprF?uKs}V>hA?rlmG8U|3-`ZD;1hu@9;@nO5`X4DcNx zF?xw38rP+FRz}98in1?~&5$xhyG*`gjC8(*!JRY>3}#81!YKHOqoFXmOd8Tj4Sk}r zI@WT8@gBGk)?&fNzF&wuEtaQu;;O5|hrvxAO~=y;DMXI972$~~Dk(?{!IR)N91Q~t z3x>fYVIhu!+j6{77-8W?A%p5S3{N7M#`g$09QlCTgy3~xMRY;kJzR|+u9 z5*6q#E!q+Zq)cx-V-ETW8_QxRT09%eW9$YH7-_g%3#<&%N~Kjol@JDxs7PWUy>9{o*C^^Z0s;ZpGn^?1}z%C zbY)}e(&Uk_TfjPE5<;$Pr?9Ul9Lby!A%+s1Y>R3NytOncsdHb z!xD8GB>(dYh#Lh{;21|iBdRId&*8)1=8d*Uscc!z#=;C{hxpWJy}|@f{6t)bpyD*c9x0%VZF z!EyI+u+uQ|hWj+kk~f5pgPTNBQqxl4eT31%ub<;^d_56;Wj}!3xy*UPRZoyN#M~#_ z=BAMr9IULe<1k5C6+R4Z5=mW&_2}_|GR-Or(uPd@2BAl}!?tFaR=V^1_&edIkd-Sz z_3(jo*|I@ytMw9l)Z8jZD(#~{9vw`jeFGVu)2}Bxr-e{(@?x|NvL7Wyfyc15%&X?T z-4d;YfqgP%O&P%R;vei$~|OlaAyS|p>K${_{(^h#LX6z#|J1~|_|J*9if z!Vvl50zJMW`KWQ7LIqAc3C4BraESI8d|w$12fJw1g8szD;uM_to=-0n8Lu-n4*ftt zaf(I{=9*i}pQpnSBF6cxbP{{Fbe1??g1HZX_5t5Qx+q$i@nYGlhh{dofy2 ziNJT6QR?)fmbxDcn4t(t_qU>D7*#!CFsRXCJz%|->O!C~;%qzDS3}L}b=qt`?D2EIb6dKtyM7^Q(H9`=U?>A#Gi0%c?h2}hx>1ZA< zr)GF9aprKT^42Tp^VM`Eag z9>?+l7w}M1vO?~Vu9ptSzDZ%8a zFeh~NO=koHbZLTtl%dfsVt%=@PNHVaD|L}+&6kiW@akL6RQiw0!7raqy=V=U9)x6% z3Y~iFnbLRZlmSSYP&JdFmF zWbZ_bUFbM)Iue|Q?U$d=^)Kx&|9i9Z@)`76!8gJ?4GSb#jHieNybVz07Aq=99CBCM zS07H>hFpshaGhDP%W#qh6v9~?(c{1n8$^oAD)pdvJYIW-6Y-Qh^q#{OE4A_7^Fe?X z%Bty=N2<@T(WvDkX=M5=atSKftiIul4El~uY=)~ht&y&F3Dj>urXWFRcsdB9-w+jV zky@8~A+ED<7uw@2s5PBGndUIW3e44aoKYB%?vu5qi}(%-6H~mGps)}k*Y}M`HblI` zP@r|F-O!^WyEml}Em`1JfH>F8pC<7j)k~s}g_}b*)(&NRZj?a!6JHM+4tXenPgTX$ zfQkwF;7;lt1_Qx3UBo$^676yhy%r$XjpA!DFfouHg}hXNC;K-vUf z!6_ynd$DKueZ@?^0u4ggB#|-5g3}x_0Ky#^2MBXS#xMqM-e?Emj*J6?Ng`ty1UGBE zL9ob(d~$>TG)rU*qu^$ZBK@s2T8uM~5j>^!EE`4C)uCM)I?G;6Fc6Fpx-=M~1jY)4obg6sTnIY~c`n480wR1E+_ceP=#+WUrN@n+1y|`wC&(3^U@(eL@JhIO;{v~n zMCk^BY6_F%SuhB0TjDe3h6S=it>WAE#IiBNMXzC!?Fqs{Y-an$cZS&ul>)OE2@*hO z*~Imt%Oc;I)J>*)Iu#=ZC~LhUlVcUEMR(5KnQzaEMMT-`LJv zk>O4lAh?{p=QzU(GQId%bY%YK6cHmj-httYj}@e%v)h$T)F0U@4QfmGC)_zI?jDCrw#euzgV8Qae(1*Y?&FLSI!iN z!A%|Iz{3VvFCjIbG!GuN;ExRKCcn!RC5NxR`-~33hNX3(t8i+Oh((e{0al1{!h$WA{`Aqj4(S({@kSKk0^k)Z&5(ZYImiXQyaTRBd`!q`4CwSVJ1 z1AXBT%h3)43~8*HCOQli8b^)rIPk4?j)NI2NUnp`GuE=VbKtU-2^Q;O8MN;`V;EQq z+Ca$Df_}OaSK<^DZdoaq11G_fXtUawrsWof!A&3SFj%+AV3?)d6h^_#9&MY<;$xwr zda*w=$79JdSuifReMbYqN^1jQlG-|q!W;7$TPz&KwTxG&Clk%49;0Iqk8v2>fwdh5 ziwlC`fI&VW+=#Y;l*afJSar7=+&KCS4bOsudcO9#R}B|v5UTh9?w_J z++-LUkhG!UeMV@Xz70*Gv|Q14(PV0oNGt+2l>%E?Lkr>g)to$10Epdn10iV&ijYZw2)Z+^z8j5!aHkFfgh^6|sBhiM zK2+c+SV_@UN;nGBloa8^;OCLHGGd{DwPV)zC%F(rhTzQ7ccRgv%!@7KJ1)?I*}9m~ z6#X|~2tJs;6OBgIk}y*171AS%Gs>Vi4O8^r@bANEA<~7U>Fz}aLxETFke(4gl|9VS zmft>C{)r?{@0@6^r_!=%i;t6J)8SX*_f9lFE%i&^B>L%(fBf>xuRs6o&;R~~Ncg+) z?oaT7zla7Dx@yB4qoOXP+2;cgRiGu-wm25+lS(*p+17aa<@C-}-Z(#`PZLV)@99|5-So7aD z+%q9(cb9d3Zj!*L8Rmg+fe0=pv1R1M-^SMBd2%ldhbkh~HV+Qb%#4|D0EV61WS6xG z;#n`k#jf#C>eJAtVPO#55s;k)gf>~hklPQNn=JY7kjki66356N1L1d1%s>C*@4tNe zr(b{l+izdK{q)OUe*5?r17V}Fv!X3%Ct<^G8acJZAY81G@KJDUcUc}47!?~DI1BA9 z2}PX<-EXwFXmh1Up?xCx5Ezl~?g*%hrLnzgXQ4^~gr>(VXMxFZ%sX`LNtQCOKm6g_ zf4>tlv^a78*p}lfCjy-)K9hlMMNQ7k$&<0qqa~Z~Co@mxi@?u8m zrii0Cxzi28$eko*e=;?hBzFpf;3kj;g5}XV36166iRy{iq@2cG;4IuP<~R$pgof~8 zaC1oVXD$HMgWd;*f(*`g{oFS+~W>jvDefG#Xy?T@vSy z*H|>H5ycLYa~)8iuI%UW9xxNc-2^FW%ue)~gRo?V;W`LQrQ=^Cv}1OU92}&xE7qCe zAZ_ZMgmOQ`zRp}u7!Jq+)kyR0jgP~!e%;E!(}?ac-(NVKyb`9Zln4-S zd>pVdxh@9#YRpNbfguLJ4#<{5rQC~k0ndC6Ug|gn6 zIEXPrYhgSTg`$$B*6XF%^S7jX(n^BO^M{dO{^Gl&yA@WR+ zKXdw{Y>XpMl!{eTbA7sCd2h}4J+8#D!3Go|*|6=6gTp~7rRR|W_6sm%t;$AgPo7%l z0C{cdMC;4#je)~JVH^bmVN;+;oikiVAt+70`Th+W-Rpm138W@?8{({LHMBKzae2#f zQ&Lf123+Bk6FKRV4uC?#bt_h419c@V7nKx<>Lrd1*R_b|(wpz$&>%6KWR3TUX!M{+ zA9Nz17-fdxM{#ibkSNDN=78OUT8?^fSl;kZ(u9MA+)~K)KPC=_LvV1XG{CzTG-8`3 zRHMek?1)|G>p;BDDGmg!B0@x9roH(_4vm(ZoKy`83D8Q>yGP4q4k)7Oce?(Qtv(K#x`nO&j_3P9tWknJ-4KE~v0#{zuyzKL@L^lvqQM z?8^zz)RjU|if4X1N0k-hMUo?X*P;}PD`JC%Ni8evV}8~4)CI=%bVk*KWIFG>oci_G z-iV^sMm?eQ(p#KBh>>Sz9PC!aYHk$_Z(umpm+a-{WgvOy)tR4)>o7#!*thp?4iG!+ zPE?t3b0)5tZtCjdM`3WYNgOjfIr2UXRN*h^U{{ru|1?A;wPe)c+zG9G@6506;~+f% z>eK5uWNycex;u`8JKV0!@hXa}mPX1|^Tm*#o&>@GnaSf<7bXsan@EZr3uC+|k}GL6 zDoTtu`AjCcq@Lp$3@{xivC4be@a7vk@LqBtM4s*q+B(96x^g9j)SO#aXpk43Vr_4{ z9jK&^o84prJwDf!CFsbT=_ZqRtJ?Mul|*UBV4|B5+qDt@&$$;m5o<59 z#$qb1>lbaQnSh9~9yYYY-T=|B#yxgZ(()@If$C6`NxPZG|ORHRPRR1&!w-xQZuSST>mD#SCNMW|GiCncq=VYEH8uK!|dI?5OvJ(Rb=* zoEr5}CXny=u+U*SwRc5yfI9ktu*=sb_As=Vtz;fn^!~I4{O}|rDTA9r8VnoqL0y|t zgeb9x)qY7~7WUMD8Zf{fj*y3C3HUxLsOz#x-(C_1BvPF`nwmVU3lR+pzTLwJjCV}n zaZrSqAtHnUaK(BTs;5)5P}IB&J^4kPKt;(u?9aY{M;#TDnxlQ--UQK~NDg)>IJ2R) z(1xm9iCy+cOXkPnx(Y$5x$TYBgQ1{ZGaAM3O!rO1ZAe}^C{)%1s`5sOe!=&tRMcl5&LQuU zUIb7u;DQucJl_1Yk*9H0@)`j3Q!@!S|)HdG5FCXd1a7yn zK=C9`9IOrLAa2^corAS;d~UDLEmB1JhBOxl#^5P%6;Pa>gunymHy9CoHyZuw6%p=N z5|XF()#oUv;{n0V8*dP-oHh{VsHekG(ajnS1WTSJOQ4f5P4XOu!A~1s2>tG|>K`>tNb8>_c1klHK^6#W>ULJ0oBed?o#?k?q=*y<$V5 zl09lrI|~ZoT)|Bm?JTS$jfEa{V;0wVDG@Frr%pVjRs|703BC^v)HNh&uy_?CX%Q`j8b%ZV$iCU*R}3{#gUh1RKO&S#1FlUXz*(RibzBn;PZCapu> zCwwcK29ZaL5O;XsI4nG{6NLw4&FHoSWVAOr?9Pd>$D+2@wdL)xsBI^W%KTb%PE-83 zaFa;Mawu`IC({y#IiAe$rEnXMb`tJOG17)RZv+S+IJ-&cZD9MD#THW;8bnBWX}TIQr{3 zNzxF8!5v#04EIYp0>dmXWf%oNgOo-v6NqLRB!r%o?dpj#$QTveH>0r#X_b{Cqh!RV zsjQe)1~o~1HyX>F>{eLjG|R1s=1#sDO@ZEdz!#^u<`|%iOP#5m!rI`+=U=r)Zy@A} z&HTwr8y0zBqug?%04gsu-Yyi<*Afh9f+)C6$OSo?qGD2NJuF2m;>go-V56#R=|z&I zm`!CFDJ*>}8rgHbAVsn|_qCv7wn#IZyt=`FOK!BFKBRDnazA(9k7hYn5a0x0mEM^5 zD_H1S2$?;kKgc9RUYQ&(7+6cT8t-(hkVB%&T9yYEUaHL}3lz(x}EHYX(yq9f?L- z-2&QfyKhTV$QlYzXajj)u{RVCkh*IHn!07)HSJS8_&%Qj`rGas(iE&N2_pTufbLXD zp<}J9b3NMeJdIRVcdy|3g)Tkz@1>LEdLBI=tZ;pUNrddtTZiqX|bZ-M1bH0 zRb|0wJ242?=Czo?K>=|rpS=hP8JYnmH5JLDw591gz;o;5*TP0tU!e zO1-0sXy-wVTI#LD=iz!Wqoy0!y1It}|L01LxYAs z7Ptxqq|vNRu#J7lKwd2=2yuxI!yOz7=~IcMPrelm17ReiNujB)cIldN9afO&^NTfg zWzlQysA|jZd(mK***UWJwLrC{*HftDosu=iYcCPRNH)TLJ|Qm81YBupNe3+;>R61@?=1x;hSu=I)D)qRpD z@(CxR7ZhveF8y^FAj2);_tH0^5e=|kgVBf+1?Ht5wH}JpTV~&(h)TEYz7dTGMsOxg3wssuc z>0rm9xD82h01y4{$F*2thhpe=y%wqMPRF%~J{P_XjU9$-oT)=_9fs?<$lQ6ko{Qi( zbB7r2T?w`>rTwQ2sv>>ZXiUDu{!s>1G7H37U0Da`mK-65ZNNhnvOJa!az8EC3e zofw5Nu89Xp--M9Zi!xAaAo*H=ShdiisV8vR7C*~o7=$o!>5sm3guHU*G2Y#bi;cD!%sKC+HL0xyWlicHA-Bw3 zhaMrrBRpCP0TgzAufE1=(}i0@_Mrnv|i#ff`ccvf^sK z1f808-F;!z`;d;lto2kcNWo=Nbxg z?B|9t@H>&nHV`;0D%`W0X~cel9z{Es-FqacB@VSAfkO=PhV1}KFZ{&kvSofM@78pX zKu%g5!J&3;FfDrMacb3HwRYzC*mDMKION3fUfpE(U1>C$Xc#)(_W~Tc0Mb17qL4P! zR|E++i9{ig8$e0b=&~^DUv(1bSM@Y1b-TeH%%HoWR*{{XAus4fyq6)1rk1^y7-w1F zr$sM!@W^>^=h}vT~5mObk?jU;{0r`lCGw=P(!unp{F_fDflu zqz(f#pgnyu9Y?{UA&;3x!D4EEJcA`OK5D8PYQ#EF{ZD1=dP z)5sfzQCEpK+fkULt}0;|+%(c)u)4}%n53?Xqu?fy3rVzga_=hv0qlSvGs~5L-4GMB zz?#l|C9oY>ewRer`u-d_q?WZ$G-U%BgKt!GKb967h>Tc1PSbMEfwYCW0joR9_G0z|Gi-yqQ!4l{$P?K{HL@dk)r&aYGUw3O zd;%0=Bzk<$eGs+<-MIehnj3DL1L)yS99Ri(kWl;RIqLHfi-kPLKDsp1metnhATpbrQvpn zj!t~MVPIMR;ANF7av_BjBkZ-#fJ0Dp8!r|Q2gFmMgYvzH#4={EUGHU<98wIApx88) zjvf-^3hO}|+ImlBi91Q}DX0~Q`*j2aE40lWd6q&ujDnj++EEbcN9n_j!YsX~Fbr-E zX)uT#T8I(c2&ABtU84b&dKuH+idbjfcdsdk$}WhQ^qD^VQRzM%7_BQVq^_*`_%i5Y zB)#mQIwOMOe4Xq47-A$euPE_zo0UF`($7T~s)ch`S*0AyJDt zrusCV;6sMQJbViqyA(FNEKIYPLsABWiV3)1(QDyb*xV@CwJ;Q5#!cZ^5{ALeAq@u0 zA?+|sl0%|}7JDb&*w-K#sdu5B5IwYIg|Lgf-x}&*41W{%p2kD591wnv7U<6V1~&I{ z5lqNam1RLFkJ4OQS4-v|oZ--yQ6r2W-@QglRXmti;y`P*!hkw?MCTv_(u!;%fQ8P3 zn?}}gF;=uaB#xx6)2?kZ@n6~u7o$OZS|ekt!yRcWK9*J6W|#M!;v>n%Eaw4%a8ifR zz3`Js#I{c;5RIRU?uB^cea#99>YAAuIRZqK5a|2ZpgRs-UbLqxOcq)(FY1d~!emU+ z#P_garr(c(Lc<}kY2He-bSDZ8L5{3%VpGW>3 z`P<+B^dG;LGpE5xrtS=8a%*cb|^Ji&kC;TS0Bdc1P1T0NkkQacWUJJU80Wb$=L zDRO|Il=7@8v>P5k==)}I6x^Bijlvk_b{$P3B_eTIhPh8D{2&Z&B3Yq5p|i~cLDz*Q zy%c4ceHM1`aIDDCq<=u6Ux?tO;_#ho35I>{$OdPO>QiNfKc#}Moug{f+L!dQ(_|`Q|9v%3384A71 zngxU)>FMxoYX$@s8`}lJETJ)sf!l&K3@lX`7|aqF!yx!+<8>D-E{=mROIQqJ;3kcA zXHr?Oc)%7`MZKl@KK{=YZ9Mj13J28KN=}X6r8hQCQSqdEbpce5)Gm7Gh~u2#m6ehUxQC*HP~! z{fG=G{POd;{-yopzcv+AJ>VD}(|uEs?A!=IIAl`_o!L;{%q0EN?`qzl>w#Ev|B&eMJ7h1h>#C3^d?hyyG&CzCqyocUv@-9^|K&t zWT!!JATXWyGz=Kl!eAzH8r<|zUt2G#U=Y)*G<>5;ASZz#=yZ7a9yJDnBE!x|%0M78 zl-H3yG6a3y4&R$d2k{W?niige$m*9NS-bCt}YOoig zN5O4C8VZUFFJ4Nm%?O1_QYYvO!NA~BAMHY1X8{t&6&-aHGAB;`AjD&WJFxCwnbO)q z<4z)l9M%Gp0UOxFG2wbCISA4F;3klEA9@x|$Hnf0vS{Re660{i#%MwH#*cp+IFv}|uZOZ$WH08u7oPxt{MMa}yRg9eER)`?at!M$6ii6s~$(<^z zR1%lB*TQ-Y{k51NKCo)KzxWf7mp||l@Q|oXYS=BjnvgO#WI#jvc9NSBQ~*3!lZ${b zl7}0FN$RTTx`f(k0T`~b3MHd)7J6Ov%3NOd+##rgc+frYK(MF65=nb1I15l?6DLq3 zkqpnVePP`i6lD{iE+3R$)Gv(;rr55zJ8^7Z*%OM-;!sJ{g6lwMs(1G z0EUpMe;@w$C+S1bq0OG};qdna}hxTXyl|PN>dK z;(O|!hK<%!WN-;(!8r{L*54*|uc3O?SF$?dUq0Z%ZQ}0HOC^b#$cJ z!9bJG0Fpyc`{6v?Km-NPB;EApM06lnJg!_2%1lH{G{)j*4&B^$K&&iVMksK;JB_|L zw9xx%AYp>?n?BETHKOkE8p(NQ!g?^qlSUfwgkZ0lD~UelyE73bKcQOW0I|XV#MP}o zmRd9HPG&@jNjS1WABqDyR{#Xnm10mD2(zpXL{ab^YV0W3%Xyc4FjbX4rbU{t~#(+UL1!v|>ia`yH z=@R(vG&cxHLE_c(9Rw!*In;yQ#3_6d-1N~vu$ZvWmt%A{i$p~j1UGf;KBue;bYDC8 zp{y72MqC2R;sBAu#N{yYNDv1FAGy+DWL=Z(vwr%AkjDO0#GISN%4z9E1br z6D!9-%5S;#N=CL2Qi(l$QyP8wb*zo3vQ~wyrJ_kHYwh1M(+_OZ>~0HEFuW8Xc^^di zS=hN-QJmI-$!lK|&#T5+h!TEwca&X_i#{b%L20cbq9BqhQABN}=Px3nx?XQQX$A(? zYZ3-0muQk35eC6c9aTf0eMoaHQJAZt+ONl!`abjvvB36`<7bJq7kpEiwXiEdvs$BU zNxT>ojIx)tW-cnPQFgT3!hw|Y=>ZEc4$J1Z}3H8^>qe0(@k99z>uyAo2zK4a*X-FL$JHurQUkEpQ zq;lHi@@Cpz=(&(drBXP1xDnQ6VBxR?l4!QZoWo$Ah6aOq8YUW5`Y^=Wuf7qDfuNo< zYTPn*gzch&ip*(EkvTnp&>u>g3_%p!{P9L%kbKPLGZB;F356eo!JS(h4AybtFd+7n zN-6mP2J1M*4#OKK8l=q%FQm<_(T--jiYqb6+*%vibQx--&ET8S+&dKGT2vgN52l+F zAra>H_;xhf9(hT=Wbk`Xw}J%8$2sUdS|%Zvju@96z9mhi!z8X7BFU(RW9Ww+4_jFx zGce)LkVd-WI)GRam0u&18sC-1fPl_W_+M(N=B2AK2?-D&4o<`w(Rq4fOQU8R%3%0; zFvV8$4IlYUpXDc?y43D(XSCtKX?K!Ii62Vr?0z(m^?3DlvpKksoCo?(k@geacNYhI zyC}kp?F6TTcGNs&WC5gbN*C`|26ab>584?GyL_rgsnEA7aQEW=v#-2t@M znlrTMg9c`&??_`9Sc_h6u)8AU9Ig{#5Zr0@4T5D*2ErsMR2T)f7ilP1g=I$}(Mf;o4{`5WXmp`c@8Djo`7jE8>Nt*q#RNMFlf;BD2!7r;#@IIqV~jls2*EM-iD;7- z6l|71xUj}&SmxluhC%pU#`x1xzfd&3K7RV+AHV$a>(77t^S^&7=kFa_enQ*ki}Y>y z-+cRNS-y1N68-D;rR(+o=fCMjU4#}1Gz>yFNmCC~+_>9l-muI4v7sb)(#mQ(n%zh_Z<|cl^LI6C`3#XOPv6a< zY48EFRCi*7Q=yG-Y26Ek{hR`Zpu@@O8#*+|Q#%F924SX+MR#DJm4&Qx;z!~R4MBRP z&bs$d7z{F!rMKC%r$l0;lijfS4exW5t8(VpOIdVja(NF=VA1 zqfDH>r$d$0Caekzl-)BEX_Q$4W!zi&^j#gQ=u?b)EPWt9Wqnbha*p%?Ac9LOC%;}N zM!+4TROc!nl7j&ZzF=d!wrMw)P;p51S4k2slzDz zu8jZZfBgNIZ~ye`uYddP%eS9?`O9w~|57246+IP(!b@5h$1Eu5rHHNSoW8$9g;+-{ zARtPdc12ALIWqyY^o`m4~4*}-MxdU z@TT5mj$QAEQQ%c(YF{a;SS`I9=TfM@eZsKZNg7vnmkbJ>k5_71erFJYL}***gjDLH z0Cfhj3>8x{9Pg4SP$xFoBCVNwarc1;!r-xug)gtI3%i^N103i@>=NG7%Stb$Zm0+Ms$ z!|*#Cf?Vv=cX_aDp_20ZW3dB7>XxMHOX6c}kxxuw=Tcyw`E{d(-643PF$mLC)|wrJ z)cwegLe%@^^z9vP6nHGz==+YsB#&j#9qfE#?=a4SvL{4yItmN?nVvnRJ_{oX!@_0*iefc+7(34LqDMny^r%PTZy)^6{;x9E~H zj+h|`dI~Lh>I;N9g2EECwm5xH2lmz4iJW&&4`fTv4x{FFNDJohA9o9?I0$a)XqUnA z=W!CQ{5i9*VsEqnLTJ(Z^bH;4GA+^74VlSukU$d`58hQJ(Cy4Wx&(&kDBJgRSY9NE zo!7E;X&h;(@m}cJJX609*E1;`qRWi(igg|d1Xd`iu6_$qqHpRn4f*M}>_`6TM2CVr zI^r*TzOi;-HS18a1j~mxjda{qX>SXb^h@SOLk|(KU1A~Pjj;na0mb@}I*A6i(`M0U zZNg_h56T;AxTbpJ>R>3Sn$XP7P?#fe0ESr4>5ZksKw(@613?#}=C}U=gy4m6Q%HSl zMTM)DU@|kpLQkaE?1?l{A=qSiaL|#-k_?$&KaS zMgvaNzypQ7ZH3S1v00V@)G}_57A{0r1mz^WM7$c2jfFVH%feWhX$<&?Mc2j zjd@1C;zv?cXnmmVnd_??2D&dx4G1TlzP&?(Crp4aUg!!gkSHgig+@2OeRBtcfR{n} z0>;~z8BkwNv6dW$z@20d2yD@8oPt^Q%&0Fz0-UFB>tG;^%>e^pl9l8z25$1W78rl9 zW{n~(`$RP6=D7{g_FETrkm|!9{_yR;Z;>FWM+aWa;1Lrr&+P4%S%N}zdE$+!1L~4e z4BN4Q!t3Q@l~9goKa^Hm$^1-O->H<(66ry{v2_?YY-}QLv=>&4Uv^BY$)vDbPF-5| zpP zR+|QjsJ@MF@?bDnacMBjQC*Tc#HtFW^?|csspJj96scq!1b3gtPC}P!fF~}6xs@dX zwO`7Xp86F=!FPDL*`Y+lhV%;tID6G@I-U8o=8uq20fSr@-`~OfJ}fSn?a3T*0Tg0! z!MAp}K^P_G4Z;+0Ar69@KpF^o7ZD?deej2{L+ zdqjTp1!2-&N|0bkqt7#RVo9QdN5M@UF~jO0NYSa+*yxtaVt1k%I|zf@hrD4JH^R=s zED1ySFu1v+)<=rgN$fD-yL=#qG|OQiFhq%85EYyxdSNWJvxL=^5plEN&D?Y;Ch0T? zhtT-8zI;oE8wWZK+ABg1v9BV}(rEyQm^30lxO_*#;YQ()3IdUNxryFVHDg9Qbvj5dzq9n1N{{P zh-uD3tm%YP>dVWchbO%W74S4LzZEJho4qn0g|U{6B&4Qx`54q$i)T^f}cAM2rPdd5X_N2haqq?NA3cuR*C@h-{`;&CGAZD9{B?TS0S>B z%~f#o#=@HID)Hp;N+MrFm95W{jPPS5NMLXi#~X%`F$o5V6wHz_g;8+x#+4_r^hI0b zImjEWn9}R8rZ%lI@*jH7&^gZk>&v(Q`R)Jt>3`t#_3eNDe}Dc>n&JQVzy13E{_&5) z_>1%4=8lDAaS@+~V6Y(oREUqe6rQBhux2hRuWTWi1cU`Qca+n5ucQb0v78M~G&8G7+?v=%Z~JF^zghh65Q>AarWssRKh5GT~JK;Oh7zT}p0oj@xgT_wwQ8V}4 z`dNsh;3kka3QVfhdbN|#s4a3UmRv2^I1FwIiMSWf0mBG;B}8ao%VM5q0xoQzuzX`k z!yehnxbj8;+1Q2M<9Sessl`?tr@X4FfrDPuiW~!td7e$w&!kSk&pmTwu$&>bZV3!- z26@B4>u?}^)M&fCB=Dr+#5w~GM29spoADiKs%%G4{j+OPp#{skY?5n1)(~sT^A(*| zgP~yWWkq3og#$Oqdl^T;?LyutjQ7&c!YuD)90oUm>{BTf5(Yy<)(B7JXx8;IPbW&Lcr{d(jE>ZW z)m2$-;jW8sOQQoIX54)-P`Em1KTUKomY|dmcf%iKk7v`0ObeA$6q(^TNzn5iO?K+K zeDWwVtGwfB>Cf|M#so|_3w|m|<7Gw6!IFmtC3ac3CUX?qoQ7yh?fcUd^l}T=W`*>2 zA_-Oyc2Str9ifq#xDlt$OOuSU*3u(6weL~0Fr|AD7umX{^AC-mhD9hd`Yn_i0VnZ;7CG%7mU z_dRNmP?i@BNs|g~p@5Q$+SI*Wo%v3TtU74IfJCs{l{6gGXxQLEb^a53W|jUF+8~Kv zi{6Ohxrhz6eTSNbJ0O9R8wE}W3pf`D=`o0zl5i5-JhDjPOggFTpCZk0pM(`Gj?}`r zcis?vC47q-5nPs@Fad-LZZ-~u-TKNL96Jc%Wg19KPQp++8In|5L4g#yB9XX47@OsZ zh?x#>A>3}HLLn&=PXbg>xNn{904J`AK>x}-!W>UT{4}`fqumD!j&>hG++A^FVD)bh#qVTkTy`{p!edfA@|23UAnpc_h6#WR63 zY~sgK5weDxvc5SDWcg!LETH$sH~%;hOGWXiN63B4FB3{nma-~RWBc|rb{cvbeV>IZ ziO%d}c3p}eI1O$a(onDnX?NnvA2Z*H9wLDumPGr`G(^HCqo&5YxpOxd>&x>}lWo$d z#DSsLm4G14iU`BOx2BN~Y?b{6$rF1tTnf?$6zL~Po`gdqHndQ1aMMVdcvKWPcR`YV zAr`x=R2Xvd5|cbNC9ppgLFqS^i`_KRQ0V7@C3Zt$mh%v^WdQ{@iNtyY>bUwVRN6FU zRdE;6hyGOLUah&pL;N(jEl9)RekukIdMa2|OB^Myry^z=j_!av(_Wa#ylBa5t>T0e zB2{JVih2@>j7ee?dYK(`1O^3{7|HS_FXJe)DJkeq%PxD8z`!FBYe)OGG}d~%8nHVl z^w!%nec1R}a8pQ4D;xEtUWt>gyhyXmXQnv}T07J}MT9v1#+!zGN#@8`fl2;|rZ$xo z-8Z@}*+TT{*x)9RZ5#zLTku7;qtJ7R z)QdG&c^OB+O(3;SBy&e;gJS2?>e1)=GPWyA<_pm?20(}vm%csC&7Rk{*D3|I#DaWo zg%wz8`Vt!yq74@+XD2YAO+X;f_dl_1<4W*ube0QMHUq`oyHDpn}iHxlWGHWZCJjs#=f%*2N4 zV!;231_*bYyWFyH_Bx0iNE_)KA9u5nfo{g{RTWB9u_~ zU5ab8U3FPH5-PG%kFTyYf)LU4CsJ-5J?`csvE|bHnCXHoTcg} zRL0JN?^Ppyht7f}ly(;SG_#~HwLl?OaQbF7`u02+{jpfE_%jrGqb~K0xC2Af+YQ@S zH>0eu9X4pB797-;c}WC`casf+#5HFR4&liScCX%>$&~-p#H!DA^+51uBA_}+OOE}L z*#15f2p5IxEZi-*aTa<{F87q*8W6!4>86r)B`iiZVx|(PD@JC1FME~6VF<=ZH;vS` z{;u4~BrOnC#;&Hn9CIiiv0kG>D^ObSHJxjr{`?88*cK5RG(m}HO|giG6KZUG-*>Dr z4DJQIfx#pTcu^D<->>FI!PIUH1xl_d)NX=@LAv^Z3vq`AdUIF5b`DXrFbeK=jhaqu z4yYk1Uuo&fvRTo&9%p*42NxooLE_Tnorb$7IUWiPb5FMY^?c6oP=G{?E{lDciv!Kr zsxG7*2q_1$vpCU-pdGire=ZjmiJa)5%~PCN_})i1!Cb4Rc;GOH`Vq$=MipJ&nAR8! zL;VN?OuZ4)s2|~=9x_p{QuuO?1F|N3ZxxuAR-h=8h2icL*%K|c;C;Z&r|U$MHot^u90fOjyiu^QU?@xy7UD3t>7&7LhlK&d zBw-UE zT|F@jH_?N4B4k#w_~tb_5FCa%keH-1A3qOn0%_J}61Uml+?$_MpW39J>Gy4` z=(76a8`liGzrH>IU)n%uT0zUbmwT-lg< zGPYE)`1Un=Cd7;tcU&WH7(ORHpnPT*@s+tBdz}X{;dn3u-8bLAh9)IJfk`%}K(++7 zs69tX!Gdzose{dH?^4jSMh%W%|He50jzv0wumR^VED;Rtvjhlm5)F1@!wthQEFq?% z3#U!ADGW=%Va2l~zz$vb9XmMiM6L&lF4lxZ@MWVRJ4d$x6k^>5-@L{!un36?Jv0|- zsLm20gDAL7$Qy;xZLpId-tZJn$mn74-D?a6>oyn+lXM%xR|5A9CoJGy#Ar$lx9hQ# zHX81ke4e8L7xX`b2;-u_LMA8DS1br_wbY^m!6TC;Cp6?o$K^Ka(6l+>TDfOY{O~ zF?7Nwcc=Ljfx&G=-nJCoOkNUL3~gWz%D807Jge#JRZ*t&8`m0o(z52T#DUE!7{dBI zN0}W)!A&9UB;0R>p)ku^5r)CdB9~p772$3aqXw8c@)upQ5Y^k1pTX)!;i7rU@us4k?0eiqCd$R@j(?D|4Jv}WGA>@eUI95V|>BG`kWjKg3spnuk# zQ73_+e6oJpA_F_$u*ObS()e~Yf}wLe?6G8iP5v-R_NcY0 zSaIoF)`;S>0aO{ARpS1s$}ZAXPO)p(e%XJn>o4oKzy8}_|MB;~{rdB_zyIk!e*4!> z?Fc`VZuTe)v}vPGG*VyW&Jqnh_>8_$2EQ5~f{;AHf*gzULfA1_;P$}hxXKCi`j-={ z!;%?;A3Fzb`w=B=2-5XKnz3^7Y9vgI@jvLdlr6PxYA63~(S<7Cs7AVo+O=HZz9FXz zuZJ-eL1dxZvXu-p@u%xBjza7{_&zoFeQ2^=00d33bsr%2q+q-~V(>%v!8fTf5bpah zK*)0+o>1ubA^JE_6FHHwB7V1{1E~j5lKYacIS`U9G+4IuW*l3z7w=otXur5Y3oo1i zM6fCk<9Y}a`U@#S!uIn72|g!2V_ z8yytP`LsU@v?5T2>o&99A_cu zm(GACau$>_6C&@kut5J~Mw2*U7%6lV2H%;+u0)?v7EoX|$v&o*PuC-J+H}>GLA&(D zH>FV=I2q>lxWLba#tV|-3JZH`8K)i=qH^+!??==3Y%yX`Sg3gVEgNRx$Scnh7J_o} zuyeeyU`OE&3)fLlRM=9#5F;vtQE+p|1#067frD&;l#w&AAYQ{yq6EvfnMr6y)8uXl$kQDSsO_%z5;EXz)|OuG+cZK8a7P(970`Js69Bh5f(_k3t1I(*u?MUHL=w4Ic{Zv z*4rBo8v3t$AKIh+$Hsm|mt*M+FFZff6JVLo1a+Kz51Iz!k=-eN`xKev4DBFo;;%5j z<^FUA_YMLaY2tILo41chH&_gsTB_?XNn8Gi=>(Gm`6ygwb}ZlfZ*1l7Fk94d*|*FnTz!V1VH*pi?pcta8{HKKR-76F zB@0v)MvxLHBzzL*(S|~h;8T468JbdRFZI)~k!2~9T!BufVV!{dC*5qwPBozZh_I=5 zB2+;Y-Uur%t?NXH-1a@gllc@K?i11Ae6@f)g=UgY7Fn=x+|*_eze%Gj(rdYeUrm?H zmA10T?G>!85Et-KU~z_B)c$7jv#XV81$&M+6Bt6;`eJOjX94X~m_9v&;(uDCwa-tz zp)~9h$I0u>48!2t(Abr@gTuHIz4x?bz7jn+fI?`8rYvq6SrEqU3Dc-5#0rH9MpeOo zPtk683X%6=2zH-}??s~qL!-Vn;#X5w4%$l|;@4AM(T_t=_766n7aCgMGFA~m>k%-l z`LqDeSu-S(PaqfxFs3112!Bq5G%n;Kbt{Qz6B8Is@NOWYy=TZI-@wpLw(;LLWD85A z9t`73+-t{N3CPiOVP;r64&Mkjg|x6>H=@fBsKA`#Mub6dv&S0*dM=kl#T6TsF}!Mq zIrigy=y4&8f}1_6;l_S#@4TMsJi+fcE1y{_S=J16&?gkGPHfQDv-ln~hC*j`u#hN~ z7CAMsYnNnZ_k@DAA){5cZ$h)GqN2xwstZg86e-A}W#7XNKl|ya--vN1g0eL<7QIJe zB~hfV?zf^rkfH~R7=dmDcw{KCpN_+IDM~@^FYr?O7BmLJeJMnF(mMV$mjcTC8p6TN zA6J$|4lRwC1vFv}6^_&_8ACWUxap&zfc+ckqQt!5h%@`{BZ?H2<PN z;;F_~jKqK@B%WP-2O0xGy*50`ZV)ukUsKO0`g0ja!ObCW6!bulJ=syn^H4k;g)20~ zVQ^cJ2E!d2HVy$D>{((%^h)?1G$MSR^xEhuAfc{ z5*5NQxVfXjU}3>vm?bWRQSdWI+s3xwNGtlJB|i%aTae!4JI`Q`4Lg4S-h{1eOh62t zrw1D(bnqRWM?x^j$nh>CDnmPPn5B%M%K{@dLWpG|8Nk8Hh>HSPW+@}K&$i>Q5NJ2O zv7V79854YaE-NxN=;+TSS^$NA;Kp1lt5yUVGZU!lX->P>(Ln>tTD`s^s zv0>B5>Oa<%ohQ#@8B;-&{DkvPRLmOHk2j|Y7R%s+>T?#SiFY`Z*Jp`LZhiY1n@bq4 zgR=T-p>sRSxGvn2_x)!qI9QFX;2<9095r@4Lb^HRikJ^eN&N2Fqk_kp^*A` z8ZP2njN;?F(A>)-qv2Bcxe*s83Xj1=+Bc%HXTnlQo7_$^tqZ>rz7tIsXAlwiB8jtj z`IU?z4MqGhW9a-IFo>oM6pl{n1oyWWRrXd|Q8me18Lg>&D;fjA3T){-^%cW8$X?;f z(zl|yQ5ZFqG`~0w+=(RqRBx_C_$;`iYnl)=Tl6TP^Cn><{D_WDoTus1GVfVlvpOO} zw5a;M70pje{nD51e){7dzx?v+&wu;#zkjJ^`(1nPr@9|s_(L@L>Dy1s@}+NT>R-1n zESvFPfB!e%I60t-vayA+QNW@*`q5}`R{qbng??`eP2&BRpa1;xUw--Xe>H#Vv%sJL ztC%28=xwz8D=Qcq(_mb`J}`CpdP3aaWTu?L`sob_u|^K6SOE}FQiCCbKO&`k<7Dhm zMJVSJ(5|0A=pBq;Gs`z|u!FD<*H}4xXh#PlT|!V|2(DBlih&z;4TEh%7l_skyUnN( ze{3*qTMBZ((=ixPHwuDVvo~sX*<$W;O@>4$<6L$RK6bX` z3%m?`X%(qFkz`>D6+Ft!h$ZC@3egFmZ{T1zVw0?(P+(nAoBlhA!yu9Y}ON>hLJA?rP_;c;&7h^k_4c*#JtI6b3_(9^kt; z7z`_vnT7F>U5b^lPm1;Sr#mrFXc~-1?u45;Rthr|>q(!bu!w;y{)nvjsf5TOH0q)d zZR`3T4wWr#xysv1sb=>=T{g!{$z_P%2RC;V3P{c>YocetESR?euK65EQ+x*Mn>P#) z?4hg@Myh}^$3s~@qZ%;2af1bg9a-v?B`%3_pAah8&Z5T7{6bt~j#x?U+cw-7jB>I{ z;$fPg5DkpJYlC61VPSG0s%ejd7B^FDC8EK3{RJV6w3xr?sIzn`3wP z-GzwOleA5oc4H+)CRfyrKnV&MC?x1MAv?@?Ji`j}6Aah$5S4%NJsSoH_lZ_{fbJ4fxr?-7|e!nMUzU zA`GM&Cnn8Wz(!f_nZ@pgyf&%`a=(6)ybQRh;!Bv3w ziOxyDUB1#baU!EObYHLnHd%Yg-3S1*z^;k)`N}YyeRCS{_N2!Y&@-rSyc+o82ke;* zZ-OSqpgj?slw%4YqQ;o46u2h>qDFx?qN!A8PV!3dy6VxO$~5lv^z>!$i*dyj*lo@Cn1_a`W_99rR}{>;;szx9#*HxA)jFQp&as! zKLaG%T~R<2dhr%eJ{oL}lA6{w?gIrdDP+JUwRYC%GnZ%JU@4@4IGR$*EGguh!w^p) zeUpYtm#woqwP&yp9N_WLt5m-!H{;d%O~G}n`o^SzfgESy&ZPBFXnMc(OgeMsWE770 z#c(soM!WH)sr8jfH>Q89@wt9R>TE8&Q)jo6SfJMdDvOE2B@#Lr6WATFI zGwZ8c`naGrL$uZK#;Bpnyyr?HcT~4mHqsic2Q?v?YG+R(0U;*+qJ?bdIIw%du$s;S zo!A#YZ)VdRFu>}G`HIT}*MUKerh@@@Z#)r53$MkgMHxMpN9I;!e=dVgsTD&G4+I40 zcd3U`SLsUNaYc8Y+&ax!cxprTw~{Lnqs3RO!W|gk@N^DzPo;TK`r5AMwo0Yf3?$$u z82VY@LTsOro|Es>Q2OIYen3lF%Cc4If;Pvd7*O=6z*`Y$K(hDY90odbtcvg~P(K`< z{R*LDV?AI~H!8!l>M4Izs{y(nOt(mNN$u>QaJe~s=fAGKJv|HmmB0{vB;52-Z(~mv zX+YAI5cPBK>oQZ65~2VHjY}$vfzD}QX;RB#+yQ(L0f1gK-W|Lfuzl*5LGr}A6D_Ec zR(+QSu{~GyouosreahZun4}rqVvfPpw`Zs@I%HVG3>S7h7lK80=+QW>lT00%`>=|Q zq|d?OjPK8&(~r*|{3cESDP%>|fCOXXlJIzBt|NNZ6u%B`?kKva*623PrkHJ36!}ra z=rpg_3^cFx^|=O=!74Sg6B^ZCg1*3&7;Dya91c#y9BbA={~GnrA{f}3H5g!vNLeAu zU*-#;un@+;O&jeT821x83+iCH9&_wjhf#3T#v296PiUQzlTb($CRwKpgWzV34Ifzb zX72@K_UGVzO|xGd<~SiE;6;T2!@XKd{v=6~NzQ@{t)khp@6OP6sS2xhTF4g|S@Kk{ zSUX906b|g&w}6d!{r|c9>bR=5wQBF_FDU#d*63Y=J|f#A8UQTd%Z{Wc+TB}IiLB&c*bBmGzhdhhCzh5 zPKe4{fI^mro=$b_69NbUC6&O#abmkP$Ux9ap%Lk`fK&?ZX(%mJvU?A)XtdsVT2Aiz`CSNQKlB_S*3>#RolDYy|S$3T7gSY>7zF)(4Z8CeV^MLupt!4`!M` z(A1KLC5|pD<7UV}$PXq;3jl+?0mVF+X#ru@LcfV;IKg&m&eZ)Uob^bPVKXgBG1g4a#lhG#l(411sl^ z8wFwO+o6G0s35$6z2g*0t$Mql1Wa|q>69#7~fY$WB>?e92S~WfeLBEfCg0>y%MGqc8`Jt z0xv(D?a_c8bmR!ufrK7ySlPA&+!hQflsvZKhTO!4#l-S&6jZ~8 z<$f>OCJksgg8qo>Amt6B(Ur(`QAgguCk3l#do;)(h)DzP1XK!)M442MTDra1ASDfa zyg#-_1KIT$v4bw31WB4fE%_rSDVpuj01UvQ$3uzE6X`>1LGpo(iHbTcocRKXjEv|g z!ys!NYO#E(fn=&``YZ;$mw~76_D_R`|0$C#-w0bR0Zup`PIZ)(n9Oi;AOGn=@xOu~ zUI}?Hp#?!6(jCefOr#MSc?T2FFyy!q@?=835sH~iq=5qOWWvN$??K&QFq7t~)OLiV z5#$dM9u?yyVGjmsI~w^c1oS8u87IMpfs`--1IU?CMX8R2iLcqjc9)Scko%6Zqr6)B zj@&1K*>|*|Alb{%C4ifga@=L?T?Q#>I4mwU40PBY(I~#z$PWT@SS^Dyz^V7trMo(B|=I1r+LI_(cp_syi}$mc)~3Q+tKtwaK|C2NU})Qh?(R z9vuANUep(K&i)wMRPhDv;Ijkn&`m2HXM* zQcMCuLrcbZHFPwTPz?}zFnRPG1%<$Nnjw)UXqJi3BQ}fy1ln!jKMP<2TMUZ67gE~5 z#dAb9z&Z{B<&d}+ltURvtw@gt18FBE4GIydvGZC=-FOY(axFd|3kJOrBzPHrAyn(OJfGUXv13oHoc|;gAC^8KsaR+J-WsrKk5*|!J(1!-=bh`hws6x)b$8JNo zXTbo$JG5KyOro~dqqf9qHeLsmny9q|MGUCyg-1FJ;P{z=gqdUUUSJ!|Kn)xA2|83! z)~br^ea)z@NF`eNRTbwRAxTTHQYZN=U{i|Z-1KBJVwGtDJPJ-B#e9KeNe~%$wyXsW zJB5_af~}yySujBMQZ*F?S6g0STg}K9LhiO;{SuaSlt`(g+roP#*iJL>9zlx@wKX)- z$)65#R-(&_o)(XlA@pb`K1v)nZYc_Z?KUF=AwVu1u~7s<5POi|J1Il?)D94GaNreh zLMM|MB&FbBI}r#DM96V zkt8l{(}6nefxAJQ$4XA5nu=3Z3dG;^wJRh>G1I} z#8yf34LEQOZ}Bl%DClq&puvGu2@Pqey6H#&l4>lZPI#o0oJfn0rQpRW2`sRUW^{;( zMiM_o@67@NpbngP3rcO8Uuu+LG-EGkkZ~}2@fc81qn!~U5f0IV$M;{F-;$?*4)bgZ z641|9zyHcp3 ztiuT{;-i+f@3C&Bp!gUX(m5nJwDW&Aoow#}XU>3Y&bSe@T5ST^Nq#Q1kg%c|g>s(D zv|!-uL9(4@M2Q+ymn8RTAc70Ylb|aPD_63dQ9xj@$7rBC!ELcZbt3C9U~R~N&J;DI zsS}HfAqsRTrxR-r1@V>O@5JIe%z^OJ`>szq+Ue+=Q?;lo3U$P!1@I2T^0Uda{KPk9Fh?r zb=b7Wss!!;dJdqr*y<{EycE2?JD_f`P!PloYwAHt6hKO@(Q;7>1(0)}NJztSMn(Y{ zwNZQ?BovTR!m&reH{#!In+nK;7*|3hJE6p;0z!k;V`9UJ{1zesDmgAlO-L?}XX>ys zFpw{V9(j|Q+6&T!&|_p{Q1VHz)|TA&ftft&;m;@zCh(cX#=KT)wcx!}hRHL#&7d>t zymUq$K0_L&GxRc28(ty@i)|P5Al1Wl;^nub;W>kR(SjMThyx~j0?g@*P=%#&owE6V zwvG#?RU8f@6zw>IgUoM{gM&KwExgQTC zZ83{vJdtudps_8LQnrJjm{Q8_LHKtkf!TJ{p=!`cv~cT429Jd}yrwmModT`{yVC?E zU@wj>BBTI3XF;x|zefJYIt--CAzYyu ziLV4*3r?pQ)XFR%wAhrP_OqNnYteHfCjq6T&-S9jze%6dvY4S=A!G_t`m6>0lkC`( zlRmRC;7gzJ_%q^yd z6q7jH!@$~Z<;|Sgo-$ZU#F!df4TSbV?%E&0#u7iYva_)m25dKvm$u^adjK`?2qC>WXiM43HMs#?SK?#jkdojT} zqUN+W#}*tbPXY)=vPq!57$ZplgZqs&RB)fsY*xf_t(^x_JP0VAxT^-Xr;J!cQ*wkb zfIQh|JSK8p6-|b>)(u!3<8ibmVKPS6Oq@nDjU#CciGw4X&?$%PX|KT~#YVjHI1(J< zO+p&JGFX@+!iKER0zMlEUbG{k7=0Ey_u^mbv#^b2Xb}J=3-YvHi%+i;)tnR~&teA! zYubddB=LVa*q&@}8L}HQgXJPm>qS#v%%c=Etxp379iP-8xH_!xfG1M)8IlW1^yEdK zAq7qA?cg9*l$^rApfdA-AeWRf2x_S*xlv%Y8|9-Q>o5QXkbtcLQX#6Bc8-EAK=KVJ z*&Z`84042&VNejC$qo#1gycklnMBeisR^_;8b_IsOoFz61|(XrX0VEeyz^Sz=Z=diKG=uGpuSwDGE$)h}UDFr2v{l zC|?282>B)+VjD&bj7(YvLEaX^>XcRRPchp<_O67ONn;#?ttp z1Yhz9YBR@4u!Tn`CF@awsU@D5vXYCtHw%Bj<%+r;ZW|@@z&%K^?Oh-jk4q(Ts$FOyVI7G&l*WVDWH6 zz)TzU=)Dr>OD`*!AwwVrtV$Hjj9#4H<^tPgMr*+q2>FRfvI{mD4t2bd=s$C|pxG`n zT5Q-`Fxi%57jzg?(IT>PWEbqEOY&d_%n%4r#-zj%8Jh~KYQF7|G(2Xc zjDbY+Nt8yfL$3i`c$Gv)h#H~=&7pZ}NW)=9hC$Mc$56sR0#=GqC}O@5MtIU;Ad&VP zVPHGV$kEZJj4ee+(neN?GB%%JczW^88xl6E)` zf1d?B8fHcGUhSdqFV6xqd6ZEgCtr&wj&8jXG`=({LKNB_3L=Sv`$DjDN9jUnL3lHf zJUBF~G@wRPn82pF^5X*cjbL^i1u(+3AV!kQGie$^4j5cme65(`+Mo-z1X;|Q{}z6w zv%rl3b6hRo2RSy%FN9iRBlm@1HXT9s#pnSS0PZ@}eHZoe!HR?2f``GViVMLxm@=ec zFhfTl5rO>PNfDH!2h4a~wH6{sD`C;xUU;-V5;`wFNki(Xv04nojz`9zwZNW{low1I zL%c(KeYDGET^;l%6yQ zd>TDab!LS|JP8`m98p6_z5#GefFQJsPVo+|){}si6OXW?Aq{gGJ(=mC?}l{Rj2&YG z?gc4@Yl#%C2!5Bn%2K9sa1JIQvcWnH=(c$n(Uzh(AdnU)Ys{j30;HhTb0V!etu#5W&_6CDLiu}zdk z;)p;XS;?t`)Q#ml49qlA)O190A!iPHBQ03c*LXWskU4<%#t8$v2`RVcr94t^#-sxA zQZ0ETvH$o8fJ9YbyqKt`Xjcfi6XYnMghz{HxD(JWK_;PqJ7KS=pew=cQb=|o3=Bw` zf<`&iktUBuExK_K%op#7GEiU#NN!VK1L{Q<4zSuH4+S|kP^oF8B8y^!pyV`CE-?*! zroLz(FmWJa15yT57ZfRp(}BOJqyNUKk&W&F8v%qv8raJ9C9xo>?8ZqjX_T;M%Dxf2 zD6kD?sL!Us42NbD^fo{OmcoJ#`IJT3y#|Vo+!S#G+g=7X*4VN&P&eKvz{^#J0dh_j zgQ8)OwoLi@PHcM_Ek@QF6jBrvjA%iU45S70V2TPRMH9Znwi8v?oPy~Fw!w@H1U|oB zgM2{?hhGh*xn^2H7L<@N*ozF(Y2fd<;N@dImiXXisO+QFU^bzTucOBkIH2WXG%NY7 zlyTrc5X=OU$|nJP>U0x~)N+A;C!S2T)YzbkBWZxx4oAy{Z8k$aSp#+pAyQ((>X8m& zka#LxR1zl5UR*wf2K3q)mqMo_ISOl7)B_8-??gl}6*OFU+tO^a8JQ4jqcmWWOT$d% zP#WwNRpML0%Lqk(nQ;Wbz(!5iqp|-%W?jQ?n8{^7b&WcAz=01H0b{J`<_>$kZNUff}f` zra_$w7O8l*XVP$)kx)P<0d#OY3s8B}ShOx;>0necr$wr$`JRU~Y-Z%6fK0(etWSgn z4Y~_r0i|dPtQZk^PXfBAjM{_UrS}IdZP~cX=HKgG+Bi#sjOM-3$@uJnSCBc0m*u6$kg#ix&>IDrglu45alJY?| zt$;DDy&EA#1>QX}w%H7}Y@z>40~l-W3XDSN~K zo6-!`WRIL1RB1TPSV5q;U?R=}j1){bJT$2_=~QqbxTNz9Y?m3(+h7@hwh~H67*WZV zWFpH;(XsL0@wyQ27 z8f*eKO5}+QmIDkXG8s|KM8;kMX)UmMw+b1zD{HQwU@V=r-Ums6nlTchlCy z?md!MBQ`_Cvw%um3#C7@9is9Y%r6CJ(b8GqRJcRc%s2~L2?blT1&m-VDfy_R1IukM zVqja%5DprQbLEX=EnW^<&Q;FFvAxJ3Gk|$J4Q#s^*^*3-kg_G2S|TK0D;h48j5{G? zAQuyu)fo-6V@eHq<+q}FXVzdV@qi$W=x`83w?^fR=+e3noJWC~K*~o!rZ31SD2Kja z4+XKuzEH6&ftxN{)9A>&|zLlH$){f34kD=`zCPM zxEk1wGcpkJR6++m1|#Z9bxb9IgNcN!cy=S$o-=yzT}ht=!x=yVQw6YE1nS7^^kyZ? zFo@|RVgujGc^ZZ@C?QBc-j+Dw*&zLRl_XBQWN_rfi~vdc8w7$aaRLaUW;i)isUUIU zMuD9^Vn@oLN3%^zA8BpIDC#4t0tGW?4xNVGj0}R5J=#D}N%qK%0y}w>P(Y3#vKVxv z{0SP2N>~h>AQ;(hGqf{E_dyPg1cFf{{HeiCfg1y6>ZnD|NW>o-$BTqH(3n>b$4hTO z@@PzZNM~fb&B#E=H^NG5LZ?MFydK_yib6i)t)%@y8uQy;36$4p(Bo@Wyb`o?j`_8q z;W-h^_9FqIfi9%T`=dD}rZa+~c4qAL-fZ+o&T_(tZ*LkWf`B0ApN4vFaC9|d%~mzc zKkj>zbWV!*2;p)e(oMeKlJC5v#4evIhf+OAs50g<7ezTx2!(T<*ep~2BE{x%G-u&W0{ zoh&g$3_z7ohW?Em2qX#_CkD)*O9t8oEM`Gj75gXTpiPZEd<`{4yh#c^2NDQe6kD== zWgY}82u933OkmRqPy#a0%L5LLMG>;}cH^S;HV7Iofn)4rWLq`J_rZwK2le~~!~z|} zw2VenDT**DunWV#x)E%v1{(_U^B|+3j^}}sAp!3;){W5PSzUBMtL_YGQgC8B7rSq!XYpsb5c&m#O4|E8s-|=UJZf_gM24s7!-3S z?7?6i`*7}V8u9Qk4g;A&LJQ0Vsj{dbsE0xvoKo>M_E8kEpT$OjlV(hsCJYqhvtU9< zpp|YxItwP$X%OR9K*nGX1$ry_F9bVvlrDq`tKIZc${0X75Mrwe#z1^1`9Wakj#43I zt0B<0ups4DM-9PSN3q=*sHE1Rw{IfxOiV+`UmnW}Ch#laho^vpU@v8&%b@e((mXMy zVax#aeG9@ML@vbesE5idu`5l`9~YE=7kGVqjBINLxwj%m1gW)RQcOg!cM!ygz>gwfSLgeDJ{9gn&9i zf-)HMMi_ZxBQs^hC>*0;90s&kbm%o2NHY^Q4k~G{;4o-$7I=;VGiQ`xkQ*$two2ws zDrvA7y##}oz%vhLyE4c(0<%FV8enNvx)D@RfJ~==_-OA+h-DP_m0)I#wzEK~Glrz{ zl|TuKtD)r1vjhT2gj$|v8{3ybhQWYL5oBF)ftjv^0V}+EC1g!$90ftZrQiq*Y+nX) zht+^8W5ntS@?|k&&_G-cdTUdA9F1|9lLIxZlXoFP4IdUWDr34sF;BlJ+d z01l9&2Zu}nfx#XO)_Rh+v%q#{(4mk(_Z?1N9u^eUI&|UDY(T!CL5ZdK#EHTJulFG_ z;ux6|9DzNwU=9)zW;7_t@5~4tMYSNoY3xOYL`)Pwl7i$7^Gd+7~OLn2lm(+yI2}hLlQxNsXWQ^W716^ zC}XSN4u63_;BLF6;maUnKwTEhG)a$~x-KRXVOK=U$zC2Uh6Ymmz>U!HFl*V~3<62< zBQ(mrH~BE2_ojgK(JlxQ3cTDowl@PN)bOJ~Jfx*G)KutVAx`2;SgQ+4fWh8dDWX6u zYa&u|>?-0NWJO2%+^{mUz6)e{h-j$;p_E#ZdV>-~X43!y+aoY?;sS!1LZZn6!#f-Z zj3&(F_lJ`JvtYFV!D(V-WLq?7Q9T&Uls8+u@;V(QPf}N2*{4Cg7u+Z?Q%EgwUd7G= zF*tB1p!#7(0iu#bnn>1SYn~&CG%%A$Ey`*gQYNsBNfu1pNdb}Ah}dZ|Dj{{U7aPQa z0yyaLXzF=7F7RDoq>!|WL*A)WS&5Q)XgO&xrclj+;C8z+vfUbJ7e`OU6o3HD&lV)r z1B4N;g^}mAU^_PGbhMX-d;ugk69!dqG(tX2!y8rP54_9mMz&=Gpw%M*yvW~ddbS=O8BYbFfXzi0BT?R54473I(VsSwJiAWG93PvKBIgj7 z?b%>J!eF6UwzM)LY7rCBK@}?_ycAHjVS^0=WJ|O%VgRYb1j%NTDpp3gF<>T;G6r%? zkk0|t7ix$J+$b>9M;i(f8Q1_;iv}X*${_>Wi;U9bl5cwmagGrfsj*M5zX<~s*z_%uiu$RR<-Ksh17E(re) z1!l@9-v*i9L$^T(hKq9OJ@!zLN^8!ez|0tl?r1@-L=AX)AyGh~%vR0=X%7Oq1M%Ocg_ZOalX6I&c~m4QMGrA5Cm2iFW~=c@xQTkcEXBE(7L_#OUA{Nw7T{$koXR z<{$OeL?FSd=%tvERKRf59ueBbYppU(b;Yd@==g`4agg51zRNrh0%-mJ}{F< z2?V*A5IfYgajBF|OM5Xv%At9e1&{+U9!lhj1~Y^UQoMvcEDCBI4t2yv?$f}`8f6e@M0YQtk@J`1Ex9*AP zbXd4WzNwb(0`h3id%;d1Wf-LBV1q#&Oa^`w*eRrhg3Nu?(M*Emj8s8>~r*nKYY zOorT~wImyjz@Q9=5xWnC)MntE+(Z7Dc^D)dWV;z12{e$;gktPw(!zmL$q75eY2jd? zMH#u(Ag>y$qrlW~4rZW!%|byB?MoB%)%60h1)w2d*VY2_E3oNoG#dx7;-N*Mm;95ljgKu`>N*h5MG3++Y2m<`O{ZZxgDOIYZ88k2A={(Jae#~u z@=mI`42qU9vK9d%gQ^1a zP7o~~G%x`pYXS6Oh@yfV4TA+p|5;2H#cNt2IBsS(sEx3W)g{*APS-y z=t`gwD?sMV4DBa68)lOdv_S1W6VhRzX%$a97Bbk3*{vWS2Iw%5%9{YGWDC|TDN<++ zN?^g-gFzZmaGwTd3Q6sFGuf9?J6?n2fg%G1Oj3hXzG)%(vmt*nrA_%WrC~pVheC_0 zLk}jX4jPGi><(z*)k4XXpnN@f?sp;$`x!|48NrRDK^3k9=#F$YD6xhjrh*?!TjqpK zBA#9Y+ki&EozS7orif_3nGhg#XCj#nS`<(OLEc_ykRv3YlQzm_c7(**2sTS7fhK=v z0g5f)%HwP72?(l)kbJolzSWF}f~FE^yWwM79|}Dl2r8>o^HA^(Yaou!fPu7;;l$(u zk{3APxD;mSr&E;;QEiQ)ttM>mC2yGx!U_Wj`8?RlZ0S6xrOf6&4(u!vI&KzJ*K&;1 zLqVAs9;%6ve1jS!hK!>?4+HH9P)V(WXGJSrHvNKS5ZLglz{?9)&?WJkEC7#Ni?%qUIG>@WFx0i zaGfol22Le)6V!h>K!B3~B7zMAvi()`a6)V~m|<|r({p0m&p>~`Y=lq;o<}IPK*fmm z>u@~`#Q!oV`5e$20ZCl01$45IV4nlg?N^QoTG-BL#PS*w8ng0sad~e8+kl3e3P$u; z#HKv8+>k{Xu$iioraZ3=kBRL*Lp@gk>*v&)*CA^_=S53RY04fj(p7$7)auE;k7qc+ zwxA)Ia4?(1<6uOSMQ0(AXzDA?S^5w-?nJ1qt_;KsTb z@ar&lRB&o79SA7xa|rFjeTwy7KxtJ(fu2nRI`lFM0x8HEm0WR49eS9C;OXRPQKw-` zBLN}r*2q9mjUg!c-IUt&{{jbQLMb{sI<(M%zyL?U z1XMjY!=%T75O{!KwkTzus7!+rK!Y->i7v}M*TjcnLLyqqfTZ9;^Y48EPHG_dj*N#w zz7+DFHkd%-rBKJ7HvgqyrjarTGKE|QK_wJ&eiWGWNE-_1Mi~(&Z76^s1}3fI?*u;# z%oLJX9yG_Y=F)g1G3tj9l3H?Uq)nX6IJQ5H7A-hD7Wmj$BmftTno?#o9VmeU6GF+S zLFzj2`gobx9yKx$R9wSS)CPneT^OC9cg`zku;P%f1<5QU30KRt;GXrft!iW#sO3aEN1}bYsClD`og?sIxOLAQD52q^2>(!UGCUJ-QFr;Z|}2A-|gjL`nl!q+}+P zgadqf{tgiGP*WxECR+78oqD!ujeHmk$flv8YC8;6WZ=dqK6}uiKh4vg1_O}eN(dHO zF+d81E~FXiaMXSf^h%y+T5~9a$;-eqzG55K$QQ!)R7e*>CF`l^y9o4ygAQe8o7c#f zLZGc>o$XH0&;lcG1>XsI&d(bmNymrhTHqkiMkZYgBMo|K&@CvQ7ly-N2I0ZXL3kiN z@ic&-r^1#yNH;?@xx>G>8O*k%d^7Yqu*$(H)Y1+XaqM9xtud$}dEh@0%+{oA8f`#% zMGCBvYqYqG^Z$)Yuo+Y@#;uSXqXj^q!@LIi9_Yu>$O3x(1|>Hhti5_p5lu7O$wod1 zw1I@_?%ycT2D3@go2ryMkjyas;EIvV(Kdj^H7E>!$+Mt(IpHx z?c~j1_%Ln-yv>XdRz`nX3XzmUl6Ih4LL`zXz8s24mITp3hqZlML(V2=pq7RlU$@@O zHnWirgOo|4yny2u*beH+B)J@1%xo(g8wzqBi7Nr~H}&L^g2l_gQEOAl$chZom5{SY z`AVoJi{x5XHEWp}qX8{qG2jVkQ@#ikCj*S+zIZ^8WJG1>Y|5e4pf}Bp0yA}#Zv*9uP)M{L z1=7cY?6iU#E>cUH^FA>1M(9f;WB$7xt%LNq3Fg772noCh=l|K zL}j6-rxpxoc^|uXdXC|QnQdI7)4{iibjPJRw&)5eYH6w?Gyntbr_iWqYmv91FUgn*>Ms|g95!c*;j!z1E(ICnQdJ|Adp)$P4cO6 zj|L0u50O8rVv_EOl-JxCFuRU22J(F%#XPIdk6LnQ&Xd4wJlar@%4_)~sHNw| ze<7HyM=c&kgaBJ*X%h-mi7_lH2@GsGcJ$3iiWmt4$wX4pEKg;?7xK^{?N(2CAZNz z(dK{p*7#}lj^fzP&&S6xG(04tb9YBQ^a%dGH-37P*%3eRD}J7STCJlv@ui;{9mNsH zKmO!Du3L?usL(nQ;nAMWTD54}u3e>?8iS>Bjgb1$L7w!>wL+tU`-DYCNAz*A{*zk{ zhzu2f$I-J+Sf8lqn%#o>fJx!mC`kG_Mns-%!a}0EMfp3DA{BXn+x?HD-QThPng0jB zZ-fIN{afg-2C=zq6p^w1n_;$1zlYLPBmTSS?||Vj1{$@)gCj!1!n@+bg!_g^h1tGt z(YJFnfj~%T|Nd_L3?8qz!=63diW&& z+wbbc!y)~d{};&GaJ2s0thiz?-uC}X#??q$K4iEBRxixA2=D(3gsq30PQJIdrwwDj zMhzOb@v~x#Q`<8v_`gxsiy9AhVAg81|2(@!8E3R;{#Se@+~{{(4cg1~M_= z_@Qz6$0_<>&X53=t-rtj);lGCZ4&$F@>kMd>8wisUMu2sM(96un#ib-eolWiJoR^<^$ZFQ4hs=+!=J#b z7wOq2Gzx!8BmGtH8Qe3pOO!`V>1(a@bzRTsuwJ22VO>3H3WD@|I_dZ7cy{g+6x<^; zIyx*e%A;@Oe|$;bQR(j&9RK5AA08Clw@*;de|)?D8@l{`uR)8)LQl@s_}Z9X)%T z)Yq(0Y{ck71G69Mm*eE2(}xQ0JJV`TEBB-s?)z@_d0XOEkrq8u7Cz6G-fwvIfiW}B zxz&d+w~|>6bZo2TZ-Ps=`dScJ11YI5%m|=7AA2 zyLDYO_3^bYXJ^g65w_z-@<6wzmO8PS0=o?SzUub#tNT*cMthyxyr=JiD*g7P+-|2| z{G{_v*LMExHedG`cIw-O?E_MudR})bI=euAry>PRT@zQFa6bFK@q*M-f6m@`;9T6P zi1umak8}I~*>-we7BO zJaNa(kWV2yu7#vL8k;zG)8&N}XKtOnm3-^is1)zD-C*bhrWx%HCx0LEdCQF~UHYB%G5t9Ds>;qk!Vk}#`)&-28FIpa=YG@2Ro-`cdN?un!yaGj49#?S&fU|8lMg5Tnf#|;mD{lq zZoB*=;;y_Izxd>qv&kV}rr-GzGpx|I3|&WTOlX?NrF@~5j>Q~GjPyAYe8e=;+2L^3 zWxsm<`1ZNdE7SG2yQ*~?cKNScK9}Q9$KQxgiN8Da!qDr#2mR1ysXJ~=t|TY#5`#1ixNGY7M3_~4v2B; zJNVhx%kO)favozmdTmV7n3togjLtH8dX3%Ns<$n@Dc_##KgNtYI__xUqY-0(Z=7_gsdl?VD^~8`JE^x?`7nSDbuiRgPH`UMx>;9=HC-<)@eXU5+U5G~~6z zqApb|=8H|XR;e6y^Zu^8iRTjURybPWYK4mxQtCfh)#psViha7ryoi3$w^($snEg*a zUVlDl?b1aC)YlY8MY*e0Wwkxg=rXT)k#Zu3=GfU*PYR_DA8^(?G`@3u*P+3i+m=k~S~RBQkMsv-bUYQ_Id12m z_~QO$*IjQgerJsvQ#zEHbH7aX@nz$3hVL(WeEbj3)pu9lTd>tRp>M`lb+@g4e9gO8 z@ZxPd4j$UpWVLheN5u+V%=9&~Mb+)2lBRZ=na|0|{bRvzt|gWiair`aOdaB^}9Z<=-kUU zs&};?`2q&djDP>`LApini^MnFR4$?TN3Uy1cZX*Ak}~W~x#5;N3G?r{fA3jp*UFB2 zKeb)3R#$1d_f6->Pxn9V%yZz}_+s1oZdvY|tz~QT#iJ{dZXA4kpl99#(I>RwUZ%y5 zVvZDb8a`-Qv56Pve((M0PMN~WpL2z^bi{Ps+VJ{>AyYhjW#H-7_8(Smf;Xpk>5o&wX!C ze(!X!_^_$pU&JJx%e$vf`V3oBFPyqQec{qh4r}vduGuPniP1Ih!-KV>%M9IFucTZ3 zD)aIthsRcZacE(m)OBwsbZyaI*EIKxyL%FDWbCA=Hs!Y0mwEg983TKi@PD!SODpH9 zb6;m27(Ap=mhx{(#NPkw@ti|3!yY#6m-*$EoI4(LSY7AL=pr|dyRF-qv2WqzZ8^_f zsrj~QPi z?nGSvxS;fz@A(|<-e_gH-*xW$?>)0K@!Xe}S#EtFz4pNVMo-JnEaj%JJAd`r&XynV zLk4W{ZZ*AP%fU_iuj}7@Qn%C|=V}E6HLsTa_1W!hhcyof$p2~E+j~8pZzvM#RAteq zvY=(*Ls0~UF@b-c9y$xrR)XJg*hh+i4t)7@v-lGiVL__TTihiOGWX%^ zR~~k9IQMn&o@Zq$oatZNyHf5}qsvXJW%ifv4%|6%VCqSy zPm=;y)?5))s71O?P15;x?KnDaQvX(8!(;l@3LHQF!_*!=`kn)CM~^vl@zP(H>W;X4 z*{#a*vBSqT-ZMC2Ue1jU*8!0V<1$LDs~r>k451(Ra?At$wP<-ui1#pB=I9xQ~aITm9K9&lakL>)O^c*{ zti87F_=TotFN9`|v!*uu5bf1*#P$v;#cM=1?JayC^Q_D=&*`yOAKcp2_RZ=CB@(_I zHDygY@WJuIT;Fa3Hm<3)GUbJ1j)LFZE+6dNVQP`?x+Oyt9Dbqz}uY+thJ`n?T?Z#QR;`*v~n zSBC@3U!FfQBiEzjC#KgOm~m^anAwY;g?hDLcwoc++BsvZ{PMnYVwT_^arsHX?vs0l z4_)3qDP43MOG$^*(>siI=+MKpQR{YboA0*}Hk>omYB09ifkg)je_B(cVy*%M>kO>3 zuf?F-1IL|-9$R{GopP0fb~b4EX!Yg!Z}K0TdvH+9)+#$vhs1vE?6xR(*TI)lALQ`= zYsSJ`>0ehHRk*~FCSK0wlso3(j@s$j;}%97dwum?p|TDQ!oI)n`E6W-`&Dmxmz#6p zX~FK5Zd7!-n(*a!hJLpvmF(TCOUL%tT5f9g;X&_A;q#{+^%?a!zWutoXZ4|ZJ628a zrfapw?@UEqpH>wu1533zcIb<1TvWqDsq-Ua%#Z4SnB*OhEx@OA|1#%Se#!Lh!SR_3 zJ2vWfuG#wmIdg|M%RV&edG59~JU;s$NLre0d{o1`o=xevsifl-2x?w=4hu7lE&d_JLuwwA1 zIoWq@$yGT%!?PwYquVdJ*zJt-sz$pSAMq;oarB;v)4mVaH=UZPJMA` z)ToC0iZ1$;<#wZCCo?@w)qD6Bo;)aHwbT0o>-OGVDz19d4-IwqE^q!~?s+2ppD{n? zCu?54nVI5Hu{$zrGUZT_!3TEk|B<6r$h4$c4fgpRajli4 z&tI>3@a1d{v)6_@6>GcmVZF(zy)&0A^W=Wwk8RcNAJ|vS6h70lZmofi_mjWJzRkPS zaO_%O*_=!2ZFzktJRoexm4sGLZbX*6o5^Qi*~rQ-CmgDCC%$Es>*sTI>^A80Jf|rK zO1}ttvGQl*?Jd_dT2Qe?*B+N{)G$qHTPwW5ts=uFTZ$AfTCjTUvaN+yFV1vty1iV^ zZ{-Y+vh^M^tzpQ!fUxjA?aw?aw=QK?*X%nbQ;Q`w-hU`#`%cfd7JCt#Zd3mJPx|e4==Wmj&oyW3eF?hz_;;bR zH%^RmPRv_+_4Ep*V>gtkSf$?HNrCZWs(o?Udm#4M+$Tx5XT5(seYDg0n3HF(mM-1A zLGcSZuQ7Qx6nonKGt_ruUAiT_%kkG=i0!MIcvV2n{Tt5OXIpJnK!rZ zc)I`B@0&k#?YYB!@9wGTN;$6^?c?yw#T=fYR9wEq`RQ|qWzU}Ap1;6_pXSu)><+C~ zcR3KeJJRP)&TmuhCY1jk(#F4I<>vUhMO}ceO=*^L;4M@O#dTB_6G7bN6%OaT%XR1nz3J z;7q;`yVec3yCwI}qx#(&>a6J)+90;$G%!?peQC#nGjvzfXKuWbs=4+P=yJlFus$Fl*?0TJQjp~qCf2-S*#8JMkxBT`_d)xVU29LbvQ)4FOTh}ry)NSMN zB8#%m)W#k?)F$}nnjVScZq9A?a!*~S-k}|Hjolhhzfa26S6@F}d_Hz+og&X${*JBr zdc@OeUc+9@jmmL&LXUx^uAjUSoPI*k^lUBnuKM)-@q?)wJ$B}PdF#NSb#pF_&p5!n z*(%pOE>$ugSz939w`;=|kA^n*IJ{flBQM8i>f|QWK7D=nvGw!12KReV&@n!H*wXzx&xUJ29ES)y2ulbmEYK!1^#d5XnRO|Nfx%HZq>l)g1_4M^8&(1hAV@CI! z!J9lU&GK{WUb*%qNdKI2HNuCrA5XF>l9J zIn`uKZ0zi=b#!a|+r|u9zhi{gt*}kufg3BHS+L>s;MJK+t!c12@KTx1kB`iXo4e)o z{M~u0JI2R8|1{uR`yJKS%zW~w{A1nNN%wscLL6uN`BuC#ad4)L)$TnB)-7(me)Y%G zr@FO#_RxLKoCg7?cI=#Ov4ret(t6pICy!qiDUz{TLiD$10g2wNmUu6{?=w7AyV%{$ zbKIHa>4}fJ*Itt0(6>UldM!QFA#+OD-fR<}jXM78$(Aic9bN7R9Y69_$Zl@=aCnQg zLv#FzzS|(vm})iZ_UJZtVWyXRN@TrVcS1^z$7?R#d@`_5^4Hbdo2*SK?AUOly=9_E-I-<$b6w`w3IZL%J!vg9|Jy-NrrFCD-EqC1N95`g!BtMUC{)1j; zO}c;E>6Pp6d`(WSXc91UvR|_agAZ0aJ0$PB{y)lnzOnVX$6)V4KTRVx)C+YleR$-L z=hd`_b9AhlJxBAr4#V&7bDdW$L*eSHue>dw8}47|V&$)iCm)rc;9kzK~&fy~;2A!IFbpFW8zb<|K=nxRtv+dyW z1xt3i)8}~uw?%%ZkA9h$>B5@Nm%3hC|NXA{5{UW;G zmwU^G#Z}4PDEMXLgmK36yUMrL?kb%yY|Wi>@y+W0@!g;6Tx4t>_m#U{3f|q=#N(_v z|bL!^LUmUadt~$7Mw~yV29s3l%=6*=hsaE}Z_{6w-Ex%Z_ulKYXZG0p4 zmwaAc)4lJ+bgdWHYaXBLOWTYiDt4S*s?6NQeTVe^G;96VQR{nD2)h>j{&w%VmmeNE zu)gE{zHR!nEnZ=SQ{y99bxGmxdK}TdStocu%@%ZdR-u@kHQyDvIyuhRbx|>mD<3{=)yAc(8da&mMr)Bv&#G22QbiA*- z>U#Ww$D3bu53F3hX5ZOMZ_M}J4s?I+{5sd(F`vy9GZ%XPeEEa)_41w&%&VW2TG+1e z{l4ZdL8m=FcMEAdxmr2Lh}XrtkD2LT_j*+0#)dnyrd(WAbW^$NKEJ{r6^iN6vRP!A z$`cB^t*SMC=C0MtH<|M^?EWm-yZ#WbOWMoM7hY6s+@^xB=i*s;6EgV!9v2)GoPK2Q zEu%A=4|lJSvqQG2TdMCd9IWtoQoWL^yuHfgo_6a?|M*ikw)W3ee@*lYZNt=%-_N=g zPG6_in(g1p?5S49J!y1~8R^Cy&9!f*Pu-P!N|ZJQm_PRqS$99$6!pR{^;o4xL*_28 z+-SSlR!z-8d?B)7uOhSkEUIhvy1C;#kU;{21(DP4Cx z-O@R{;)y!}wdUT7b6=3N^6$ah4mFHUz5g!pa4z36i`QI^so3_$?p@uN-ke#kaqY&T z2P^g3{A<*sa&h_nO$Tai>3C}Rt=D%7znL~|Ug|Xe16Rv6-ZdwiWy*ku3->OG(&gB4 zDD_+2NUu+iWLQTnQ=MW!v{x-UD(?NO$2*rAuu$Bqe!X(2p7O?R)1PrQ%CZ-roE|QLrPoe*03w zr=-JPzh|SHrQftRG|zyrpWe5p_MY_qm}c_H&TTgKSy69l{P6qB^`D+5Yt}3{I%7-r z(+_$sOHEC^z98S`%MNqvma?d;y1#xNO?iz(1hQlB~VnzmLsTW!ACd zQALZ4FPHJ>$U_fjOj*-%O&Ozq@%ougofvl@cb(xAVm7WWx1dy(+k@86J)3LPJ@?+f zay%L`cu~5nKRc(mG){Qevsl~l54&XB@uqs7!|7}8Sofk-m()$qZr`i?;o`}Z+aG)V zE;_=a(LA%4tM@Yf?f1!#Qz~~|uirfVuPilc=GyVw-1*U6x=CTyp7Bp#xQ=YBpnYm`xY1L2r8jJ0$ zvhPCf?Imj;Gq_apc5An~zahh=Vb6r3m%`>omM9z6tY$skw6M@S`!*Npn$Yyv<^=DW z=@yUm`TjA>hdt--mCPQnU#PRh@Z$1-F_R~)eOhDm+ziQ6{HF|mTjXh$xxFhb&+>6Z zr99~d`Zf*E)Zxg09m}-q-Oa;0Xd?2Ty4!cz?v87(%z8fW_l}ACeLggb+?eFC^MHY;mw<+C#a!(cwDxTtasomF``Eslp8nmQKh9@QRnV%0yx&G4B?`~GjjglpP z7mN3K_db5=v~vT;4ay&%GteU_?Bnl?dnfNH?-ICVa?MI#y?(ta`g(WD$O=Dt-)r=& zZ;g!iJ_kGBtN&x$Q}edy8A7HS!_U8O`X#dKu`&l^)|P5KXQipovqr9KcJGecQKHoT zes_~ICYO2{qu=o4c839Z-`3jDb!PG1U8lZ%acEQH-t{JXEXm;9_OoT0_lUs*3nYBG zP;+*`h=)H4-$p(X8gnI6VJL0_n%OtZM_!t_cj`RB)Y@3QS~DupMR*Fv&XSsM~0pp zu{u|faSvw4eOLhf!Z+xs_n+MD-bx~0!nZ2F#Ws*yg!eLk+U$Pts1g==M|7x><7E{)l=WofgGDE9_eZpMI)><$pzk6!Krm6KaPb*up-=c## zPu{&9mP^R;=6L!7mu~2y8y|ISR%l~x*MT+XRNVP7OXbixW6JhwT~}CeZppudAOOdP{YL_;}mAv>orE1l+S!%o8p6)ij)2Jg|`+v%RHgmNeNBnEIUtmh=Rpd|B zEw%0qJCfmd!*^xwUf3%9oZG4L36J}g?iDKgAZPRL3pNhC;^kiB%fp{-DwXke8$YKtB|JMnlM ze?z(#-Ew!jJ3Ia4bBV(X6>e8-M)A_m>J9I_cXa0UuBnSQYKG=n)wRsI3}y0``*eBS zh*BBy-gVq{Dfa04+s*q=UO4fB!;qyDM|Wv=qF}M)ayb(2-o3N3__hZw?s*eRd_2A+ zSHix;^4+U6YDaK}>mC0(varxJr-*bp(`~rjJdZcgi`;*hdj-y66?#);=XRSGb-hCEY@@>vpZhDpi4KnFs z(s`^skdSaaA%CT27fv1xOK=)r$m_IcqG$R!G1(LTI@;^(2W=1EVfQ!wT9tA5&(nhf zn;W9yx5Y18J}=AA(M`3(y?;9fX3|y;y3skI?j+x!yX|(lxX+3HlJn2qq45`^N(`>~ z_Q8sTwnYY3&z0qK?q}<(o3qx`G&4o_X`9u2ep2u-W4U(4B44Ikl(TC#_elf1^W4#H z$g%f(`kasIcWdo`VQ|QW0bBPvZEkq{@dx2}-#(EaVlAn$hI+2Q{ErPvU$LIha9QUS zb-S$hn3%bBgS`&b`?bxmE$ORc%M1>S#`uK0O?O(lpz`wSH(Qw+mbn^}-_O*p=fEA_ zRp)=~F~On2k8Py}Z!72TcOpaXpYKY&8u{bMcSC_9BO{LFzW*{|r_cVo%UWcdS#a0b zg`3lxDqm`mb()*g-E?PWY8z)eZ7NzN&zRmVPQ_)|=y^Z0%RSGpH9L)L=22|Wgt(*K zMi1{(Oj~5H*Sj;#XYI*&!Hp&^7v|Wf)9WvE?X`GUuS>D@2f4Lt zTzb={HI2%B{ruy$cbOHZG8vPyF3dPOXP>*ZpUgWk`dp2pnd_h2+$o{c`D5-gdwFy? z;#_25#Pef@6(?>^Tv|CtnOd8Q$8DN-&#im6aD&tENm+-c)IPdq>e%%{v5?{~O<5QA z%T~Ndy~+C)Y|~1p99CkUuE=-YdtgK+scbQ zKBlboukEk%tm_))S~c$I<}Ph-_`lv(aZmRJugu@7ZOXoBY18+M{k&IqJ#$xUa?jWB ziVW$zB?dCghzzu`MGV5ZhT<);I;PLk|&y8f9-0}xzzR< z>q>czProhWqfvd{S-xib*?0ey=-<)fFnYPQw$uEf!Qb6dsx7>)FB5pOus(jt`<3T@ z7#q6I@Ju$EdJWz7?Z%Tf3*XOg(It6a;@QE$PY&n{pX*?0p>0uhXJnVhXP?Y}t^cD- z{WAZLw#S)G*Sc0-xH|@u{fnEVqUyT+Ie_EPPZA_y|2A) zmDuC4r}V9?{wsSOem(7Vxmz#yINuoB{l~0o3;%cw%Qh_2Ft=)BmY%D2qS~p$#}A)7 ze12HspV^r2_o%XD@sv9q4)l!~^eXoH>Klo#zrPy$ddu}+C*Jfsa5(!K$1d5N93rx{ z&U!Ulu52SSIr^?$RyWcwvbLsT|FZpEzux@o=@fRVRjjjD$;gi}Iihp)o!n<~RHGQdE5;Jgd+@!l`yOVm+r(VoT-)5Dn`Z}? z+of+;_Ac%1@v&fL$1-EKA1xcSpqXn+$JEiQk9q~|+}7f{n|Gyyw$M~ z%Px&B`*ix-nMtRoJZjR%dBFJSa)%Pr>1H)5w>zZrjiv!*-cFl(`st%KkG?!wns~Qf z?1SdH3e-MS=TPHAbvLj5+~TiU)yl2$Ti0R35iMr7IMiZo%CeMc zDWPkduN~B6>Y^oHhoZkHKR@5Zsp{+}DehI52Ka7WX}VP9`LA92*L8keNFQCkMANQK z%632Vyw3hfZ&qe`Ug~@4it7)VzodMv*)#6&q|=jTR9+F()UCI9&EEC({JWPAZaQS5 zZ?^D(8BrH;%jflgB^X3EgkaAKGE5@7$0%gN}yXu3a}`)#AIZ`7Vwbb8hsS zb=#)Cu3N)%O8m!dhrYM}>%3qZ-Y}@Z#dfm`&2?>_DQ`wVACFN)A{N#wT1D6OCLQn^<$>LidaHlybVoWQX>7^lukn~ z4}I&EntN-NJtd8fx9_)^x1(OGc}-gvd{JfIi?SmRY#;A&?EHp_J9qgmh^+gj#2dZm z*EX$+UTwMTZRZ9TvwOI&X!w3+#g&bUe|{Aa8@tjxS?hMYWYg;AOfAlpa2vKc;$W9r z59j1E<=cE^#@9ys%2ryE>4*9MP z?Bw*`V~=}}?j7fRtUpj=R?YW$=e(%T1IH4DX=Rbk*Msb7x0SJ|s)dt(JX>_12D=?DypI6q8Z_muRg?%?^AoP1BAu>l3b24PywEk}c&OQsZ4{JN)npWujdPIpyjW0^Nq zE00I(G~Jc8e?07<%lJFhFds|ak02q?I>C6>#M}G?a%@Q}u;a6EbC>L??!YW4?G3)Q zC`3P=_@tx4SJFW-5$jhpv;?GD%;ZTWT6LxIkf{=K7Fs4vx>)MzG?W{AQ+f=|N@mbv z8Fkk0fv!2AAy+!`hfqmjFx!1qoWq{;reW<@)P76w)%kDu5gR~3uFZ9!+Q*Q_t z=MkR2fSc1Ac2%EDiA>;Ts>|2x-&C#Vg4qPQhte^79Wdp{9fq$5)?+^}sw+exh+K zu>3e>=932A_J#)yiR1=H(?1w0fZIRx02v3L!JSMS()C^D-|ScCbLU&L^nT^O*ZjIy zknIpF%ovuUvXmo}xn29AE`Du@O?1t}jh*pe;Dpo}H7v9f?=aMd&P=SM@P6g_aY|T^ z#DhPkRbiWvW#$t&X*sD{KEZqlGyF|O@~eqLSh;M{ypFC73VjoGZT}1%_oDB*f4p(w zU^-8D7xvtdU8EJ(wlRKHv&{Mn!T>*(T3lyDSJvR8yPmqBHl@w!pn|at{#2!YR zvK$vTV)Tg;G3dkJzuaYFkv!U0*VHO1Y-@C59|>T4vh?FZS05#uCo`ZXFu($|Ydi29 zDip`O*3kZdnIEp(qUik^cB$)aZT;&ztT+zMxawJ-PHLQz#yFoTxB2Lyi>FW;{r(;; zwe!K0B0ex9sxaB!=9H{%PyZ5Q7{70%~eukSUgWk(9pX#elnq zy5v(s!EGFzda`bDyndpYJt1T(UK&%Agh+E&sdeSObGd{jz_YQn?W}R>7Vn@GD%MCD zrvV8`sbT0EW-Qhq;Ep`qSX8c{F;6q5AKth3!bVv165w5XB-y_(R(n8g|4P7w@zd=YhEFbXJVOHq zzD@JZL0dG?p4ykLddK|su8d#HkoWcT?Ob=F*TSSEN6>qq^B~qQjHU%u)nlc6tZpOT zLDmXwnDJ~1d(a7C$2U+&*Qnaj{9P4QM>n<@idZYtGTrwMfaIK_%to1U0TQ|cMYBBJVEf|X z{Ie!^*Q&C=u6pzFhd04q1_~yQ5~Ym1ziyD>F5^$`I>zqA7{?FiDlOV+t%;Uo)rn+T zHHL<=+wE_*9^--gC`q0@q`68*ALf1htYRS9CZXYo- zOazs?MATxcb&KbP#bAar?XN&GBsCe2rexPYO1!heGMDpTk zn-WTr7xP;Hc^4;G%?eSU&3KOaVI8#{6`vA@on@0m=maX1pi3ic;EbWQD=jv2Y>64*8Ul@ zERa<2G$h=&=I?1IRDHY)X?z%beJf0|jQ*fJPZS^IW*UFPQbrX#Xl}sOqUilFm%uNh8PE zGc|GJDH-%h2lbqRSsM=nZvoy@!o+{seQEFG?a|eb2Fh$QD1F{#Tu`B~idb=erEHHj zdP3anVh4`!hbh7&r8}0qDhn1sTHons+#l<8FxAj3MGK(Z5@5`YNWGU6mWlO$Ri%q5 zxhn{h$wUyNLAAkD=5%8AQg94?q9ruM?~h?GMmF;8-XG6^ymApDtH(rsE94HMY^4iV zpJJ9A=DvOB(T6W%78v<%h(EPoe@OvI!`wKi;HT?GpGB16?(9|ZJ%@{P@oGmA8S zrwj2+=(=VzzeGLQc}+{alYo6|s|`l^BWq6nl-BxAykwLhw!j+@{+PWGHMQ0CH4UXy z$)QxWKC^5_eIJX^svT%|ccEzdyP&jQ{PNNQh`Ud)=ku?)xBQEE{HUAA-OtJIMp=ciPJ_0-iYk|>jB&a1qf zT`&p9C4g>|yZZEWT$M9VQI)g!3Z(M3+hL_}6aS`kSOMD_=r3cJ^=lrxc0cT%_x4 zZ<|7nUlO5OCc9A0dbg;elsC8M8t!}&0$qPx>RR zL;~jI+`y?C37J!Kms5E6``_lA4=QWlap4!Zl5^LJAkCG7ZB0Yv6rye_N86M+is0Pf zt6>aN3k~bV;;pdT_ZmuD|NJXAm=d;$nt9KRKF~PF_P3V>?D5O3>g%cVTq4{>soMocG9J+$T!JQsv_Dc;wLJhRgHiQI&G^ z{wG<-u4ZxO>6E(+_8ou9Z=6@G1KA2IJC7WOALEO7vZ9iYSgy$+={?SmqRd=OWV~`{ zeq_6g|3X>wv#Xbpi2rgBteQ1hy{faBSI>eG-4Ik1GSvhXl_HZJa!2$-v;);N#3A%H zB(JDW;|M2P_i8PSEu=Im1V>cnX1vTZr(X{Sc*j(wtKzZ^xsc(RH2B<*4Pqu9(3g;A z+w3iOEA3yq_z5SvwBH2^9|7aEPJAzGy?^DCI^T;j*miko@4GaJzHMCs%#ww>-&5f8 z->wU8oQ15w^qcCT4Tlreok;&ph>5fVdFiLNve z1sUdt7400B9DXhEvt`4D%8T*KLG-FfY$eYXFSUdWSyiI1P<%5TbB9c-1hlG3YN`wi zjWhg}OE50uTB%k_%?kP@9LZ8P+8Bt?2+UXNoe*stDoEpg13TDq9ly;ke9$$tJplg3 zx1Y)}acKSbT^AJ1CFlA3ikOOcp3w=jXoWycX1yX!{3Drn95PfSY1fOAdkGp4U_tJO zm=CsYK1yY%`!*5*J+AEFVO@5gOW)o3)Up}0z=g`R3FbIkxDZ9m)e%URaZ6q(b74p` z$+)ocnU%hKT|XSx>6rK|G?sto${Hxr*F9=prLBKX3&dh&cB9ufo7AVaf@}_%nq#5? zF_)c4?{=f7L{CCMlu+&0t6OXK)k{!S&$brXLuB4>BFDpVzCpD0$@TteUoPyoLu?L4 z5d)_Shr5jtSlZd#e0w^kZc=DTK61r|nu#1%E|R9_F@mCMZ$wFOdw7=VE&p`I#{#cC@-=F~G>#s#(LgZw}RA~wo%4s>> z0RdhRJTu%c;39`uk@bpZGuP0p%qY5_%Z15p>}pS6fw=wQR-T=r=ZP*Q>}Wiha>X2Z zLi^a3mwLedoe8sQhgkij*?qZNdwi;eRizxllAB{c9%5)E1-G^|s82dg`ltmhdrO(F zEk@>PR-GIfvJr;hwLm@drMbweuedY4?`T=@SsesPwL-<9P6)^ZHQR9r|GaNusB_Me z!fBdIA0tA#|~|EHbXWn#PIrgHixYUjXAhs(bDMy z5MLB6VFjQCgbG+*QuK8iNEi9(ICfX>QGt5=AV$w7cRBGKmD|6Y%Tv_!7~#S?e4F=( zTcQs1JCeUet;a+F{EJ7|em#ihAP4tvY6t!ms8gDo)u8tQWOc1?ukI*=mdUzDL-83x zgUWQSnH=YvAc3~9jmDBgB%??H8`V`y8clUgc}-c%8z&n%Gh-YDb6gX|F721jT%ON9 z=KC4pGN_`P_mP$`sipVj%jxHIQh!??AP%DANQF4PG6fJafxgZ%!$MF>@}MmSUvcSl z{G9b#Fb0;qDJrOz(ygSt2lOdTpCdwE64i=I#OpJcG7e|;S7k>oj|UG*xhA7vG)^TN zha*VLC5lw=pWxQ55n6h?%JZ?o)itF(RYV$Mv<;&lu~;Z=x z7MSor&h&2ojD-Gjmn9vZ{_;GZ+DxKV$&9y^^SlJBMIx#iK1yT6ukJa2FVuF;J4gd% z9CusNnBrJ#fyB#$(njGz$M62M{#MVrcpLD{0qlV_jd%H0So%+Wo}QmqbF$pZ6M76> zI`M+C-`0MZG)-b?~5OFrPsx?K5^w zFP~pmP-D+@z2I>t06elC4|`VY8T!L~*2Fb#n>Xvhl}JP1UD`L`($!laXt=BaP6o5= zj>lnR%BQNwpe!`0r>DO@_EQRw@Zgx_1Oks)UjESx&9lDR&^%p~4|~eOaMHv;m*{bW z*vqyd==uXJLMkS0pGso?*0m6OMmQ(r_1}OAxoTj%w@qM3Tv%8+F=--d27!x1aB+gU zw(?td5n+N{P;{L*M76ni2Lqc#&NJ!3tnY{n9jp;`2jl#PFPN$2&POXzE@v{IpBLDY z1zL@wA{qvp1R2Y|8I~^)nXiC21H41bKkvk_$uTSIuTFP};e=!ZeAZXczYHuAp|}*H zqk<&GzyRQU2moiip5LUvMMH)! z`X>NyO}{OG3d{$vOdekltGv&O6YRdy;@ab0i-;#7&vIhl1-_+^2>265Qr>O~whMaA zn&-xo1w!5u<1;#UC*r<;u2DSAZWmwe)BD3;$5&LfJH{ct9yTduGH}uHzyMI2Yu*IC zWm=?g;zuHULxZ4`5E^F1m@gW_iN4rC4I@6(0|StRCAqcGhX#;HfzydsuAZHRo_|1e zIsPP+%2MG=`XoJfV1UW?i~{L)VI+vKuirQ7!V*#53I=L;ztB#5wc?4SHgJHnLn)Dl zKwciWfmyKi=i@^$+(`lHj7rEtjLdl_mUNsL?8z22dKJ z#kT@<@hy~CRMy6NHvLLHvuo*QC$(zR;UMA1wzp$q7$Cnlr!6-q15PAJ^r%0>q;eOR z)>;Up5nfzT8{;=-!R&Rwi{M&71eA`$UEUe8?|UpBg(+Z4Tax>s-cB+psyIj@Ku2q+ zav+O4v)xbxg5j{kROZ$k4@N)-6VmlXJd3g+bHQ3l+e<4NS;vIw7zLPOWg&)n1DhZ| z@MCWGHLTTHy7rlxjRM$c1!PzIUWSD2y=x5IfQ#qm1G6jQBUF;3zpZ7U4z(sBJoh?>nYZ zICL}|v^-IYSs)GUs}TG45=0?69%_8WYG?-{mTWiqCVcZ@3VeomATB((sSqXssi|zK z=)NI_<4@+rJjk0&9_$4?**281jCV%7NLM>B$NM1BF1Y$h?l2Y6pawGIAlV9!7SH5& zAZnI_Y^qYV-fZ(!F(^kMF?ugwh9urA`FwVe$x}8U=$QEM`_wF#zc7>i$?&Z&GDDY^ z?BEPK!W^~t!|B(|$xw)j$V$!B9_*1?i(j4}j4Ff5jlanuorWF3{%JtFZMcNhfh}^9 zO2(faJqKRC($>(`L?QgJ3zFlZ!gFIKQy6yggXg!Y1w1OUn7RypGhm)VudqRJC4)QO zm@1#nwQ%k&7&6XjS^3I8^}D8h#YsZc>rJ5Y?GV`)(4-_lja!M`@!dc2@R1_M!FMFk z8LVmPTER3k6#rWC+J}qG!u(9`fpRLPf%_zOTaiQmj|!Ima>M^s#`3>h8TtPb*#Fy3 zkpBeOEdRx?xp@BF=HdR|@oV;fZ2G_B*Z=YOf55L<|1~E3|N3j5|G{6IJ3BZ#{ingU z{b#V9JRQv}-TvuyCuV0SOJ>*q7iBYhFnj(_F#A95O8k#7`@g$1@qYoc|MflmYgYMx zV0MK+vlr05!G+E6ifLQ&F#8v z_>MtBma#~yJR&g}(rdKT_wNcydy524_vg)z-+P@e zT=RCh?U+g9S)yNqn=0uKuIts7FqzRJCPsMFlb9a?z$_zBl-)9kwb zRXOa~{B!viHp1>y^Mg}SJd^z6t--N5Gu zi(WaSK2Gz#1-~5649`B#q^J2c^tFNC#~Ys};8l_e{G5tH*WtEBz^aGNd-hklApUOw zF7&*}+2P%Bp)p4wSuf%(pb;Jn;vno6LRg`Qv(M^1=YnwgqY_zZK+FU}^cdx7#PbmA z4)IPI3@yzB%ZD%`3=YWk3KC$~GwM}TpL8wzZ~2PsAlwpY_R&{RU$EiSu@lgWBn4dgWltO71YN5`)}5QJ3-|D~o&$qvWt-jO1PXT;0o<6Il?csUi zA;YlIG|@iM0?}g8qS5q7+yuL>uQLby(ZqyQgsjpfWobtUnI^w)yo?|S?ycbKnmX`>ghOPi=*Gt>|lAg8&*GEAgtw?>(wE_uK* zpE0?uEVb_8Kj9mL>7T87&um|eL89v_2IKkk!utM+m(D-PylmJT&=(@TDs~@X_Rc54 z0u0LZWkO<(7BV(&2XA*THGjn%0wKrO98A0&gu268Gu=#WE*=i!s3=@myCDh>>+ap}pO#|X z#Hq~*j`en$6H8~B&^m%{t(T2qKs@ z9cnj(eZz#yA1KduDrR82StGbr?Iid(n~Oz|(X$FhK_2DyuEP|QB!eO^-2mr`(U#Ew zR%W*n7;luURhFyQj5F^aMNuMQVfw+Z2sh_uFX?x@TyC>@1WK;#-hW}pOTO!kO9b0| zyAt)?LkYQnpuT?)@qUu0@B1gf_IpDbvGa=ZcY#X{7HqTm$UM433MeIM)GWd2lpRXy zSs8`=<;XYYAK7)d?DJKAf4-x~|DBV34-C*g5(!Fcu8|;NOXBJ@!pks%$A5OPm?LimIgj`m;3`UCu38oZl~@%i1%IEZ*OpcwPLy1<&P@tY$1cb%3Nq5MxtMW ze;auDlR}K~Y;ID1!r+H~oL>p5-FORRksl2k&!SD3oWb<#5bx!~%ZB>$A{_;F9wD2u z*bU_f*St@&9%``3k+SnA(0u&}p8-n9tmt$IzrK7&S4>T1 zJrQ6F#K{@Bh3j=2l>p>XlfcHk+MAwQ{zg7jPi{s}H<_lzW^eO(@i|nB2}pR31_&M550Y)Lg>yOkV)|`(?$W9{pNh=`Z8rE9 z`Ds7(*$GB}=J7R~<;OlX!G>XBr&&%Ts{?}N;iP>A+Whtla;L-?R=@liL)i-#!Cn30 zZ<*=z1O>j5s|Ov!k*&V2N73nQVl2UtrX4#(6% z-m2|D?vgl+*zude3q7f%K}Iy1sqW@H_Y}9ozTe#!c2w}wV-a}AlO!GPES4|_o7B$V zW!gbprG*K#f`(1qZng<=*0OS@;cb0=H*Da=(1VW1gWAy^;s$e-#?v;L+^`bX%u*lg zQ`PlE(tD}xkVqb#90l~Vbh6B5%dsoIqiSVF+8&kfBMo4c@p-y`UTC6@=%2f#XXUnP z=Ts|U4%sm-Xf-4Z)_u7CgyODaN321@ByxI;BdHJ<;DBv2n7?CTq51~& z0!HjZ^Y$NaOF*1jSk1L%=EruIy=l&0Ut!Bj7LKMVu{RFSe}rMsacZXI7UX4AS^Nn^ zl_u-u;#?;YvXCB(R8I_zs6_|TaSL8W79Yslx7 z+J_WAa1D0ui;)qCHh_zLF}l>8>w7$sy$QQp^kfo7bZ~Sc&YyBoGQuTbSXiANSC`|y z=~nD;Z~5ux+)R5Kf00Dqympm7{QH41l;F3_4pyPKIHUTu*ul8+#VBe@8$MK`nVGMtocxxYeXbm{W_%aN z$)AMCbkU-Wj55`_*3EnmPpeFhUqW9q-BFb{aiZ2p(GnE}N+xKBtr?Tj7=ujFXaJxV z7(o_t&KUw^UcTPG)MDTR)LBjq!^dNyq2{pEp4S1B|bvdO2XP6Gc*3$9EvA`Tio4=0oKJ_9{^cSNh!80 zz-%{^f_@8`^67Qh__L2=%^{=x>63>@|x7|{OGBR@TE13smL9(4U z9*l+v|KMMDe^$}|5?LxN!V>RTO(8qN!Yslq*#MLG*?^kAvSVSWx_&X=;CWt)t!_$n z^w&ZjtZ`+g_}N*0nLgHpR1MG46R-uUqqFR^zHWb_5E41t)fHgnAjkQ zNYC~B@JZibm=fTg`(#Eg=f4?5^gFI2l`S)ZaL2PBN423Kc)xX)1V zv<;H2Z=-La1$-1e_BGtGD-552iA*td83V7-)2Xa+($m@4+2w81vB#S`t*&!Nl@q2F zwB%ajbNtNv`+uh6p5qG$yNQC_FZ^wFgn*&$666WdpF9qEEI;-UwP|zi#ct>CvMnCy zzCioU>qmedh>x=qba9!}QPQ!sLXja=CS!%JY-v3rANYHiDIwpH`15yO7H@9eckag@ z=s8HiboNqWtttZeZTi5c3sWBK)Fi25Lu~K~ewZ``4(kNizBgLy+zjiQq()DKB4j7X z`vDwL1Vj|q&Ql{DFV`n>VK3_FyGC>J`PZ83>nSEU(a^{-VZmpURTzKl!%uLWs=RGE zs}8HqXPJ`e%@6O~TFmAQg&ma%Jo_N|umX+?;&MaDAH*G>}__Db-z< zGDH?EHb}{RVmUw9n@nOeIBNh%q5Dqpz`xCp+xm{@YY(q4FRwp@G%(A_SHv3RDCVmf z3XB5fhqv^?s_(GhNV-wC`xysfGPs3iq&@-2_5tPsNt6@;lqz~VZI1{kOQmg#w>)Uh z0b%L`Ry#EZcQCSl?C;ewHm(O|?2GtS4bn=cTh%PJGQA+mQ)YO<%WmDWl;}B`1dTCD z8SqOpKEFSgA2^(CbUi@KZAz5g@VrKVq&Udk(vrY&UH~UKvh#Be_mP3r?ZDC}F6lMA6q6H@EK1^Yb zGC2H9tqWjYdA+JqK21GcRUwzZm-PpvHBN(Xt4O^DZ3fIiz$#br*ytqXN#U=_l4Y1*+fy1I&W zuVP4vh&tHTMz>4O9?C|EBGrNrpxee*-Vxx>(RR{tN9qQM5kbSys$#k4OQ6!HrctB7 zN2^IbKSyZ{{V`ASb~M}w(x5aa65xDO!0+u467v$57Wb9Z@7SiQ}h z^K}N%Ai(l)N--&@D|QY>{x|tPupE6{TT~kCs+?Rc;||vitJl!A-#`7=Hj3*6Bj0u& z;?8>P6ITH+y#T=zQBc#MVc8~jY9ltc_mj@nSSBuu;WYyP7mbHZ`8Olap$&voz`L19 z6}SC!rbsN5kds;?L=g~&is@Y<7aKE*a7Gi)tr0FF!!{uBfDud}ymvqbWI@Cq?1tm6 zKU#jUfWR5gq5MJPk`fH4DbFIEIiQTAkW9j0lw|QpQ2MhgfQ~des-pY@l3lFED6Nsn z!k`&(OMN_1^#C{HMMnpQ62$dT&!W!o)Mg1AKK>;9{XTt}_3pljM@2^C4h`kPzXM>nIDk!nK2hUCW7loB%OZ>St(h?r9W^8@TD#C=>NmOFrXS#=zW~a}=;OuszE{W# zHXEFZ`QbJfo<%-hAU8Q10K}$A5Xjtx6F}CWUfIeFd%mXXe+1fySbzp>nV48NlO_2IKY48Ln9;BF}WnX;n-ioH1UQIl> zDH0utPQ=5_Y%1>ZOU-&>`Sj!>JIt)UL1qh26&qyeM|p&WB^hL?Ik^%xxGcvVJK{PB z51~g)CDkGkEd*j78UfpXAr(h|`r?1JOtv1P1bn5B6wkwnIE%XckX$ua( zzb@;<{;Ij@xlO8l2p%7)^);N>KbN{DMoP~^FhNeu9n~UR=(|_9#6&>?iggo;mVVtZ zLS1r4E{Fmm!paQB;=841LHz~DM&wf<{-~u02@m!Pkgoltz5NP~eI+R@)nB^TU~(2VdycU4777!E&F$$5L?>|n!f!l_TZRWd000>M%F&g?4 z+0E4_1L=;I4NuvX%rU>ii)(@OD?D5}0$oPi!jLKiF+WNKlUgVUnc?iJ{fgx$h(Trk ztx+U++aQ*5N%#7Lz zq%36G7M=#wF+Uc{-wSdtf~W1eD;VCjt=&W9dvtCt&IXl#_WUM!u_R=lt86+>&MCbU z{Crxlqd0{hEMb%trmsl5dk!6uv_}U;L!)+jc6yziGIH#Y?Vop5M*$b2vR_ui zffv!Y1TW@J$vwc2XE=d#ox}X)$`i}@h~Bxu)bwp8E&UxS0&!0R+l~jH+a_p-ZE`8T zxTL3I1Y2%3V*It;=aYdTwO9;6sx0oJ;*eeyo8n#=5aPO4qPys?J2w|NE}%pPiABw> zB`VS6M4}c3OT9i^z%jvG@2fSK z;NDA`y_3@QEfIN@*7L)zNaeuLQ@Xn~7EcSnS$?0b!PW82pG(8zB}@`I0k5vJr+v zgk9If+VVxJbLS6qENvy5Yj7ykXr8|%`Y-nvHXcsqlJ;@22-+Vou?9%hT77N1QGx2Gy6VotMm%HHHD`jiJy`IGnWwE zNx2+}LjpPlXQl<{o1k{u+o(UtFVv!kP|1TiF!aV$OPV#*NRUYlH7VGAp=d=y07vt9 z1dQd_84Ru~k%ss-RIMm3+`?SclDi@U&%kpE@-i-e@;eg@jq&zSeKn#_0G`5!qSM@0 z76CdRX#-N1YXYne>~LABxzgx6Bt#@b0jG6)#;Ql;s2K8LJj;&j!Pnj8M znE$pVn3k5#f}SS!8D^e4m&Cmny*a%oMhLgo_WFMU*P!eF)JIcU!yyWz20nu6`P`!o z+DrewDk+=wJf(13$NSQBUVneWctIN%Ps;LIaTM~77xZ!xxaaeHP`f@6%0vgK@F|^y zPt-vN+_u2|ev1ex+P?%n%7RRSFlguB+{L>#p2Ld12)9(58w~!d-=n-8(XmkRch^vA zf^j{2COUP?8rww*}@U73dU!77$R)rR=yuY6M1B#Cis)6|W zce~PQD?~G!Qx8GETybwg9ED;VjWal-pR7_`ho|Pfr0;Lrlw+;niCWMOVstmCH*4T+ zS#RSa8X@C)dhw6p#HB8jIg%Ou5_Hoqn7{9mVi>_u8$mHHe9-GIttirdm`s>`!dWPS z@MKZ7QBk90#-^F1iZwHnQ`u_IIT{XHlZcu6@9_eUq}Rkd8~kpSCk1d) zKDqljl6>!wVjJQwC@@BQKA3V6Z1g%`j2F4JjdhJP19Mtt-h2od1y@f|&x5YE!D?~y zyp^@(UB%f`)0%ZUm_fCq9TyJf%iRr4P(@{+tkHoui|BQ=Ah%-Pv1Mn@g#&%de+q20 z9#<|EefMR#Yn1=Q;LFCJ=M2SR1v*iRcQ(q9MNA~^BZ+zJ4V_9B)pG@l`q{xSYrTd+ zVkwbK@1x&XuylM(Wbz;#!yr8!D!232tuR1;lbc6P&A3a6v97<;+8I?ZCdfuki-dLKIfuL z)c#DB29N{OG$* z@(SR$<{xuv-m&WG^MbyXyHfDl@fz4r-1y6sPvwEdHsm`bDMox?TcVat&488(YagjN zgk+ioOa;aQQ-JITx6jj8drf;b1M@=LLbJOz&MnTZx6K1K-!r0z0G=7w?6al?)3{-; zXh9@sf<(So-f}(*9vi|b!dk*DLO((#J_FCL6JX+SG*S`oVWLtZ+ISX$v6sPR*P3hV zk?VL`BHy^)xP9U+Z_vT3_c$?O$X53+<{si8Zp&2~`ep4(T?9Hq|!6V@5tIIP}q&R0+5gxpH*OAGMNalAV&u z5>~9nR}@~T^O(%21EfauyA-=jLS%_()N!pNc&?FMs@rroSjHGhKcq)rMs5|K3W{O| zFu$>5($w6Lx|0p$`b8T^xs#z~gYO}5WyexuEa+;e<0jo&G94)A)5?ws@6GS&?~BTH zhtd?Xc;xaa<1G@8EhpR0=3erN`GXHW6CRT0 z26(@-;P)WAczz`_sbQ~K_D_ptVKU_zJa-XzxU!?Yyaz`tccSQVyOncK_2oGJ*6vPk zO?4CU=dnlP-`5t#7|h2}_PLM4(9;xf8W=<1@Lu081^_Wexhng9;8;P&Q%}2gnM`tT z_pb7nZ42VulBe){%W)TNx@~6N4ft?B3&JS{2cNG${H%%@c;yds-}!8sIi(V?V?ZnTs<0{5f<5R@U}&LgfWo@lxfCep*>%U=rIv>Fs`} zOi3z2oleuJP^)a!PFLL2$+V{J!y+xoTBLZqB(zw%Xcs491QW`M>o;do9-Y7Y_kv@f z=cFo}rkNHUyxj$7S2P_PWh|^LoDimnZ@$n25xhT8PoCV?+)2z9!oAv+=1S;kUu`)2 zj|_VT0Q&fPg?(^V!F|8*o|`sN_p*PjQ?PB|+d*C0Rm;|oHP6U_c2v79!!v1B!r5{g z$lz(9WinQnt}bWX4e|+n9vmm~efBNA0DU@7H6-Iab4@ew5TgIa6{~+gq~-)g{b|N) zfH#*|wX0PB4-MulQZOSUr#kWac<_!FePi?oWV?a&{v}`)fuDR}$Qy}kDRkCl%Vf_(D7QawmyyA1LrlL&n>lx(h`Vc?exN!jwzv0| z+20!XhI!8O2KP-0#ht^<@mn_%#_>ch95jU)cM z#Wjm{bUDxd&o1q*tm0L$*Y_UqO8K*#)6DzKmLSj&_LCwf_Q7%OeW1pz1++p$v;;U1 z?-SY=^Ft+&O2ApMa3Ic9+(^u{+(dcjB#l^Q!7kuYyF(}HR%=>(m`1Xe)OeB(er4>Z zR;W-FNmcsfReCV0i>dEJ{J{{Ed0?~2)yyV+XGPgTxYMsR$K7F4KB#0Hfuh352-6kF=rwAE zoH(zTIkUXRrNXse~8k5r$B``L-caKFq zgPK3abwZJD=L|DLOb;+ZcBew4bR`|!)j!nmeHbFK5<*ydDssO zk}~ZOWoBgH(qNYi-wxqlD`lE;;DJ4J`D2*u4;Ln8uC82b-8Gy(h#??Q($u12tfK-* z-0f3D7zv*MK_1IWH@u=`9fu-thh znK}}IJ|vHAD7pj>b6Z1=M6!x5N-K4%`6Crv(@o7YPc7H1`f*hOikXU|dVWbey*^+6 zKJNGCrT(-7Hvmo=s?U@7Ke+vMoGxD?L3;K44ZC1#uB9}R;%)@L)s(o?`}9ixP@Z4x zzDfNjhcT%T>RaURgYEcm^i`R3f$?94zr4Wd1mv6-E{5xDMc3dW53&Q(E)+qeVR0AI z&Sd@>!ecDgxb8sswT|7LxvMVpM_9CszL62oR_L7A^26KdeKvR+fMlDbFxh=JRDo?P zbVI+Y?mNr48WqN=fKOd?9kOK1<)1+5~v6vfMnem&OdbEu!#J< z-c7#PYHXwMh(YUzAy8^f4l~=$(7I}%*Ase%U;a z;g#{^a&LL+^+u|o;E?`feK5)I;QeZcsWa9C64k7kA>R;L_`_+mGUYdwe_zxoHA*2y z%rp$uD+Lu2@=Wf6M6aBlvQc~z`-#K|fWT6FX|&BY_W;#9|Egj97u64NB_iHkl+8sw z{ncW1e0Mk}r&R~bdQbu8D_n)l-07X|+#$Tmj;4+w2~50})|ORXI!t%KsF zt$*4GWea6zSI?YJJykQ`Xresbj-z`i?3b9jv|@UvznV{%TFtDy`F(0@8?J_#UzCAS zIn6q5+m;wtr(D^hYDmDhd647@AsMY8Z^5L(tkOI}LhvS}L9K9qko$2nx#^wc6P%W6 z+Uho4VCTU$$ucjlcp~;04V@0f`Fak3j(`gLPMXYxJ0nP88qDdX=p-Pd!*OIqjwCw_ zO)Tm@6rp_Tc5qU<*ZDy09GZJeq)alHuF!s0R^LE$_{D?-vEHTV2EM9MHTfO%{(wGN z_Y<6Y_Y~iMAun>-<5=epQTpd=D8KhLOjnE&$WAA5%MimaZ<~q6^;tD$`LEUu`=|H; z@NlopC}d>xT;@R@FsagjABBe*r^76{MB28im)sC8xYyC$Oyo{_`6_>NwEWenX$TL# zPDDetMewq}9(9Ey1YF<~l#HLwE*u|WG`ZM#!3*Ab>lKuzvT>%>O=_O8j$$Idd9GA!rS)`5K>A5ZVPT$}{_8&2 z{8d`oVrToO<}x|t{5+&@JxTcQTS0RbvBN+=1p|{j(ZuoIi8UvO)w=31HeK$Yjnw@$4xKA^C68S-5kcY#RyVmyTuHOtj#?^eNXfQT~j(v$(QU2x;eO0#};GD z3z<0%qog*&AXV#M_4bIfp~t8QXuhVUc=X53TVxxOwrv9Iv*b zKLPmM=!~BS7n_~8t;oAKA1D5_`KSL9#6EexkL=%!4sHj>glv`)y!Rq-NlAayVB%4{MMRFU-dhj=Y$bMT-x$o<_?09xLW1doF<$n+Q#6o9TbkvfOqyQNyI+5&{GPXXnkmFgIWCl)t zPO3tOGTpFma^t>-#R5u!E6k)$Msl0jeBh;x2r9$BR``M2&g_?55k z)m2Y#T9}ttC}df-b`fC^tVO&8W1+NENG`I?Eped0Pu`hG%X5VMyw90;7Mr<$_eKH% zR=qWCyXy^-OTj-%{P3y9N|EjZ{^b;G=1Uj%Vv`l{^_PmpU~P67l9KvBsYqkiDDh4Y z7uSi_D3oRykrE&_NRv5vhbx)29$@#%nA|i1lx6>el&E+5YX6D^D0)lhlxb4@YuyK| zp!)7~mEhLC`hLIm9Npqhe?=h*@|6(zN$SXq`iKx@CqnrQDWL#+#wG5WbJfNv1j#6E zp0UC*PU$QCY=1&dMF4A8Q0E)m;ap<+%6&lX+tSiZKdaF7A|T;b3QD7Db3eJkF`j0ZdNC<$@%KUaj)D%x zdbqpdo)KMLbb6h;aW7U&ac4D5Ek{@8yh`lD;N?-SykJum`J=YsFG^iQSam0iL+ViE z=4}W=8Bq1#m($_GvFK+wxm5mH8&~1No;hEF6xfP3a8Jay#{r@K#=>l`T!nWPVEXpS zJ`XHZs%5li`*y>HQ?@PmbE??p;yX98)qz5`6ZwT?mts?baoY+X(S}tbiW=YXo0{hs z3&n0=nCNE2IQQc!hcV-&$>PA_WM)|w#NJE7-0AskOz(qjaxlD-XAb;{g=F`a;JV_X z$O?kv=hcY1nT)2PWGTp@OiV<-Qt&ULd8;@ESa^7 zO*9bO5UBxn;Q|P0)`65mO}}ftU}w0j`Eu)t4toDQBh8@E@|3|>sosP2pvGY&yHfac zJFU@vc*m*rDfe1S?8U^F5uocc9yH2~IO74S?d`Jrg2SAF(FFnwWmKkxJ!B@J(45i% zqDwG(q*EAuizZ{rV_|uVFUCr#0Feo&$Bk;3cH}`ej8X7e6k;MeI$f9GoeCL53jQ6& zL@+C8TQ+S_wV{Y61P2aV0&gQrv~Auvpi&%=54PIR?8-8xelnZrnh0^QZ3!iAZSceA=|W;4o1LJ= zvPK%bx=b5ZRU8=SKu9yrI^IM>lHfLfYLx*F zF>2c-j%knqE7~Ln|HJOG&OEW7Lz55bslM5NHILt;H8Y_;FP)u>;x;EzO#@;H2r6;X z8It7H_PWgxLn`aVdQks;-9OM_-QR0_@s4XdC7>f!=6uM%cBb~^QGLqhI)4i81`UIztV70cV31OauYZuqZG^0(uD=)~EptT_5D^Z}n-vc% z^+0rxIk}#U(=X+rw zWcj)u%UR4oHFZ&D-oDfRi+_qyg9W%sbv;19L?}+v-j@p_Jdan<*v!?e^0MxEz;RBhBL|-n=I?%=-Om%Vk*ApqpKvvJO{ z9Gx$yVfMd*btbSI@OdXeKKHW=I64QXIlrGYT=z%F?}Dy<@{87IKJu3aam8yrDsaxv zFER8q`RJcoacQ@`{T6o4sbOjfyqdf_Yg#PucY;KGCEU7R0I=C-1GCo;JTVg1_1F6l zqRPEImW-g=bJ+xji48nS4Yv@Rvpkr&z{wNabF~JGl9a_E=>TC^fT*DY#Bk_9+mA{y zE?xzRrlo^$p`{4VXZsGZ7_EqTD@ObUzO3mQODCk`Kp6FfoW z1=m2SN?`p>R^jAL)F{lPWk${9(1q9l_lfsBZY6Q-+DHBLdCBq&qj!jZ_!a;9dMb#qk&Zzy|d3CgN^vvQkG%?2t7?kXW%(ec0@SuLt zvzhMU8(6TpB+spsfI+PRikt5K0N?{+A;W*v&7T23mBU4lbt@p~B4~T3%04(wN_y@i zUqUo856$zTnO3k-3!+!6S)Vj*E~Fu_r;ZhI1&##jDY33RQFGzmPvTuPLRX~~chnC&UPeF7j`DY<6(0y%9? zDdnogfS+DcV=_RL6}h|RQ^LJBoI}9vta%vC#wwlYAbM)Iq3n%Md0$dX9;ZQ#`5Q^t zQYA$fU(5*b(w{);U;GM2z7v!+|G=yv0x$AhI4AO<$K2JDZUa*kiW6RVjDxl(Xd$o* zV$7m^0)_7JYPP7s+}y$Pyz(ro_4r0<$R~~-nf)`ZDxMI40s05#Z7}IP?yhFhU-y2! zS9`4TJDG(MXg4&0-2*UDJ0|}DW^d&y>UXE=DHaTU(gKe5$BL1NAP39eoweTp4}Q98 z!irp;-=EkWf&RYpN;y#Q00OZ!W-zSiH|ke@etB~sbLvs~r1}(h7LOs~aX}jV{E=}O znAcRGI&oTO280sW#4=R*p_0-)*jm0nggR5Tfb|FatE{zUXgGLh-%|esb9Za>fkc3* zv!J1Znf|WFRDPWCpw-~H8v|UhW5NeRy(*RkHcuu5wy&MNBruVg-WF`P)NdugHxREm zFYNz~GX4L)-2dt*{qI<&|Hi%hKXsBsbU)fwmLCD=Z)T?dMKdw6FflRy*ew62Q1a)^ zeD`tqsjA`hKlEIKVhGolkZvS>T#g9rY4?L>mKXN1Nx!68)Gj>#(c zq=Fo_F%8R%L1W#Zfq7uquw)7*D&=X4Ry9|zSi4g=lgTyyH5yP!>O&2 z$a27?$StO}cmG>*nI#_Pm{ae9jmEd)zD7g&M41+1;Aqu%dMW+Aly={FL9c< zNuEddYB_OF-bb6E)u~V2xolzqP#o7Aj{7I|67u2jW%4EVG4^5hp_lqf3#*RNDBXB5 z(&$J3-u6#miZpeMPEPyF{IzYWPw#6CrVG7?MpR9@CjGaDLE{eDgs^3ziK5M-{-O=D zDYFH$iK7xftSa;q3<69n^ac1V2% zj=@iLH$67p*KTs3suynTwr|gF@BH@i#o+_(D%$IS*qw|ZXzJGX1NK8r+s@R~)Sz|p z0>A0eRBIX`6aP1t1%^#2;}pv|OP8_h*o9Wp?u_Zo*Nk@o$J__qebCX@QNhu|5z0OD zJ1FCO)R;*6 z@KWKWLde3hLd?QlVW(i^02FT!O7TEqUCLEtt_aj&Y!ZJc%EAaZlC*gw&cY0H%C~TZ zeQt-8b_s7;Zn9nyT_iqKKP0DOn48h59YegB1 zBo||L4pCjo>v+9{-9)|wzj!}QEUg<_X*yNS>V|dehN1oVke-mHkTem0k@TNAT%bs# zNU2DGNQp?3NZLsFh)I+d#xpH}26vOY!R>)uQ>r^Yr%q;=Prt4pO6(NrCzR`J66n0+ zg?2z|SZ?h(wt0u%Wl_2jChx$X z;Ytw2e6l-=?~rXEK~tOMw!l&6-hojE+rcFFaUDfy1W_cE(4@zc-$a4275JinZNiHbjsEdXsRJDYf^+MN+MAW zvF$~fkt(x%{QAKR^@i@Q!GxNmOm!UW9+G*OP3@GLqKqfhb`52;Cv~zwv2xm(e6>H; z)&5|YB%qhwW)`*(^A5jz$#t7v7UMn?`n;iv+9Fk6pqLZRlH|VAcLrftnwHQMJNfW|ZwPPjKg8AD z&4Dtoa?7BNDq|^*LlYyYHBJ+xWt>EAF^8T*JvBusJ%&(gFwY_qm1PViH8s1VV`t+h z$c(*>1Ad2BI7rBgC4^mQuW~ZkK##wszIAdcDy1c;jt%F|sWr!!kaKvnNDI45R=SaO zz5OkJNec5Y!yC*Af|e*y*3wYkQBJL)rB$U4EHl5gSF?olIM_@%rh`)t(%}@=K>iYn z0z!x>6rAf!sI6_i0C6iNNA{Snp{6^I#lO@YW^IC{*{w2F16waE8;$aa&DAY1Fn09J zEhXpTYVxm;j$?==7b+|a3NIpWIpWd4kvpZdr&_=mB9`)8w-B3=SZ6^zG1!X z23e$mKNn!x<{$Qob8MESh?baAjPGN6xiTbP{Oo0YCe5KXGRi(0ggbr1#>75)B*N{2VNxOjX1XTMMmNEQG#ZAKF7`E~!eYVmT%J z%eb7err;Zayqy;+FfRJQ+Qs%|e&Zmgl@L_tSImb^TWeXadZaqbs2>@qMp#Y&YiN+o z3uK0vYFM>?%^)uqiOWSPyaq|(2{E2|*Ixd|Lt2u^XskDi%wuwRAy|LBhNJ%YZTj_k zc8OSJToux7j$aYxS!u*1XqvG&p|J`4ugq}pRz5xyYShqR6?=8P=7Xtf zZ}r}dopowF&y45QHU?gs9E=6b(;t15Wwyrl(!%D}+T4Ka{^#5V;Bd*zq)dTf@+rJi zA|Y1h3&YXxbQ3EQ!S8-F{$fP%?)q!MD>^pYb3ekwD|LuyFHxq-+o^lhW9x@ zt5WIss6tt~gkDTXfHks82!uqi=SkP)9h<(>?ecu5{i)wlvsy-FG z?h%|cu~@iBqT=DpbC2phIdbJhHC!XN^~#kn_6f=RU3_FaN5-du)mYzBrx*9-3Df+Zsq+S$P?fY%r`Mn1QUt5|-P)`xJ4uziBg+_%< zE`#uq>V;e(jfr~-_cwBTj~sNR{?s<2I3f(Tj|RK9j@rEmeOskwP)}LrzV{KZzs8(z zh`)X1Jcd$%f7jM>uyh7l<}o@7Lj-A&Vin;qQv(CUHL00ZRpqa7kQVJqLqnK*6Le!K z6BR8nV;_EXdqZVbR8m#rcS3{)%u;QBg0ZMAZt;dc)U1IL;Zo5q9P4OwAKr!K?Sa#4 z#`cg@(&MwMJsRGECynS0h%s;eM*;&$V$E_}EvU-u}QR7;wTy zTnroAuMb57W0du zsWOZ}JL!)DyCq0~2>7R!?8_1wXOoo}&@yWK(0wEmRMq@n$BM?3WJGs#+S9+{8~raX z;7Alm+KND{8DBy`F@gyc$DM{KH`s1MkqE-huYK+ak(GDHMdnjBL*M8dm6LLRsg1KJ zW;`5LM=Q4@T2)k75b!P8_^hwaZAKwD$lAxLk8OohaFEI=AiA~@z8_gUT@KOG1=cAq zYxjWJSoR_VxASU2MuqM%kcu|tos9fx9{bX^3}MSpt5OU!EM_Xmh4b>7g@=xgmWcKT zfq~%CAE=O2`GuO7rl~BMY;M`S@&GGNJP+Ywwl=`dXuI$zbE1>}Pa{4zF(m_ym~-$h zuCqys*~4&|PE<*yP}#y0t37#Atd13uFlsVV3eg~RF?WE{EFTs8?_}-Kan2kvTmG{r zH?R1ltbBMzx`>n2hMR*cB@8M6w76QlWRi5{mquuIlP(zO&EHviUESh81>#v{ifL%f zkLa0=nuYA`6GJ;wGp5hQfB(kKx_rGr!&5${x)IkkznBJ4T~q%0Lr+b{nQS1met*8_ z7OtJ8qAIbqMBY z5aqJ5DU3=#Y$J(3%mk!@6yoR#7bO3S;RQ%mT||Hs^r5UE{H+Xmtom#rZ47D3@S{HZ zWZGDyRGfYy*~FONZdzc#3zR2uJV(AnN$sB^`Ehbb8&wN2yl?bmJ_p6jtnA~>5FBNy zq{!lOy7U!I6>qWs#unPi?^UzuQ9xbKahDTUlUdy{R~2;XTF>4d!sc01nJ-^bANOUa zPuA;2)0LesrSJ2r=bFviea?B;muTbeOa@>cqjry=bsnPDV6DFOLc*2q-R)^zi?G$V zIggj|;Qa<07|9RDy5Cq^5PwGu*tN%aQ3&$cZAeE=zjanvZW~h^048?SU0PYMEQtm(mRGZjaE_W0Q*`bvi?FHo0y7E9oPP9@T8xwc0qviuqR0YIl;KFyPiEM z;jd%`V)e1Wyx-@d*l@a5-DR(lneJaXP@{EAehxKg$U%Ih0g?>-Y6?Jv?NF@LyZK7KnqchP&+7qVZQB3!d`AcQ1JwN@iH z?6@4u%575byWhEj$Hf@#4(}C%QiQ^mp9N+Ms!)SgrQG!70e$-F=jd(x>!s zy6qdzt;7pk8FJ?@&eA^_$rJxF?(jM^Czst*2pTAUhuFEel@tvNO!yYN8LMdZl$Lbo zR-DRPScaDlM^b%<^QSdiS1@Nl%%=oCzWKW`<`aklVS?jQF-H4@nG;Kt0Oc%HA2TAT zDUugSu6$z?4kQPWoco1Ac7;YEe$UJMeUHw!&W45}^j{Lj1_D|0$^*}m%)>^dj2Qx; zOpSpm{-XJJ?GeM}dcz^|AO}wqtZ?ng)eEW1SlDV9klxc}@13L=2-!&3Vq@-tyg|AX zA_v!(k)@_nN|6XuH8ll6ZQ15Uwb4I#dxgwO%NLUkte}MpGVnh)T7jRBj?s;;1yb~oe?WgLJ2*c0B6n53-sq4{k z%JrE4&BQ5yu-s`AH+44$XY7w}BJW*&s;!!4S>&KHm&DW}jt~MO=(*zEI{Mh6?;b+k)u2 zU68eVS3Ae1zH&`#=~wW zs0>w{_w>NyG`%0e&A>2#Vu5hy;^05v>OlvgiSVx$+obNgp1#nrI)}r0tjb&cWu|tI zm$QxDtR9=h_U};p$GL1Q_cWdMf5WQp^w(30n>qv;#IpO)1RhaE+;d1?we8*RoPY0M zK=2h~@SN)r>yK?aOb!j@j?q{G1L0`VgUt*9R?(kGwf(+cA2^E=yi1@31*h+_drd7Qa_dr!-kc;{OCzXG0Cd-tmYqYXJ( zvr>i52y1}a_txur7P{NnaG26qI>nR-PXC~;{_6$J^Dx&b_pdYWE1 zZozSQU{D=3fYlZS(jdvJQ-N5mS{pO@ZNi{K5y%WCjhjA@d7*3s{eo?Ty@M7wB{jFc zzkL(>eU&vgFJpM9d9828z@dKK8sy`GDRqrBW;u1gaz=u_BwtKjL7q9>NtAcPzjiPr zo96rZDgeXrB-G%xBG%VQtDA#xhXB=iR4mYoOl)yi*I*ft1GgOQwyUKu4J6F^(y zkDT^zV(Jf@vc?*k=D{&$>Y>3LR4xM*e54|zW2F6MtBY(3FsQmOZ4tp z4{KkHJA(xOZ_ll!SFm~6@1Av$*6NV_}^xlur!rl8MY}XfQI~Y3j zU_I54Qr%1V#)fwN;N%oprc^RY@(-}m`rro;A#v9HLH0o;=p316VlR`)40+`lH$G`S zRPu` zH4!sgO+Hk<2P750)q*po+%xC^H}7Ntc_H0$atca{|07{(f3yM@xT`_~vjbHcuwl~D zQet> z>v>TZLv1cWb7XWPdDuctAJJ)k@(>9ZJy)ha6SPHRxP51&Nj}%(kL4C1arRI2%R+?W zq}0}6G#}GTH#ny^;8i{a9}86G(EvcKic6!U^N;?LZyF1>*3gh9misC!4B%MAW#7SM z3_w?_CT$+b+mjmgcJbVY(T5{zb|&F^hmD38+&Fg9fg*f!#9~It4~iYK{|s*fmQ1V_ z$SRwErf%+_Q2!RULlC+VvWB`t$_XVNFEcmoCgIIR%Jv+l&)$apB^P=;mP8M%3t51v zlE0#wRAK_ORw9_JRXJ$rk`P*sp%!R_Mfk<_Rspvq+#3|=rEd4r;V(BHW}?5?68BUN zb>miuxNLcjcH!qvL6GV>x$-1%^T`a7rDSTLQcjtK0y02X# zIz+R0n4*T_LkwSA*1atUvc1c|!I{o+vH!BeJNTA;AT|WL@~QJMum1UfzTdeuuh2P- zXjcP!=WPUQ)y%>OL3JM*a^VuoL0dGe2=4#>r*CQjjg_e%IN*4pkSX|{oN5q=Tf`+l z{_XV^7b<)yBqX9}(4SbWb6|u&=CL2n7!#fJ!2b9peQt=5uh+efvUZ^*;Hj=(05w${ z=(92-1KUVGP^N8{lQ0%|b@*3gV1&`KhGY@&X)<#9^FH&BS@9i2Dp-nf@l8Lz2H(hu z+j`g3&}?YJDd2$sLkwd697%w=@>i4Pgj^X8eKa#5zAP(`@)U>nhQs?(N!&+QhdM&e zFG7vFB0}NbeMM!L(;XWQ6;t;4s)^gUX{wH_QA7F~{@J;K)IU90GU|_lc!L%|wH@5n zR|PSFN@YM zz-~>ciLmna1Q1x0hM#hPMasZyj(v;RGWUrhBdEI2dir`FE{D>MzP7H4f+Csf`?7-t zL7*Y(@nt84{o&jEnQESMX!eGzJ?s+%y1n;#^v?R!`9zmJv=?3Lx3gz-XCzOu!HsV$ zXoNp|ZXlK;y}?;l{SsmQp#i}DY9)fhfF+SL!}bW+y%}XQH6#ikh3O8s(~Q&r zw4c}v$sxR7idueZ#n`~NSF}c++A*UDY5Nl;qcb%}NBnT=zTdPe3DU=9f3kllP&&U3pRB|i5r&Ot1pg{>5> zs+kYRZ`l|c55Vu+(}<>X+PHULeKY^I^C8ffgF|hkg<6OgW4q>|s5y;1- z{Bx4k0e1VRJqi$LMX;PZ*9)*kOJ{(SJe=JI(d7*UM*X)of9XVIMaqY31KF|p;VHXH z5Q35X7c9+buy!YihC!x72dsUWfrDrvZ>*O4YcHTL!76qqSQP)PppXfoSbu`x^h#j3 z7FN63^;=w`9y(q_2H^=&Z_@0AaEP7CKFtvy=+poj1=B&I{RWp&4#I<>e@@Q=*yiTihGH_}jAoAa zz8jn#T{n1rW&A!n2Zu^XMN%mT*7Cb@lETq?Doh8%jn-H%xD4tBhf^6mn?9lv;SN*G zy@a}Cl9dxN9};zih=ZIuO6t1Nto|^Bsk_W=0Jd;o+tK`c{)F3lU?dCqBvzGA_=RHY zs>~8$4AccO(DSHPozfj!8N=Q z@5=8R9uPk6U$nH-_r?6zr-Y|)HSb8T1ZD{U1H(5%&N{!oKC4gw1Tp>Ui0NcMi^(rV zk4kMkE117vUT46B0<@-BZ-auA0==nDw2u&>XU+rq!BJCLsd(;4txrX(qL&e9R@sZD z6S4s%P69f-pWkokpe3OzTM+9s`FC9}TL1)yqa+(%55S&ZiiXu_K{;6le|TM@+PVHb zV}e6p+V4}HknViW2W(iwk|atsOG=i=0ucxFLjQoK6|OBDPzwo~XvPKaN-9^>n%MnI zwpkw!dNYZ!K(7w;zQg*w8<#KvXh+0cdl@V-pA!6tm=2`eG_02qWMinQ?vy{gPEH95 z%iln|mQOh49EkG~y0Aw1*NSkibPn(zvrb5uCKplp4Me?5_E0|%PC9|yTxvN96cMqK zpkYCH%Jh53^Z*zH{tg~9H+wEWhrjA_fgqD$;-Y)QjM;m4@orFQoDCkX;65UNIKRMNohetDtjF6b{MO#h_=cR_?1+5aX)S>MT+7r>1f@=ShG*GO)DMd-x%vvSTJvOcXxcCNma9s!*c%#`v z1dC`tm@Kb64+Iq@Y8w&Y<)_ExQ#@TZz{vA*KhCFVnP=iKU|&*F_w{Q-n?wrO+|NRk zyR0ecFIIe*HJr=Sm%p?aga1!Hv5f%wQ^eq}UeI3!1*VpmKw;)=sjlJ1#KIm&GD8N^ zV}fue#O(9$fI)@hGRcwmH+Dw?wLM3e&rhBTpEyDdhNa zDgAADvl+7Br(FzBulWW7I@8=4`L2u+2ec}XcpBB5-(v#wHDkSMv3e>;xr~JOCQZ-z zl3lW7-YY!S!FC6B2^!8Ysi%*J{jWzq;4)AN_K8m2+_>2u+4Of?USH@wrNM{jG}VC? z)>Fx?71lFbPZCCD%L&vI`U%p9k|C!Tgx%DPN`;DLi)7Nf^8`v!P-txLzrbj4I@l{5 za;%hI#rMf-hkS?N+v3|f*o6=&%uKrNZu_-CF4#OGFJ!f0Se@uvehX(rPb_>LvoD#R*uCK8!R2 zN9(X@#1={&xvvzjj-y+})M9o?h!jn#Dua#2X3xX+;zY@klpRA)*TYnaTpF()=M<#3 z)0aRm%We?gL|=>bK%>Z1Dg&%0Mh~rt#<8RG>+Qd*DbQ3g`Z&GZZf>6rSC7lP(tn^+ z4AtuO`+&#;8f&$e>TunJZW6Z;(-7AXTam5E2J~mOZbk#?9o7x~#=paxhvgE55@8b_ zNm~?%(z%)5W_MZ8H11OMD)mbBYDmQ47{<`bfgk~@_Nw+uH!%Rn0fs;gCDJ94(kU6W zj9QkzhMhypORW8@E6uac{STF=HcMlryP03+Z@9BVX?y24GwtZUU~L0DxBA>LY6mOs zq`a|mdn_;bAJAWT-`L;yd2B39EbLl#txMCJ7ws8I8F3oz8qpew8krgy8a*0i8g(1> z8^s$gOf*o@8e=QQff%F%yhtG@S6N0>8p8M}<%!ygr| zh(~_u-(B_v_6_sW<50U8y)@fo+my|ip0GtB5`%aKz4e;wQNYS7;Ea1>ObX1&f3_aau`~36Wn41?9@cs4G#LB7X~k7uwA= zoy8ucADT|`zyxWDRgp}=M7m|WWvWSAQQnU!p+2CQ5~Rl?@CyB@D1L?G6egSy&mt=g zQx5y=*czD8t8nJlz?DTtNjlQ|#Wk$W!+~DQOi9YZLYE$K zfU*|G3IztP9ckSWP`%sRLU_#0k^+(oVC66UMGBv8Gfi!DO&rjj_PD?>^4Tt!$=EeA zl0}0h7v&bARk5<|`xTj7*|wva45-A|JdtnoJSGuuzWkJWfp2ifhvEp_q8dr#ULSPl z)KY&*VTH4)I?LxJ+N>yi$qLxE4n*5I3vUhx6PPD&bC(1mh8axNNBH6j>%NQ8Xp@`- zNxsndqJB2A4RT5#C!xt=fX1%XK0kdHg*~m1cnzW!3Qd?>i~tqk34wu5 zCSDX8?n~Uzw;4X%;~wz?$ud(e@a8wv5O zIaE(Z9?aNx!cKM>%?*Vl16KOZ5GlT&--eRDJHBon`l|ST{#T)5qu}9)1f_(k4o>HH z3X&Nn2pKTiVwpi>TtJ6}<*cpv$???*();vU1afMnr~kz!qa>thYs)ULrn;n>qic{m zi|XYT#%ANu+Fa;+YMssIkcS#^_TX5!CTzHC#TPjR3JD{j;$vo_3B&Yi*-!TQ+-;n+ z$YG`OwZRg;k*R17EtjB;i6M<4LN-S2xK`x+;oEs*gO7gno2?zZWOC&@Y~XIcuA0lN z(=OK1tn9+6^;Cu;y+9#78162L6%%qzyx8|~BSOG@6x?gMPLG?Leb^x}3;jf7Wi;V^ zt?k(I@Hru~K2YRJAIS&xmHZ|yoTq|;4X;e>M($jc|7F`SC^>KC;rPr=@y*(0BWo|& zhm+7^R&^g?X(Pa=v*+Ky*$#!9RAoG}bv5%zb=a&I&3nA6jj$LaSmA5`LfCnRyLpb5 zwH^wuTp~(YB1Y-X0>Hx*afEX=Y65QJec6V6;At&qIDr1pzt~>FQ=rx?N+36*Vm_3B zvtmliJ4`2+n<@gtabTgHF7*|)^K@nvL!(Q_v@f565zop_-4(x^KtT=ilKO7Fo!Dp| z!DCx?`An}eYMJKs$<^a~kd&W~rd`grUxO@dtZOdF1ky@)67eKIq47FQz__T`#IF&zpOGh{PRy8=4yuKsPm;sx#(ETqdByU&c6GbN(F%4f&%h$6@AEOHG~%u4tBk zH@5OIGLtjE#1{JwANrC`Hwrg0?p8`2$IS`6!t+bM&L?a9BML~34G@SX#oMCBhMD~% z|AyQ?pKg>&cz{mDy;*Vf5#F(~!tx33!1j@?gsXFGq}H5aEC#^@DU$k8=X=4Lyw{em zx>PhlIE{>6gn!xi08`2(aO{)#2hCxdG&~mCMZ%eMjRu+5_r|_HhR(=|d3iEXAM@`v zXpC+o9MP7zN)T5z+`e$UFAu8`Q#;<`yfhL}%bUuJ$OWTn<}MIV`SD3I{ad+kLVi3W zHYJjPo~pa5@qU|nR6t+0@W0#ffp+^oer8oo#ks@ge>;s?rzciE@?Jka)!q4dIgD}^lSXL`Pn@J|GO1WnZ_PmnQO?fM+78$24bSb%ARtcX zzBx$MIvtL6wNu0gpxoYyA54AEDUeZJjwl+KiY(h*gDaX9O z^Zz0X6>BtW#ub?+IzOr<8_NnB`uOT6Jo^3?&{f&x_)8HxxZ(6}&hV3da+MfuaO^1y z7^GMVPbgszK9HiJNs4CAp3*^ddrI&3%J}5(KC92?e}mGG+GJk}*-RMu@+_fqjJ&!` zD_x2!ctKwa+ylnZ4JS+7uJ=%pWuKb+ZI`DSRx`EjTsAlRB!7ltC04cLR5H}Yiz|`Y z+J)UU&0dw1faX6^iHH1s8EABEQUGYYT_|T)4sksEyWY=#?n^lin)8iq&g;*-ulk*r zPiRA_{e63#agk?LobI5HIW2#Uqv$E-sn-vh)1jpwWLjhnpsRP~Mh~9LM1>)5y-Bo5 zc_enjH)TcjS9F$GHHF$zTF|`}hCxFUCoedKD#u0J>MeYz`S`0^am*`Fe8OhgzB@>U za2MhV5cLbZZcSasc*R_E1cwvLdre$_06~;LbdT5DX&TsPF(oFWWw;pq!&yFT6y9?m zU}E%UBSi)O{;o)>JSrWj@%Xx2iOJfZ&OA6(+tafx=oW}O4a129vf$yUn4LcqQj=o2 z!N1=&3ERBFg_49b?sR$|VbMIFpre z5VYwfSZ2B5c&Y;q{~N2D3#u%UP`N0tOfyaJJ3OD7UU50T4;*A*&K}zh@)!=D%iTXR zwM!7Ci$_I$VQSj~x7Q&JOZw!-s)W>1u*KD+C`2aH6<_Y=QrPPL1FPDiA3hhS|y(_QhW^_dAR zRLVOkBfd%-=}x(sr)Q*wG2ohe>robJ8U|ZHGIgfk=e< zExvUbuMW+z9wCUr0qD#~L0cY4EMDh^{bENO--OO)|24%5D?3Vh&WEc;&MRNeZj##C z&hF|;aozaJ0+wZsjt*f5DGMV%o9Jr`IZ8JX(N^Xh)lsTObmKr)I*t;e9#*xS8fBJA zL3zhOko)z$0B|)R85PFgdZWuT^m}AyM_KXS)^J8)H{I4H%yF~$icif*d=xu~xdmIO zhLb}QX?`=*JJ+k`G~76Pz}R(uv&Cx#QhKzF&13y#u$o z0*!BaP2P5mq`4P`>T%)X39NK9EJ5S)CPykRYptvcK|_zILGu^P+cdO%X}buBMj}ET zCMLpW!YyG{F8s;d3@mXFA?mE_rq@>lqNSyn7*@*t?3kdrco4EqR3CEf%j;Wky8L^) z1ZsNiuY4X;+;HFjw5e#BU=z{KU>qe?M&QJPnG>Q)GW}k~QGeQH7G8n+6`$2BsXWbT zQw8m99FB%k0hH~SovU78h8)pSo*-5H`jDG1T#nX^`}-{W9dqAHv;!luv=5(aatl6voZw zp5syAPNEJT{A-{F{rJqBt( z$~xr;?P<1$6b)97;AZ)TBBRZy4w3&MvnnM3?PS|lIu_wv2jA+nC#gaww0vr6irFRf z4&FFDKD86$e0!AEwtTI9M{%mHscVQkJopL82Z&pcW$0R3W@Zvv2H;LQU8ll867V=G z!fBl8I+T}oww~&bB5#U9L*IJS4#}iaM-!5aH5R2DnKyUXd{am%w_>r!CDwiIVzaH4 z!d(kc=M{8vmC}>yrqL^!JI=bfb48U(GKo~E{E^>)wBOWju~<(FKU}*wG~OYtrc^jM zLVlGt=GT{Jm&ewGLq)_O{NdeqdLElAy?Pd5?Mhz!(1ym=vCXH)l$AN7#@7!cL-qXf zDB3>08NwVz1i*jX&vBh|PW-YFR2G*GcLTd0^)>GDNpGWb=-xGZi^?kcZWno1BjK9M z=01=P6;We*QfAk^GV$$*(7vd!Ata&b=9IhOx$1I-J%&ev7y`twQ}qvn6HUax>nVN* zRqAZ3ZwUB^wmMd>a*RLnRcJoRUER$a6m^D;)_bmoqt3=&U>MV7oyv~4Umy#;(t(5Cef2_*!vs#BbhE2XNX6RRP%){9p>-iQwCG+F`^{+@Ya%!iqj72~5HHbFkjG*bZzj zjY60{==A*B3au9O$Z{(t%PYvwT!6w}=+35sn!==N+JrL8j5qymDnWF7>Gfe-L>`Gg zu#K1EZEtAMGsc|GNMLa~7}5GdZ5d#N83O@#L&Ny&qi?pw`??5MP^4LQupXho50I*& zEftUX+{D?e(f|+$Acz=c%1JRl!pCiAZR>0N8(hq{U2Kq^kkvv9Keo2}*)w;yt@kl{ zWF^^8)eGF}c%P)d)AZ@Bp|Dp#5!|`VtcJ&Bmq1^5JNh~BLbf&Laj`}A8ynQQ)5O04 zE@@y}dSSWx2DoV00pAtzc-pc4OeV7*?)3hq+yCq@e9<4VXl6UKrA$g36J3wVv%Jj2 zCbb}YOZYKUVF1w5KO8U8Nnj->67*S0YLS8ySb69|GihFaCc!YwU%S!Fq8p&?k$?Hq z{A<#L%*3J<(Z&c>@qZqWtX9eB>g;fSO>~taeFkgV39Bo)Y9+U^BbUg4y-;JODYXjG zqhdlrQqFb3Z8)~A=%Mc2<*ta*dEmB=;t ztm@&<5IBA{GXr@6s~uuLLnLbKoOYa8ZC?7r&^kD&fRvU%wg>=Rboa8&CjqT2HdS6#S z6ALspu7k!64dVr1!*lm?r!1|MOgzn`@{py)#=*zM-X?*VKlWV)+Zuw2zl_pHw9;gm zedFebvw_=TpAM@LwGP88%45>{9L_qS0 zJ#pMt6$}F1BrDz5T;D@|yTRFlUr@X9JpUQl?0>xQx&dnmUrS)UArm*Kh{>^|3>kR2(52F5{B=QME`n3UjIIPOdI7UGD z#HF}Aj7+^>OjOIM6+rW&1Oj*wPDGVtXiR9BX%G69defOgFE@*tlx#q8i!=b-{M8|K_;NCpzbxnq5izN4YMpc zQsp1`%yirhawBq!fgFjq@=63*ku3DKptl^hX)$Z4!rKAf;Pm&7VR=5Jv=oHtH*f!j z2eCIkFlah50ImWUYFkXj$}tiKM{oAW`IQ15QkX~rn6?$>XDgyxO}2n?Db>s+SEFJ+ zO%`w9j(+%;58}lU+K-IHG?S;rR`i^6&(j_rmv>Z)4|r2yaoP^WMnmcf)wbos%?dsW zt${rDe(Yt50#bk;qC&#NC7qE1mwc>8%CDY-WP8e1%OY{ZXyB1YGJ*7IwMTi9IU9$5 z?z=8~g-_nWgg9{O1)#$s+BY;~!n4i~&(_k2Bp;L9f?j3zbrOF7!??P1t@- zqy_Js5VXQ$3u_HrE$u**Xk=IY3_atBZWnr)srx|(NrU2M+I>C^OZ_T2x_=X~{-VH( zul>?prM@9j&8Mp&Y0>l;BmyNcbZKCYr32bVH4$64CBwSX0RuSFvf*!!+_l@+9H(P? zGsA^XgFs%3vWh&Wk@;UeabT_hw$Me?M5<0%YTleJAVgo$y= z`)2;hJZgKoF{Phw_LJnrcWc(}@L-=hZj2dAR=`-^gBC-UzqipXu?>d&!;3{e0}^_z z1tdth5NIz6DjAstb82c{eC`7hqLvHwIx;d*&J4Z=6nj36OMrElZV-tSzNZUge{KkO z5}F5pf7qvV{AYB$1Y36XlMBYZsE^tyGa_~zN55F_WgTmpPL3OLe#Z9{nK!y6>-&lg zYzlWj^(KX1V^|?n=geN8%}Nxg9jc3$;knRw8wQ{Ew851xtezK2I|2iNgb0eD^jAC& z5$F7m0LraN+PHW+Z0x#wFhpu`>ULY1x=f{mTcw|yoiRlXPWm4@8f)9?YHJLK^F?bj z!XJQm&_&hFwUYf`mDwsp{r^GPI|gSGb&cAwF|jeR@7T`7*2K;o+qP|UCbn(cnTd@% zwsq!t->QS})cNuK=<3~Fz5eX#>fYUJUF&j?Gl$ZS$h%BK%hEyA8C1Jggfm=V30QJw znH-<9!kh}dTnztZXw`&@?~CLBiVN(YWifoVDZwn$T=$kRj2EpJS~SF+Id)+-#hu&V zJ3nvuq2Jv`vk?yV5FUg;F4fz|{RKy*1@Q}c{P5mKy=p}XYyUme(Qw;tdXAHHANl1a zKrSLwEcFIdj=KpR_M2gF2E*&_yAoVA@`o|`AZD&Iq`5vDQ$JW6_F_;qj?I-Xd_+W6 z29+?i8u_j+O8DO}@WmKTf~>zfyJ?O{@8UYh$!zicllec1_>3$T2Cy7>>@vP*+=&OK zbKL(zkd>{JHq7|pz1lg7Rlcf%2mWL%OeH&ozzQ{YML4IL0@bI0>opCBw6#>a#SSAD zT_6C1WFoiIfM(MPVQ;{a_W%(#o>M-r|2#2D*z7vk!!|{2DdL=46JAxLh@;3_a5a)F z*zx%;{E14@2L80`MczH16KH35oB;*h%JP0R6SreF*amC`4>kLw<%xhug@i#8s@|2_ zK9eG1ULpgt?iX-tNMnG8SY>W6+^^zR!Cy`j1Te@IwX&J^su z&Z{-~l9>7lsndyzep(?JBMpOdN5}%=i^g-M(OPEa8aZHy>ok;JSRoPs`H8c4oL}~T zbC4{o|BDJ#^K<|L{$CiRf|`tym0f)a`!} zkpE0?|2qNsUx)vbfczKv|3DyF*#0l4+kY6y|A{?H1AYRG07kYB|J-dxcBTL$CntM% zQ+s#2{~ozI0E_`9|0HifOLGesfSIKm5MXBS>IAR=Sa>>E0PO&l02_eqfBbKj|EzEJ z|K)yjFmeLg*#OO40RJ^|`maz28&~K5IN^Y{mj5k|a|XKo3wO5k0604snE?M;;#@48 zfdBUH_5gQ)2f!2H1$46i{}7oR|7QCCBr^YV=KnvD$;raO_P-OEov=O_qg$L-Mosi? z!Xs&afqy$?5fZ*hK}ZM8p?h6TW7+I)d8c+Bkd3#P>Rh&Lc(>Naf(Q3E%_V=6BBh{C z5XI-QB+tL$x#D6y$a(5~>hATvdcs&qYSx~f>J^yv&qK?U&o`q&kL-Kp+V*sx@0l}< z|F$?OF7eaUCBv+VYgRZ9V|h6*)To`spD$tMOoZ*najVCjag|mxrK>{KHr5XlhW=xGPw6 zFsNJXm)thL`DymoUHexfpXZMsAtMsu@*lSdvB+qlJMEd%Z0i<3O@G=w)`R4gD=jqd zTP^JtyGl3N4XlZQ#O6#~ifs1G_FP{p?}0DdCE*GrS)U&+6jZ}8-0JT?-cvHfXd(@y z*_?>}^}dmglP09Nqll)slJZM=|BJMZr$K`@ti0Su+=Z|OcGge41NSb1O9Niohx<%~ zYi>3j(l}*S->9F}Z2D(HYu%h@wjxKJ9#{mBt}IH%Hp4CoEgtT~)?qYeKxc?zq+`Tq z^kRt9iP3!DG&XKowPE&tJXdezn%&~@{yHCQ+%)r^CT_yi($*wyajCzryRSd5=eP76 zx$Qc{N!!-`G<>N{&0Kb=#QWJ;^{DBo1Fa>lU91Vz%x!#~JC&{UAOAV_3%J>^-#`MC zu%@@3@v3c(Zmx9=aSd{9X`Xq_c|bmkufEdp7txt(xu^0%^-XXq-n6t|%SS0RIl&N2E!c6}q^gp-WR?E7P``l$&ZmV8|UYA~lURrLy-PmQyCafFDx-amldUMvT zckN?P-&Ao$^^x=O?i^>ew$8w$ciyk{VhkR+$q~yHt98col-&{7{ty(aa!k;;xlE>ZNhY3I4Rbz=0js}xX+0F8W?Jqb zlVN&YAJhH8xoo0l!#!vF(xe_S!ya~nB0k_4^0Z7N(@CEbiWjN+1&Q01S9YqDnwI>oMJ;Ogr zE7+??h?uf5*D0SPx9?m9Fvm*hZ(ZB7_~m;NG*Wcb#@I*HVBf?;-iS|-`MurR9INu} z3782^ga`%iA3t$RZxU$6a8fcAl4Q_mOqBrjyfC;Pb&9dn0a3Zbj0Ml}k6- z$5y~y8^?`928q4Q1q3WXp0A0yn(IQxW3W&kK2co$5In_tLL1~7hPBIy_8l5{QT&9- zL^YlzJi{8m@21nhKVn~HEWZPFPHhkiy-;fQ9dffK8x@7E$qxg;K?q#RG{G?xzg;#E zYRtI9osH&HG!U4mgeGc&I@@e#`gP(Fz&?rWe$P*iKb%O%#7(d5qpnC9;5vB*Jf)niU3Erh5O2t*Hvgbz3-~( zMHf3~oysZpl=C%o(=h^CD6AAQRnVl%Y z#5$BvLJLJ%?kNiqi)pvz3hC<2rN@_g=?pOn-yif=?-NO_>;CbfdQz6Nx?`tD9Cf7ZU_}SwfE3_=~aHS z#_aNE7U4sb0(SZJ`A@mQYIze|pl3NWGK3>eTL6B0pXO2dfc#nG-xN z@U@43=j-h%*sX2cTc;`RADKTl#@QC?_;YhI> z3XRd|(31HcnDCqP+zGlA(OY(n(j8y%al@2vLWMkJT;+U+W$ClcM?iP>FphOZ*Cw)0d;C_nLq62MRCj^@n2}rY;-~{mN?wP5X)_Bgi%x28IVSpk!vcCnKv-GL zW!TYYwvI$PY4%?OVMH*OJnco_layalu~$ef4!wz`2w~A@&qe{*KzyP(7{FHojz`NZ@#Ed_J&-976MoF9tH~SaEy4|jrUHol%Jxrt4E5OhA96! zeByIrDuJAHo<{WAZKz35`~=Y@-@S{JBt+r!K)mN_ScxxqmU5yw3z?`+q1)L^i$zw! z;Yw8RokTuZJ#@qZp6!R|d_hc|oC(wwizZ3NVq!s#DjbKXV69YmDeZ(!rKXO|`0K_* zlm!0(fp;yR4NVc@@Y6Js=6ASLSIXUN%nlKV=fJe!_Y4+T1+MUqXUH;KY80AcweU=7 zyhy@5Q5@6TnmYFoA9;emNJO~%Oz0(F&UtP50f`uQL&+CK`xp}k#85to3pqwHMh9iQ zPhc7xLAUTtz76UOdW#bTCXOoB*cIq~=<4b`++ev$@0&0q5Q)AR?1mTCA+`>um3=9dvtE1? zlwr>?uU(T+N}H5~5lVDb{UTk(Fh#e-Qe3>o*;7SO{DLm93OuBnu*)NQLeM$VL1LA6F(R|=L z`=Cttg-83SZVog6%Gdo;{Pn1B9$^IxD#y8IG58i@MASP3tAmn!bJ*~Gm1vO~CeRvu zOPphzEfXe_AqW}rh3-2LUOZj)idT9JlU|`dH0qYKtbBv#l(0&8@Kj zs>G0)g)@HWd`^Srv;@2pMSLOx(Gxi|dlDOzZVleGt)#U)7VM}p0xYN!swsqis0S*G z19kdU{9ID1*-Bn;=EqC>YUZUOWcX!!8rsfJ}`m3OS{#l*2=&@JIOFGy}DAhcE)O1Rf}U4R%q{7SV1B7uzvJCV9+ z?e1NDuT^yh&~*-a4#EQQ^uPmtGGhHWNr9#7c3uRV=^NrHWvmaqWR4X zf^*Hp|3PiKwiSC-rZEHS4u-hA73qzZh_0yH(|-6ySpV}mYcc&GyGPHu z8;(}u7hGUnqrgl7$vn7<_q$O_E?Eh*{9CzLx@wd2x%_JOM2I$3G)g>>OAU&Z2M|c% zKO&+7|k5kG76x=gwtM;}-WI`PP3q zamRkZTuj|08(3n#5i}c6MmmSaN7mY>H0NRE<&lI_~x~^7zDy$`+~H) z^F-Se_8YLwAX{S!7@32()s>hhOvr0qu3a(XezgKbWJ4K;jEXN)NI#-v!43p&SQahcvigDBlYSxHbrEi4x)6K{1}7KoYs(oq*FxZRs9gCzcv`kj zUMBXEec{qw=jrO@TRnW4#qKD5cpBmKZ?Df;SxNYV(zOVku7Uc61gq6sw&t^LE&7-W z7#@eqx7vo>i8S9xBr}k_kGP<0njP7- z)-}T%_4_T80bN{&h|23YCI(_gXCGSaC^9uK4_ASyPzZmQ`mdw`)NG&5gOr=t2x@NEQiGQ@v>EJ-d`;B z<0os1;-Qc8us-7-GOv;B9!f|B80H-q!q!+D-(bIylLU9d>}H7+)G8G8F*`q8_eDKD zdYz-z##vX@R+Z6DGRQ35X?gOrd&+6Ki;o)Wg-jNL>V9{rk(URFq`)@@Sy}0lg9Bqk z^ifoXFG+_m4ZwQbKD@A@nbgcnu@Rt4TEK&cD7>^@%xt!8xgV$}~%tQ$e8} z69WxiC4jQkLCU$W>$m-IVt!&iNO5GJ2Xpa+pjuYN>nZkO53|r6I%@nNw~^%)`j~u0 zhGJOvEA}cAjVeUGCLPWCel#&!=cClX^cwrUd4?w4m`FI&Aq5Sgh>lS|x8B2pjO*2K zlR1biw%&m9U+I{tOevB$P{9(tPxn5wNlPSx1mDNWj!TJGFvAHc_!g7M$yVk-yLY08 zpSb9ev$Y?4jfHn}1ATu7DVrIEfYMBbG!W*H&`qOK8u-0ve-Y%0wjshIIbej%J%3bo zX^oP%uRVw&Y0srVZ;F)<9{G1XAvTKV2; zv!?hge0;w}G+|zkOCBYmee0^tx4)@+q;X_VZDy??yc?>Lo09^9mz31r9$bvfHFweT zP&0|1;L;~r{)G1Cu?KSMao;{{&WEAST!-Q2{*wBhXbT7G#`F&S_*}b=9}KcFbQ*T` zGUvoCFg$QXSOs{=Sw46UJ20jW%dVF>=TwHhR}~BW8_}VA z6W_tc!|s+&s?W))kJ5T+{a;sdjctTqtz;YwjMU`h5+2a0GofO}z+&y(C!yx*^LbRa zm4AK9>&&4;Bw?<4EB^FA=je9#YQ13&K0Yh!a=Wku4cyY(@c-kN*UBlPtuR= z(<8XP%9Nn}h)WX|;9`Fk2_zkcBhgmnCe6pHx6Kh%k^}z%X6|56k6Iu^DK#IBdMq*y z8!Vr`=J`WJ3iTekQyspzpI6ZwX4=8q{{treZ0!*B8Tod~V(;se8lULVZlF_1o{+UJrg!y$NoE zZ`fE^ZqX2%_Rf9LQlg{7Vj;UfEzLbE5A?rMKWA5KN1wXexz?uXb)_QUezw&K!g@!{ z+lfPrd_RA{kk<%@GUWg9#J>C!8${u&7mG8)di?RGD~EAo86#LY=?nARgnQlo+-Y~y zcP&2S>dL|8zHi>oy-W0`=}4oIuC30FgL)abD0jFjgK9eAOG%d6xF!`mbv&|lnDcJTNS+(U(8z<9)^O4oS#SZJX`!10H` z(CSoj^fH1y9f9R`eM8ZzzKD@q4NU>8;5|Y-iZOZ<(AC0|^F}U6J;7;WTp;lD{^DyI z{-CseASeQ?W9>|$n;Z2TG_Xw3CqCd$-IdZN!hs2$hnl;htgNDj6n)#}EO=$FP@ssS zF#Hp2n46`pq-M1e$zJvL#Gd*sKhVbtjI1q$4VJtl4CxwB{1KJXPOBa1E^ecTxVZV# z{q~1WhPjlCjW9QA6SS3l9+(LYbHz;RK0%Mv;U2Ej_CC2(Y$mR5OaHWS?9b|8Z6?*+ za^>3uZvB4K(IY&ZG{oqvxO7snoX<Iq`Yfy$;X;1P=jy`Y7eeA(kbC zk9ChGK20V;+e@G5W3`@GyiW*}O+~DtoyZxV0}1Sci<8`JAs)3N*y}eG_VCx2e>Gb{ zSLHO{iaBp+uHj~i!64lWJrl%co>@>GEpYY(B~>g=BEq?nMQ29Z*>gk=fgndWzu4qb z^DxOB_pQQ(6tqNKwx|7pM>XBTzv-!L%eS{HLzv=f?0`*hg9Cll+-+!;2u6IM{*H8H z6Kw?(QfI6s2KdZiPCHN^B4&%C6@+%Kjk-9aM1Kb6X1gA8IqU>hk+~CKi*+O zl@d7Axq=o^1CU}BXibkRIT+;@yFWP`Sm0;en&J#hNO``*nq2DbCjy)KX+kP#m`^&F zLD7u7hqdr(P!fEASg>F;u{`w1ejZtJb1`FMQHZl&Yjj{P$|lV0VTotBx|01ve)IW? z1%s$Z0c}~TDdq4fZ3%r3o)F;vh?d)$NV&Ak%WZw1ER4)-#_0iOJa(D!Hn??>xsC6A zF!>#0c8h6BT2yUXe|+ERobBHtd1eG@`|bGW9JzxNiO2 z9q$oL6H>k-Z$aXWau2%<4(Bm!&~7m|Ps3Qo>k{X^`B~`7sVwtJ4tYh`YB# zqz|dR)r)OSF7iXRr3FH|qa;|pF z7=8PA-({G|pQtgxjT40NB<64e+>qj=?~Td(#INqIV_9k)Nl5~aM|RrL44u=-lV5ePoOHlNOh(^>!4zJBH|@WRY4?evowq>=^_U9o zLfZ|7(sSBDr>D!hK`Ydm% z^Bu0wXz3k3NN%g4B9aW;*ua_`z_NlP4c7=3Wr=48u>^6!xdF1CKnc?pzRrkih->I< z_;mPm|BfAN1kk3|mLK0ruJ9x~{}pOLA%SSMA|YM|A+&?QU>Upw_`^y_aA5@G;@5XB zvoBc%sYd)&$G(g#kQpF6MB;~S>_fy|jL^}=F^LO}$9#5lde_4vpX@P%&gbQXXKmN3 zws~vjs$HXi+1oTq79+DWyN>yhY0K8ss!i}JRb#c;`jlH1BilNVildReku^C}D|_CI z^Pf+hjE#@Yn{8yOKZZTqNFs9@v;2ouV>{LKfQwIg^(tYb#`G4aj%~~Q)be@AYQ`#g zBehv?ls+3J?WRPRfc%=u%goeb1t8Z^jE=;Yb+!<{l0X=h(vM5gQ8Sby_C=; zh$;15EKMoIY)znPawU)M1*ZaaB4SkdHvKVj+qUEe4Yh=lrQ zD!KFlIG(EB^amzJ&@<$GOp`b;`FO4ZERLA+Gkmuoin2h8akXMCQb+AZn{)ly{ zNj7nc;(3&}>}xY6=z48jaM^cfrnj>RYUtrRo;J~Pb!2^NnRJ=Fe##@*8AiQ(CgRO%qbMOhsR&Gw!W&SK64dx>o3+9$MTPuyclAJ7Ch4 z;a=$t-E?I)4aXsdRWv%2+bQ5hnKr7id@D8I1Dm;Xo)zOmA{g$f6+JIBu8i_^PQL= z#IWz5E_&{=0D8S@VzmAslIdOQXPtL}Z8$G-$-U2ru7U+x=SNwp<0PemmUjH(7(&uV z#fL@~J(de9MylE4IE&hwXBfp84Onq=yFP6iPOhT?(pQ?qBbu$WoWhN9V>iFE-yK<% zrM>N&f7SoeS&~%X@(v3d&qF33Gib7yOLo3VDT;}?i>c_zpn3rg^^4{3RDW1?fwd-7s4^nRu8SWPx82H}U$7K7hx3ULadA zyDRh1Ry+tl- z@Z`3YkXJB2<|!``raeAVR5D5pVzY7$wUSRPU{NcJ)><9P{S>S1I%smgiJQ)~6>o1} z9u{>5IszdViT)g%5udtS^zSp`e$Dtgk!bTf;`x^kH*vx|k9eYW-TrQO>==*QmWPRc z8k`xt#!Wv(2$yV#@O8|nu06g^$_s3q`K?u#7?O6%eZ(-ipGqThodNd9_wsu~?+OR^>=S~$6Ua{c)P4pkG7bJn zKAQ=^UnIrpzp@Z$T5?;8lM%Gx;kyQ3S5L-|A5Aa*wy?)~C@m*!@OV#J%KkK?hk5jQ>`@N^28NkJ^s?x?kvvm^fs+ z$;;LIs+uTjtRrELdUtk$N_?%7!>>V&7o*sNt;g=QAQn|IJM?!F@t(1&MX8z&J!MX` zTChLuPdUf*fZ0&B_}$_@x!W3ScYfPouwy0a1)tXDXKK`J2tZ@=6N4(fHN7>p)zaxM z%B467o7LIfa7N+rLf$iU%*JYqTfZ8Hn0GUn(w*>X@)D1hTx|Q~O3*j<#q9-|LS%uG zKC#_&N2?J@eXdBF~)5wZ}J-H7=^&iLG{HkRTJ`m2H<# zCSE-8!}*a(<=(?qD*9?S&&PcfcJCJA)H2(sP$$=E*QG<%mmrS*BR3@4{brq8ZS~Xy zQCuw(FZb(na?7q_=L>E2kY11wa@1=9T*Y^woe#8NvFaRBu|Zd|sqM4K)3l9nQxCt8 zMz#SB=HEl?6qZ~xN-&pzDHSq{_@=W66cFsi(h4v>i!RhI#T@#O?eo%R6ml1 z~ zuO<&L3LQAQ$tnBZXwHa~I6##5`3u-f_Xc4FMF(QlMIPT=+s`7SZ6if4jl4HPV+waDEo}JH^_nS@hf(o0<#Mh}aT|$Fh#)^w}iH}6z`XVui^kKU&;*5~e zXwpkbqEpA{wsjN?#9WSd0S1X7#IZ6@=IK?huP5zcd9r#zn6dJ01V1)=vj!g{X}r=E zmD;{o#=WBj&?3!PB0zc0Ch*&53wS<@Vhdt3{ys=Jfx$@pE2z`?f_wS?_{6WvK1f<~ z>E3Jje6SSmB&fpZGLAnVKAb1Xx873xCy(~gNp4ScKE?=%Oy~MdEMDJRJuGPyBw??j z4TpW5HNh3<#_TEY#Xg@3h3-4!-wU>7L=k*xvf-U?!EbqV;+eAzev7g$c~jjwgUCNi zc`UvB2hz7{Si3!NUJ3oiqz?}z@}0^Us1;PU163RR$EvA|T+DlZW~Zm;=Nqg!SsqO- zuDz4_>KpkP=^IT?Y0VDh>YMqJc_TUKqqj%KMf&AQDC)3Z^fe_|)-Ohts>`3{JOZ4Z z@7VA7?~Lz8P8Lg#mWrs?KkB9OxAPUK4BCPhv_n5R~ z{v=GB!mi4GGI*-rmQX06(@E#w48(Y{{W3S52IbwjjN~t|NBw$Kwo8G<5*}7TkP(lC zz=jh>)fVP@Hal9#yqewMZge&KnkvjBXYAvn6&ys--tb}&1$__CD4d6>y(eh zPj8!MmFD(`tI;px!R60YBURCnN@PlU4^1w$9@8eR0F7(fPbupnO?_qd|^<|KshdwfR-fg%F>W+rat#>Ztj4 zRxE?%q-R<*L%JqY>xTJ>&57BGIi3}s5uSBhs7)a_N;oK*>yEztfU zXxXgt;-_~tidMT;sP?e7x;BS)vlg;;ZUe8q>*A>f9%D8@hiRQ+o!mKza*Wlqowd74 z_YbLc;=@Fk>F)-e2JeP&R#RK^kDv$Rv%^1ETD7fP#@(Z9^RE%j>8@R_NzEf23mx+v zlU^%cORqH#8h^HRzKlLl*P{GF^<`=cxfb|9!S`ru*(fEyPoQ{%nSSYj?7R{LWerN~ z=Sb$CK(i+Xwo(Ltj*zo~oRzkmYv)Zo7W-z-Q&bC7GgQ-5>m_p~<0X?NvqweKTG^AF z`Kvybcd^r`oV_dWma=OTHU=$vhHoVo+Utxhe72i5AGTsP`!>loi?)rnceaSOleX5j z**1JOZ#Ln!lD4z!`t{xBZ`Bw1En{|Jxz(Gxovr6->$VN+z<)Jr5t}TXq@8e`+?}SK zNPN|Lsd~|yj-AeK8s~Pao6X)9Ph*#So%!c}tz3=ymiFdfzdj(|C?Dz1w_CSOdk6jU zyMK3Qcb9h8`u;2N@(uIN^Ud@P_HF%$_`rK}e4`&RPL4a0M+i-(XM@~-f6fhD_tB`! ze&j@fg)5oSRg&Uf?kP_YnFc6wxiG?B(~ zVQzXaON}YeN8dTv6)6ZAe>18aB!2>~nzs*BBx>#%YP#B6>POb7fIKL|)Q515hdPFI zfmF%3iARIpZ3CzhNfC=VP_YC87Bmt9#z^yEn95h7m)j3tvaVPzJ73bS%dD6GYBFTn zk<8f~ex4M6mDT9)>Gro(co9>TXp@JXVV5vo@kG3V%LlXVPLb@ePa(JeE#UbD^>p%O zVt_q${8)bj)*X(hL#^v-Gj=)vo#85eWMLY!+eCu4N1ukw_4b;^U2lJ}`IM$%DQsJ% zgrl17MtfotwQ24+T|bJeF&oe=>mDxt?pOZ z;D(2~n^j_gti~}n>lmJZS9-8AMZ|v3Zp7E)ALnK~ZggjcflOos04D|F_%hF{Gg8-% zbRp>ii%CdO)huJlZ`k&ITCN(mSp7Mg-7Kj@|sCF(cRrlDy zUN5DwppDY|blBUS_L2y{%w%M4rv^xS#|AQLgbc+oLlk$L_~&!p{08~+0j$xVc4#fV zI9=yJ80dbSIWiE`_U9W$TSc5ZBj#_7!7ZI=2F?44k={X4sTBVOPigLLxArr9@3*Mg zNBu&4Q(%vH;JWsVg}qN^*rUT)X1q4ivGWDJgi@n5o?`C?j@1++YN1-q;B)W~YlX!$uOQ;D6*0Qx_@|g3-$uzQwEx`?uTJ1H zJ9N)4rky;f*bb?D*{AVHbK1So5{30IM6Gu+Mmgo9`QkSx&DXLI#cBH$N zb#B*wIv>mYZv$vc``VH*%apJasr8mCbnyiMAhbGRRajE-oam$XSqSQdws?U1Dr_kShL2qHpnQBYsal z_k4DuF1d@OhWEL-zT=Gqd+$)H-;XiB@aP%uUzY2?H@o&ihDcF|QT_xw$SmehV-?XI z8Y9dV&d(R>@PLf(&Ldrd0_8KxpbHo!<9=~9P!ju>Gsog5`^aky;ha|9jc}kR(_Bza zJYxkSS9*|0*6b6#22klFtB@)mm}BFxRm&H1IlWM~AGYTnCdtYZnb)v=oFE$!`KSXE zYZ8`^fTc$Wi7!~=$TlXf@~Y;V-5Q2IS>f5t%|=aekPcj-?cV4dT6N*ho-L&rZ%j0pdzO&vCY*tCLs$n)w9Oy-j9k{G>d}Zm*i}8JoINLTc9EQX& z4u3!r-E-Bwd?w{QQHwo1zr2pIBn!2u{3Rg+FCU$$pdEy;W`Y^nh{#&$DhCAiGj9t8 z)wJVBps8DGAEIUO;aR6+|B{lCP6-)-8iH*M1b1zIX>S*Q5p~w`w8dD&*f{W{7~6I{ zydc{4@u_%=79auia2ZmWIjk-5bR z#HY3$?ve0r0JFm;Az#pcxYhoZI7Mmrd;TX<2kH(b>Y@{LLfFY1Qi;gg#`BmNMVgOB za$+rBiiI?zC!c?lLz^fBP>e+AJ@mzIv@`;D7xnKsKtECFp~tF@^s7+9m^qan@+P#R zkD}*iQs%)(Rq)BB6MRy8tqy0r)^k6;RNM9M`u*~Ja~W1?%JQ4BG3}b9p$_ymQHCPa zhP;_9z7o(MTlvenS&guvjkbe8*9F%2v7O}&ur8yjQx}j zJ{__LMC6C3yMZ_$5;+F9Lo(}56__56u%yJf;!1H6lrBZ5lQV=mgbcaZGS8}(kJ(QZ z1=6!iSj5h*$i4$z+q81={Pn3m!Uny?456W5y8Y~Xnwu^$EOFk$aGc?5@IyxUkMk4y zZrhHjpsDkz9Gl_-DVZrbqwWQye1vO!c<1XVrqYgOLS}ug@eep|UZHWl3{ti#O^sw&9UJR@lJEPr;5H{ z;u2ne5JqxObU?cxk=H7+&4Ao3T-|kuDZhN&|Aau+^c>kWk2X zBCofZ>#!c6AtYFI2b~Q+mGYe;Va+$r-@p>`FAy9w@>j zIr1{;BqnAS#?0QROaIBfZ}^(e$daf|Ja8w^&4K7VG?D0JQ6*>pbaw{9a49GA#XHwE z@wt-29w&M8dHt%XOIPH|JH_r$z^~CYsTtOPyLEwbRGK#EeuAafxvn4r>K}A><9VqTqIzfNmMuG$Mh-#hKYcat`~@quQ>U#imMMml zAx*$0rFuxFm(}igdkYOWCsWT!&czljEi;X4rmHMMl*l6w>!@ zoa-S_e{M;o*`#LSPC-SHdQ`Q` zT&QN6Ogs;r2{~ksrg3EAZw{DOjRl5t->6KWrBivs5bTN?XJJ^F0yS zCCp2`z5WcPkYp&FoM%YBA*n;Sa^0f@)>&59}cQ zJSrT3xJol`tg;oGD|E-Zu|R&dj-H-6{0?G{WwKA8rdK*5H5d^tF){86?$)3Am6a{x zEnFI~mJ+qhtm2=v^{A!hrA4g$56ZTE2Tpw4pokBwJ81(P(P<7bPj)&$n7{?ptUcPf z6eLVc+_8#y6w^mrMByV@J%TX43M)mpz)Wd0`m=vUlZK(M{JdLFKkv;P8= znHs+o>*Wizxx9RTj+9xL1o!=>PeNXNfY)q>G#mM*jC8MBikVvL11 zsvu0&9yU8XIDzS3I*O83g_j#+i-j!8qNCYEyrx%;+48r7?#?^vJ1JKL_P+OqSNwP+ ziu=hD_w)LTs6}>DUJ}@s@(Xz}%re+wjoTA{gIb$5W_1?@f9``@hTgAKE~!orchYia z<)60^JW?GF7DtLBl_S3`375N1MQmGfVeJ~Kwn@l zW{rQOR=?HDDmhLpXRsZKLn633ML z^?b6$SPshKy*Kr-Be(nIK7qS{R=U4wK8oVhR32k5SNe>K-a!EA6@V;SoBkhRoGc-7eqY5wf6;Xs_eeBf78gLPhW!)EV(!0zfQE6mvJ zZlX_h?_)p|5N z;n2tD7tMnd6*ieFf&pNTGR}9k&u{UC%g>HTo_)(80S~BG6SppiSJ8L(HUuIJc02Z& zsslAMXwG0=wrBU7p?ib|OkH@t!<^`&Mh|EPj(tAFd7p7w;osyLA-%yH3FNv_X(H6` znkeZalAs8KYGGKG(ra|{G_!Q8%!;oOJ%zFfRr)y5O`&4952)uWSZ=e=pnRz>UiWZM zV*ZigP6(%7UTVq@PkpF=DZs7VbT(*%K28m432_PPp!JWyw(jCOxL3S?i@4wKm`pg39J=F~3$8|3K;A^08v@uL zbKrZ>f52Q(a>N5wO8AS_P~d9E%71H%pLRLM@GrI>M@E8CNoz7>Xe0hDZ`MRW-EYq(T_)go4;sQgsc6Hc_AqkHls%K2n#u9_1 zT-hLE#`k8#VJ_-pm7|T#rZB1_roNa;SxxBtz zrFw2RQO&Z_MbLnDIP4q_yc-ld9*skSoDy3r2yK`C>-1sZ1o57rMHI!oSZ-RZkDO=D zlL@H++YmYv5O$@!!#6Xy?HXMPHc=KO)HY;(I#=$T9$;r10f!RzZ-ckDu(YY4%EBkYjz5XPqES)%h9cT7xsCIaL2H6Yl2L%Aa?%My{FhkH z)4s{zht8ga|LfjsDRpo*7x?d^&!VsdH!&?P!>?1syV`zB3#vZMw^gloUxolXHAE$Y zz*>VadtJ1^TAql-4Cqk*5Tz4sFbzj(Zpau+$AUYjv>n-i@j3YsB|?4%>Vd=7xFs<5 zt9U<1xSIz{cyP$#Lk_HhdvwK|H0ZDe8cc7x8*xlV2c%R`r-}xW+k3& z&Fu3ZFPW5kX=05J)*S6t24!y0aaO;b2X3X#S-qgEZJVgZSq_$6+`quE^z^n-6>K7+ zV`B#9)$Xf@4}fW^oN3#ut=rwaCGyl5dBfAEe3lANNAxN>&&@kHa_Pq1nTD)1W$KZ$ z*um7V2Nr#b9Ik&8a{b|rnf-I$^?h!Ami1}TgrcwNe|YPcQxp1m{MCYu#(qo6va4!h zh2%`DkEWas%I;XW+H@b&{(wvs3V&#rDYimbrotCL7OFjI{W{x-Zud(MbnacS==c-4 zm!3>IeX7;_-oC!`hxZtj`F^OSM7C}(=e1v)?&(>z><3+OAG@p%NA1fq;6c-F5d}Bp zDUpz+(w1T_**!x;`V`SDs#1RVpL zK%*C5eKulF|k9UKwml5riv zzX7-v!4LNvM7h-a`|pN-|Lx)GA0Dk9h;|JNj}GV&;Oigd5~ca~FWP_qqH~Q5h{6M! zzyH?${r7*v^?(0vaE%TQ4TuU3a;ax9h$|6HbR~j<{6AlbV76FI|GE)j14 z0&a$P&`O81ryDgsbJokQl(C$-T;+1+>dqCjjtG9yDtTMc5*_AeEv%d4R3z|G#gz|N zR;-*H(X?l~CW|XIty^bc!zKgkb`9$oHtT-+#jJ&A=vo&}PH}j!wSaBmZIRPrR$iHq zP^xoM(&@i)KXLSTE)YJxXP~t;ZtZ+5N1sHT7JjBMWMcH4mHAsKcTiHTu21{CH~PQ`W|n*C!X5 zFzMyZLjzK$rZ+iPo-lj+#IV9eON{91SH~msc(3krEe_4b+blX>;a!u#g$_8_SFV>cPlM=# zUH%$Uer$spWw*ND_Zq*h?cnuSJ0=#jZvXT&CHuxNQFVgto>f`fYGAC(@WNiLn>A_{ zyELv@{l!nOU5*RBkytai@RvOI7p}h0`pfE*%U$EkMUNYuet7KB0uv_e&wH33CM ztaab}@T5=D`C5h6r{SxD<*wbs;j-26e zDo>S(S8d8fT<_BNaCiUH!`+Hhu^F9h>5$DK*=>(hKGUS$vfA@^B?onyX0vB-xm?qK zZhO9bLZ6XkJZ)Eu8Mk!X_oZ2ugsMYQ z*1Xow{83z=@ao;ypygnlpV8%CEowgK#1!wY6LW5?_u@*S>ticMbhus3+htn&KCPCy z4LKdRq4gNMU#AW&+MX}z(4qIAV(vM=4QNxR;OQ)1BRkx5EH?i~?qb#VZ)w;m=fcJz zS#GYr*ypX|*RwSjP2I9Tx_nUdi&r^X)LC27r{Ugxj( z1#XP6+mXG|owps9c_fwm-ZV>4u5}@SEo$FhKDfrj^qF2?r{qt6eX|Vo^UO6{Ui9_9 zWVd0+j}I*`U;LI<>2u{*h0nWv4jSEOQB>rqwD_1Qi>iP4b}ub*X7tY+gQg~C?{_cn z%*+)I76~cw;zXeic7;2Z@Lc*X_~+ehb8kI!xZJqY#e$`CC5*gqvD&w$rpDvKCL9Rb z6c^}rp~J7fLs~ESmcRBOqsf#!uJ@;CW5un%N;MDfw_(JC@1r+v(S}wp`0ePT8wsUL zf4UhLa`|a+{0YZ5XRjSNuz%=*QYB}UzEC^GJ7>=xFBaUZHl@L=Np=oL(;r-(^suRS z){Z-y#TnN;(hNBFaZJ-jt+$=s{GsRQLTekwT{`(|dZkPK`wU&y`T3WDKO;vk?zk<~ z&C{{xl0z>(?au1`#b?h~uQZG8q5JP+;#0Ed=J*tBKQ-=kvF5jyv|JLqPgh~U%SWl^ zr)!$mT(zRd-u*jM3m@)z((6pN8Cg#BdRXoIV(U=PHipt4oUV6FHjKJG?&^pn|70)! z(qrQ{Z74Rack++r%Lk`rt1|oIUlB2@2YU{>zR2YCT(c(sn)8!Nxcz9fFrn{D}npqu8aCBEwUJNSnN#^c z+vQpC{_>Ui;TN8n9hdxg{NUQ&2g9b{co6($e?nn9mw782%(Zc;>@uWI#j+PuY8M_p zW9`S(%4Lf$$`Mm0ayVMF4zA9;u$B9F`v?7kRrk&x)V1%rDE>%5pAmK^PmRjie6d%}J>8NHUwyKzoloAl zLe|in!+xeV^$W2JvGdIu5+4>HQlVeJ#9!uzqqod|39q;1h6j7TUMqWI>ePWN2ECYi z->+8LTIH&I^Jup>uJV8aZmkM@bC~S2xBBN^&zEk0w*h|FIsf=I4?A3+dA#iE3RSaq z+}v=Aqo$*w!Gk%Ti^o-&q#0U!#f5bj4qmKuHK(DjCDW$~`JOqTcClBp94lvD{ro62 zb$39oQ4!JF$$_y26Q_Q1^DI_o&OzVjwF)&pzi<4rlN*drqf)k{|7=&gW|6ja^L>r2 zxijdJ>D%Qm`M$2-mh1hgvkTvy>9#I7?D@?$=k&Y6ul2b$w#wf7HFkcv+4YzAu2&Pa zTXKYUO&WOdR`J+39?9Opo4-0lFAHkFImd$A!zbpBcJvyz^32c)cYgLhexc~P2IJ3o zT)ERT*W!>_SB*#GUuTW;n)mjV;rgP!zh1u|^u~S2{62F%di_WkSh%#!_mEa81^nl( znDSxr*2@Jw&Y$q!IeFce-Ce>q4?6O>`Sl&QibM~LP5Kmiqrs21)h50lH{!4F6`FYr zuJFupK>mfB^Nz36s$!~()iJF_-A;`=e(GUdcjaumJ?+xl73(x4`T32LP8EtZSWw>F zVDXfFB?nZUv3>D%x7-seCEULG&GW_7PQQ-!x&QE{?dy8a^vRWzcPDReHS=;+cgIAB zUQSJ?<(rXcKgetK%na;NWvH-??9 z2Re0e8Wy*%+4t&0?(7bK^2F`@tB{xbc7GU?WOg(68UG@A^O>_QRZ)oX@@eG$Gp{y{ z+LQEr&$<^$oBHj^_tX@BV@6oot=L;V_bz(&x$(|nec#+1(&&rzR^qKY&(eL|%MXfv zesSlCDEF=}Ti!f!amAh!OU9TE*I3pfSDrzmZ4Q?n;4pE)=O)%I{jJj4?`c@_!tOqG zwYBGT|1`n7^^j?G7YAkyh%T0RI`!4vOx62;ER(NCxkQJe$=w?4&YZR-Y+K5qhLNX+ zecN|1{MM!pRbzfc+Z;c??p&{aFR$$zaAH+(PRDIKT(|VCwzyCKiN{*3Js4l&>~wwJ zs?(BId>B!zrsZduLYN%5!2%y$+t={p{QPwYafM#F)&XMTg$mAN=G{ zeB3r$n=Si#^a-=QxASWF?W`UruUy>QaO;}6tD6_?)}ZZmmuwpvHE$l{eR9j@VmDX! zo_l+U&+vYa-}rQD*8h4yDev{~pVTSSxYNu%J(AC^n49fd%p&j7_ny>ya3lQ4czw>! z>*t;>SG3ixRmZP1&N9`ivx__xeQsoGmOF!zU;O;;Gvw8#eTAxYF6Vmh$e8gvLR~t& zT>p5_vFoO*8v}c^?rGEJdfvpj*RvcO+Owx^U%yh}>s>eB$-l7Nu9EsQKLSow4u0IT zX}-$^59IX?_;jP-<6^!xbDUPy%+l%lw4qO19NAPark$Z$mp$E&wYnV9Bk$hX>+<%j z``$iVVl&T5iIqO<63pXtZe2s#OwQAzg z#1Osc_I0~fX|p4Xu8CefvQ&!ApvjF=?2gu2IltbZ`Rm$t-7u`l?1u9PF1oe6QE)() z&q<5=^r_x2@ao3S^A{zyx_h;lU(D`JyZSGR_0D|oeXZ$nYiH^A9Gy|rW&1$g#yk7B zm-*Gt^YrdXVevV;9C;BvaQ~{r4VlX1>Up&MnVj}_duLCd*|XP{w_Qe^OEY$Vce?79 z$HlL;Zer?I=7q1Z!?|wdj|HqMnkD?u=5CIwYI?P}=DcytbKNlGviV+@tb4OI-8pJj z@Xso~yA1{MG%;p(-tptj(ZF7x2Jg#$?`N58>Gx-(eacnpQr;hX*Jq#CWu}j}cc<3g zbG>ISTC`+|^V_txV~obxZH>kTZPU`mj7iI~d2feC54Hv0+}R*}%IGKE?k8J66nT0_ zH|K59R~2)*{m4IgcfBQRI=0_$pRhEp`)5t@y6vky>bjvyfoxls_3Lyq$CtyecE{Y! z6zd-SzTOv8O8kUR?>$dzT(VbP7gz04mrN5!xNG;``I@;yS=&V;hP?f7W9P3A_V-+G zyL~ZUNEzL*!0Oh?*%}X24HLf>t#dMszxv2(k<+%Vg%@V#5vn^mk96F0M= zbN`xeM)oWgGN$>Ip>J>BwtZ!2VCWT8XJS>4gi75W-`j3Cwe;7gSJzr6jhlqL6_N)Iov z=~8-@Vd3S5Yz$uZ@z=1^6VoQo3QGS{xqx*|rTK3?Qk#0`jL{5oURmL3{Yfplu5D*q z?#YZ}KOLv+XuqMMX2$XsNp(i_o!zl(bD#Z1DtK&ex!(Qxg$Fld_Km1rJ7)UMBD%hv z^3OcbX#L=2-CEdHnbmyy`-Hd+tsb>~aACE1;is{!);HPgI5;W!*b84%slyNp=woaP90j2 znm_Ka+r%TEi}(4wpzoWtt@pcoPnwwdbLVtJambHl0~?jG^31o4KWycNt#l-PsP7>cdjXTbCppy=5PBJ%`if8Krxf zH+S6L_tm%z7 z;?f)Cy%kukLG35WHKTK-G}ym-+qQ=3cgl6}Y8kNK;-2eQu2YBoYU@X*zb$xpXpa%4 ze)vDSG=9MPIThXae%O2bdg+{xYS>*UYhZqs3Y#XlyT)gZ+^;LXu1LMDt%_Eu z(yH>rY?F>GU%IW;-6aFxr(;U3QTZok9Y1~e`C`*9z1ucCCC>Jm%i50X+nyQlcHXTV4ymfSLhc$c*~hhnq2!DRyCEM8Bnt9f?2PF7cYJG@bSlU zzZ_?OKk?!GmEMQCe6s6%BrJB@$Y-O=<{jmDHR!Je2OVNBy*&EtLUzk9w}>%Kc71am zb-P5h#~!H_!@lqMIXh?X&eweshD;u3?)Lt6+clqVk9)jhT#vB5<5C{D?Cj_9OEah8 zdZcAeA8k5*Nsoo^T@Uz&o*vZw(5JL%_r~iEq{KZtb$wRe5_NCP`r+{QXG(<0(|P#R zqPQJx+{k&`8aQ?MY<<#uSB-0i{*%hMyXAIiA7UH**V)G%-?d$oqw=&?({gk?d?)pI z2V=W@F1 zzxKYPb+0z+KGPaweOYp-&%{Rw-3!mZ+15mnXBYiIo1Oc1Z%*pk>&^nBdGcF*;Ms-Kce%d$c&ur7=Mc}^@wL;d#>{igm%U@D zy3TI(-#m)ldbV?pYpvW)cMjT2+M%P_H8z1pmo>QyO|U86{$5hPqHi32+`1LJ z{&LF+t2_c+jB}O`9qWGP%9@hRk7VC9t-qU3gW@jT){I$Ldv4dfMq`iYzckx1d?fl@+3z;7#hNrRHrSFsC2ivArl($3j_^z>u%h$DE-gbc_iUYbA(vfN zyOg=!v)AveGW=s;=ZhNauj~)144SyEkbU^({DsPSZ*1?~s(qo8H#0qSc~s@Vv=9Aa zmfu^@I=^kVoN4b;9t5wy>YOllcg1`2&aM2?!n5O?h~#ZCGi_J@HT5s&g^fI-9OEvI z-5AqkMaxsEJ?5XDy0KaphkQAH)QQgQdCKME>;@r*Cx+b{!m2eaKYYrZ>{qU(ZflUN zDc`)XN95DFPv<*44NqNqDBtp83oah{YlFVT;mYSXepp_*{e7>bQq49_E$i&IBkPL1 zhq}g9k8O~z>!O)8YCb*t<4Wv@Ym*EgyelN<$W{5(yq5ReZ&V!eYFmlxPv)GQZ}0c8 zed9$1M@D)deDdXRgDr=K`Xy$5wYJT2cU{r#opyw*xmCsEN4Mg4KEHXF&v$U!;~4k4J>z_#Y(VvUAB%((p6FF&+d%8_KQ#b_ci+X z%%{R%=?gqVtfS8)FE|Yc-_l{%vw4o^To2bAUCVWS-Z53iTuMG&@bkL(fCf!oI@vaw zIO|&X(aug6ejU6&!m(4;!p=@xe$}>_Fn*PxN3I<8Uh77bnd!cx_r90q)+`VB+$D8V z!tQ`4r{@f8Q@LvEJ#DJY-rA=47s~*Lj;o_9oNQ4g;q70ko?TP7X}67;8hH1l?S#q& zE*zn*Y^7~kUawGto7{8esMgVEESpF9d_(&YNAdZtGa3nTNFI+&WMmCOyy6s>{K)#V%hh)~0a!;V+$@wXihV-?jZr?Xy*NMrC_CWc<4;&jv1P*LqzU zbMKqkisxG4@_1mifYlp4nk*WR3wpX$ACbpn_uhT4Pw(}Z8e8s%-B0t%gJ-6-nz7^c z?C~!G^TllSf4Hi|FOR!-+yeuUftRvcAf+_tf{sk5Z$G zH81XT@Z_beRk!5|KlrZs`XK|7&O~)9b=K1M>9()u?CcCv9xm-!D`e!-F4f9z4?1sr zSu@%{%fr@P7r*W+H6wChkV~T<+a6{q zWxscy(^S8u2l8Zk{P6I*n0tQLwyq6Xab#ser8>FCOsN~bwe8f@VigyzY`v)K`tlFv zMpez@6PvZ-=trkEblaZWDH7EJvv0e6p5blE{OZu`5@$T?ylC*Vn4x^xs$Xi1`qpj# z*t1=a1XSr=vP#8}f%^OVv$*uw^=a8@uU6|b-&pnH+1#Ni?aSKlTVQi_S4XeOw|d>^ z_UlDVo-+pu?Qt5leDm#5O-9bVGvwvq3l*czA8G%^rq1DB#Yz-AT({KB$X9s|A1K@Z zPM>_kw|6{MzH7<4l^pY4ADjQd*Ai_OG(LYTGN9oy!B?$HRMW7`ge*?l#*i z@4DT^#_n$2>QO^T43%<>o+QD3*Y0|=SIlHAe-hFA>+UI(c=9j@+a zD!s$U@3=0s*`8yC!uEU&8|6{$QQw%T4~xqNm+tKNeg4b$O-f~RniAl8cduG+bhSa_}*T&m#QLJ`Bz{8EE5}_8`hrxMM13LPjo4Vnh@9i>; zdZ+zW`F4}~Z&M!ZeCB3pT(Z!nmAM;E|Ju|y_fgxa*Y>w`t#?2Cj?2Nodl5^Xjj^ul zGqy&HgBwQ0m)x2?|4Y~0J!+IR=D%4r-ZgCchvk}Q2_w-U~{6xuoJ#NCvMbGQKkIEMexjZI*xgv1n@-M^G&mqrr<@Z{PFNE6`mfIA-|akHXqUV-oYnZ#aCx75li={9~?31+sc)*OEkVVFWVXO_$!6C z)wSBSSvfbv_tVX)^Q<*%_Ip$$u>Xs-9g4RZ5+bCersqx3 zzsWM-%k^>P>K&gvuaZyxs=cpSefz0evkKpIx1LY|Fei-!INP zAHF!WV0gBN>z8&*?_pmdsPmoyAMUSO_};FC`>>pm{fFIqyy{s6k7i41wo0lsp_v~V zx%Yp^^{Nl|wZG>*_($CNvRz8oeBQk3(Ox;y^0y!MILEY{(G8F6(XW3y{KdBC6DIUr z*0|ipO_f}xO`Tq}NU>tMz7$Cwd4EDr>oC_Q?K-b^u4?nGifNYj)p6f@p0+9SKN>(t7A{JVpDi(MUcr}--{czcJ-zKXkM<3Z)(YG`ruMu? zce=kF*5q#HoL()r)pFeOSH)`K+SZLn)F@W5TCleDfKOQ?+Gj3s$T$1AZa%}KEVn+t zbqMP{@8qHSvF$>Rztwa<8eD(<?GzjMbxDVuNxpk( zov9xCFz9-neLeGrZ{2e?|BT@_Q*UW1+D}Lg$yat{<@Tj|A6_ym$p2^TbKAN;$-AQR zwMg|G{ccu~eH}7o8*e|pT(z**c@8Gsy%Rk3*P~fyTXemDWO3;x2|2217QRmWdcaU| z$*4-NXGA)?m6`SZ=Dg+8pO>{;b?nuqT95nq4L$K})2|6na#ov?W-iq=-1qI(mp=-; zd!1OR^*7_P76;1~O25U?Y_9cqv|q=;Z--8r(miYHnmYwN(|bE_@Ak?s zYwMxjn}7W3nDqUgu4jvRvpvf9xU+QDoH4BvEmPN>zUS-|_a@Tiz^Pu_XO5{A`K{Zs z4&HTbKR=#SGA3!PbGKs+lkP>{xOMk$>9K#LriqYgI8ar$!p?TJO+Ze4Ti#ig~nQe*SouQkH!*r{v7XKuaWJUa8& zGu=Wm@5+1s*SRs7a<-{{H0tWY(gn`v8yTMLu)f`=w>JYD_3Lo%Z0Y!ko_n5U`86za zwB54hT+pDm7_9$EmuHkTcm+iJ`uj%v=5cM^r+c*eoe5r$(**_;dlv1nj#91OwecI+ z%g^fXRI4VQ^7Rkz9^l;HH^MnEI3yZhUa${{@^dd=ua#%R@@hbTxBj6Kp;(4}|Im=I zD7XIZP8pB5;b#SQb+XSG65Y$)sl8VV`{?lSkY2&j_Bt1FFe#WxV@_uMGXHvk*d;&433Bn4iB?e6Z>`# z?-T7_zWm>BiTKy-zr6A~M{+k6%EfyOT!?PkWCZ;i#0zc&)uw~whymXSFR%YI4nji}RjF}q^~3%8sIr~L_1&HN^a=KN^VeGg{r$U}oQ=L_ zD++l6tD zJTlZb8k8Rq5fbd@tBA=JWZ_h^2L3TRI65StW(HN)35ou%A3}l^eYu7Bh6TAh^>_9U z2=wg}675toDj>i=${rs#u@4FG4^rIbW)B7q4hypH-N!dF8thvEs8)y#39k8HKV-br z^*Xm4TcOxanfjs!pFjNnf!V8{eAORe>W_?n$bf~YUo)P_fCc^YYoz)q<9!+9+|;ib zKV`t8)vp;tg4ItMFU@#1V@PiW>!be2m@;Er#uFJ6W&D&eu7Vn(RmchtjR=nli2Q9c zq3T~VUhk)V>haI}GTxH0@QkLnQIkiF{ICD)O-7^MXdh^w0n?gwdad3*;Rr_t)odJ|~&-!S~F)uKM}Hy8$Lw7J%+SEn;tjE3LFsX46X-}eicUZ?+k zzY1osqE7H%b10bE`1`zoVac>+gU&*GGZ=Xv28;IhHRF9ojhSJ3J+0Yjw9s0N7M{bT z6}ZA2CcTNCHJMOzD)wu#@Ej0hG!95$n2F~wTXl4n7OjQwY%v%taxE4!Mun1s@!v_i2szs@d?Uc* zV$?C7jeM3CqnYP0X?PBkj^{A_;VeymI7{;%&eE*c(7Bn7Bws9Mi=J?5`NLUS^hPO5 zEEar#3NNNcfLiMOl}6Oyj18VEM=KC1>of?!tSEh|L6 zq#>);Aa#`1swBRApVn%T`v998Xni_Vvdd?6EVHe8(7qg}*O?_RSoKhPG|r5Y{NMS* zYS56Lu^RMrZorC}<~4w8bL&8izG{FqSA6Aoz<+A_gJB3h+eG9vJ&Tp#Ry#KFv7?%y;jI3 zjH9yz%wiDp>R@t7odTF%;s7wxzkpe+f}Y_?;LD7^?*lM{l=py{%mP<{StUJ#DG}6= z@6#Kkor7^^Y3BfDmGWK(5h2eAFeBr@U=}u^MyD}qB&`7k#UFbG~buURYURHrfPr91%4WTds2txQ8u@-zn&COvB*KGJE>jw0lN zvZ;A}R)f?57>8SZ#on}V4j2aEN#l%q!RI=y#vZoY180YXnr{k zP`O`tLuN_4TAj(jU3~U<-Az3q-R*qL_;d1AkP=C6EOhzw6OVUEifl(UW3+3 z;|vCpFFG8DJWHLyDs3(>k&f4AB)bmdSbykD8fj_?_jsp@)zR#j(8nT#}hF}WNnqe#xO~UZxaVFC7IxEXCy*hTWK0VwXNq_KQ zr4IqJ(nucyFqUDkipd6nC?+1%gW)8-AWTC#R1ZRsG^y9I9fk-B$stHYl0yKI9MbD4 z4g!|Zk`1CqbeHgFU^@)lOL_)gJmbK~c9`C17V$O2mW{BM<++)3(jEs(8%ajM5+xo) zNRaGULD&_meF5=+hjlZ)oHuni3Kk(~%p zLOLFyG?Fg{D;x$fFU(F_pAoDt*Jp$|De-62SV@OMy2zM^&Ineed0{-t^EJYImN-TD zkNjjKSc}J*$p$g%;StLH>h(k$hzbyYz{8R8E1l7xCHaR;46n~%BK|Ho6h+8@&&~IVm^b&5(S7Y9tv8YmH2I4_jDrAG05ZjcvGQsVZXKB)d*u)u`^vGgJ7?d9E7Yc-)Rl=B_ zwJ`o5B*119YatpRR;&`~NOD(BVckR$zlUX@4G2%c6Ks{mVa zZ${JtNtj7T`UqZ>j5X^_u({|w%&hmps-*W#2;C6=%=$-wIaLGw5($z3xLEqTlQ2i*hDg>(-f6oXRf zH^Q@pa;^}Pl&8}ne8p#^5<_yo&^<(J&^?krERaNUZ&)&&5llz2y^%5`+XD3#v_6a0 zKtayD8M3O%c z!6W@^F|!S4F(aTO&j@OPVPJk5$NC82CgN2K!hb}&RwMbz79{i}PLZagToIwg9wwjx9SX(hd^qPVkkfh$$Kk;RZ<>U5%`hx z4C{sbD1|q#XPEm2{Fm?3!LuNkMdAR8R?9e0 z!BY9Gp8Q%oYn8ei;b-z|0VA0R7|BGyaB^~96+j>u6a&FfydYt45@{Ur3p5Av3q%LV zFAxmIE#Vp45VQ+xlJe7lL9591A-^E_1JOSilr+vHc^cy&dF42S>j(!3JunPKBJ?Z@ zE(8n_c%z843o5>#daA+ zcA_5TDkMvwYDr$f(~!Cc=^j+9$hE+cVi?(pcovqid=@Sk;S@*2<0wX~nC5`gmNc%@Lj}t<>&Z@pg+%!ZJrXJ;uaJn4_5sG37zapI5?&B*qO~C2 zMEgLziD9rj5#-@H*ye{3Mzsro8KmzA7|TRhgv8Tu)}*})4-83l zxetUzX>Uj|6RpAJBpN~pK+02`xU_fmFkonXAQzI^dJW4&C?x6IBZZ498?g@vJF;IO zoJ3qtgJ;R+SJ?ofH4qlbT_9h^Me!`vD^RCdf6z4UjrOk03XVLj#E98CZ@auTVlIeG;Gm8br=(HV~bn zzlrD+RbSFCgV6+OC*Ox40L=l1n$82xDUCyUJHwcd^j0(JEmY8xyize~iBpsflPrPw zBYB0?6X_JBVac|DMv?IsSl$-GfdR@w;tFM(L_?5j((eXIQtbk&=8(J-xQECly^oM3 z@dq-!biM{GM#?6X6p?KPH$c`Wf;}K$j3Zf)Dapqyr=k!3-lg zX@Cl#acG&6vKX!l=}TlyiT(`mb!i{)b*0=u=!@hys;Q-~3$ej=9jZlTTt!7K$gV>~ zko-_oX%YXbl3pn{Pz_8n)(Br$o;k|k$*wa3eG-32rIHMT7eF!$6?`NkRJoZvBcoF2 z;>$f$azp+RvI{bfjz}rR(E+3UIm9?34RT&=g!W-XVJVFR zVM&;Y>aSqIQT`k-6kgM_)YAZ^&v6DL@^3^NNG8zwV5$&K;Z8G*YJealO~iu;<&v#} zDrAxopemBFMzoyKS)zngt_2=D@ewRm;&T&f>F8PIC<~mTx>MHSK_%nVX&l=Eu&#)Y z;J(Rx4?I)KM*(IbdlD56ygq1PDR)&!koE>%5PQ=?S4jUu+Xb$KUf+2oNbb$CN z%>kW4_~ZDgIw9gW6Z#TpUg#8YmP*o*Z-D3!t2a}rYPE@{;ZH6=x*%rth6CL1$$o7FZL$(DQ|csQgRkvk?{ zP$4{t7nBWC><}StsVh*y$MI8$F!FIM2%AZI!Iek6J`^ZQyAzQ^in*brjq!(CA=(F& zt(3(eD~b)ne3AMR!7Hk901RmxK||>HX8duj5U|HSjw-{G@?P~K6HTI4h+s&>5>8Po zMEFB5GT{ZaLK3EWk)=*XRSTUFG8jZds&r891HH&3OE4?R60k4%0jT~XUkGh?Q~n0Ry}KzGlEk9{~m`m*Z3-j9{dX5Q(IIAr+mJ zG_Gzwk#-Yc21!GJQO#XOiKf6kU^=NQ0Hc0Wz^KLn#xC`n0*27H+%M`>g?zy{Q~}9x zND0$8r04}Ns1Tl%Lx4eI$@d{|ApBaam|_KL7cy1=7y>ZzeK0&}4j6F^qZ|+_da378 zg&n2;p)xU&2LYp=M>yT&9|4BQyj(N#2115m9JIF>r&`J6ej(MQE`y|l5~XLUR}0mX z=&+UJkYJU#LiMkdVHku;9{DWP2dx5>%}~m5gvXIhl{kQ#$1t+%@GRMN=!Qj+uzVk;B|3#d5_$`s8l;$b7FH+8 z5}4l7cfqr$$du2bF`wo@-ayJaG=0mssER~Ndk9V_#TfvDYLN3H`AKttBqd(pE=ao_ z2_TAH119eoLsbwoI?Vwntp#BV!YLZKXdFxi!WBZLk`8nkH?;kBj|dv3%p?!ci!AL< zM4+fn3oweG0!HyuRR7UEB7jl<5nxoO1(-$p*ebIqdouvTPS{P`x8yFfPR2;H{D^1(GCLjOY;Q3Z#08 z{*d&SH7Cjfk#)o9o}n0zYRTmFAtEN}RFAxY#Jvg+(KuYk!{eClktddQ9%vw^-Uq;_ z))X+@m?m+GrYXsn@IH8Ns+otLrJhGPc~IVR94_Lcvjp8sy@fonjH|%drMo8pqn<~= z{(yVXJeMQMIfR|eb#_c3gc6O*k6YL;Y!=U#e9VPIhDnn&%N99uGd4SuPS0Sy)|3$|r#ZVC5AiD&iKEktV z6_ofxBvRH(Av%fVj=&#MMKab55lg)p8DdxR7FqqYc-AmelSR=E4~lR$Kj^bzSiL`r2|4GueQ%@p*4TjYrD(fLL40NxwP z1DNtm8=!k>v%>peynp}+_c%<#6%Zrkv${S)@&ek+$hJj4G09@M*yM8}f~wC-eI-IzSeJ z>?_U5wT$ ziqYbJYCd1CheK&H^@#w6G@8UI6o{l-$zWaYE)of*b)YP5s#8F7s@I#sF0 z8ZbCHzt5|xd?oz>h9D!2qqqgeL6XaH@D~JLl!qwwDqtvLp=ariWR!c8%?cRhtO28( zHDKU4IR`ra1PvinfDjgqqaGcMvr4_7t~Zl>JuCXdJvhf>FE<-UqUaavY33A;&R};&p&gZvjFr2#(12aUCeeQO_t~ zAYS<_1fZ}d)irW5Mhh7Aj3UItbdNG=sTVK~%2v#a@R0PQklm+ybpa!p0~pC1z;FYH zoCB_q#637c>SP4Ss0J4>k~s)pk<7^mX-S-_d#xnBU?tL)LKP3yZ32c@%6*`xhUNe# z5l$iRg^owif_y8$sQwvYII4dJjQRorBi{-z%J1U_Nz5$wfi^GN2Xexa{*V)vx)vcp zs;vNw`qWkEkk1^33E>p1MYI8hDPPGi&_Yp1GNxD8wexOS#l30qzl=YfT8h3oDp)u z(s#xH-OZQC%dMlI(ls(UBfRPMGkQ(Y!9_z+p-}qj#8WI8YVwenzved>?XS z63@s9OCJZ#5L6cl7)(I&aL+U{ueAL7Jcup)>rkrS4_v#QFKv0Qa+Ezyc8=;D zmh^&X1g{T-DrrdNge4!T-eGzle}4w4F_^c4PLW_G-vGmi?%@*C`Kp|-)ZHp4EM*RI z!o-&7nZkANXlC*OzOVad}loJeoM zSRtEB^$ttitKMOHAN*P={~(}AZ>gNHq!*PFre{$DBzXpeFJqRtn99uS5Sl&(HY^(4Rl869VQ)x-eIzzacVNYrf%RQ`v6ukoe{n`L}!GXqscx%hLrKA zE-{e!gC`)*4f$H$8~Ph14MB_(ZNQW#zXEqclijYa2&H+En56SSl8@#^e)qMgWVASgi82ipZJ<&KyQ%KJs;!8BH+%kzjR82FEhziFN z?%_MrKHx)0S_5~G4}qL8*?-6hll_OBF!3*P!i0O36PD+za>7!EshqGp50w*^`#^-2 zd_R>FmN@uBPFO`4$v;v#VJQ=l6DHbFIbkV>fB~|vAPvZ8SE*0hFX&$K0&>EnS5;1! z)`yr8?H4g4X`jL~Af8b*;+k^=Q8+$E5ut>_}%0SCa6I z_8x|@zlEGI(=(ECat=sXvIW!~({jy-V$%AM6DEE`PMBmdGE{VK>W*nz3&Qu3#*q^y zenU!xaIezh{4CXhqPv#*T>zu+)&s`5EL0Q`O(G{uavYM5@|3yvUUrffni+>%B46La>9%QkSXE*4>A zU}T#ChDs0lKFE7P2Vg*aC5^`6>m~%FyBO3h%u;3pM&A+#jQlNSq03qgT+2lLmw+Lv zAlHJJ5#brRFtIh`*Qvi(an^RF>DgUrr^0xqk)i3A8qmr(W6PEG?RwBiz0YeBuyietX zCH<+Ku;6`2A^KJxVD!x#z$hn->RpPJs)}C8`+za-RZdvapUMdn4&aK@nh`6N^rv#d zG>&t^_=pM8!V*`U69(~tYved&UT9vRUeb`t3Cr_UIbqtb3IfPEkQ3%{6yrfdE7_QU zQH%#Ls{03wa>6P!!h3_fr+u)z$FuaUC?q=QJ~F__z6T86fxv;v3ClA=PFTj*5V|4X zN`-Tz?}w6n>MaF~?0dk-zE|b^GJc@Gz$oJffKlxmU{w1C7y<}#zii*D5R}xlfYIHq zXf1$IDBp(~f0_f|(%^CQZEajQfLj~n`(WQQ4ADaxhYJ-XOi37m(Ra-eUZZ?GVAS6T zg-kX6fT7Dy&H+_M`!!QO9?V9y&VW&^vkLXeItsLF&^LkA*Aj?l5XvAOidGNV%Y#T7 zeft71y5j>dy5j>ds#OCq)AtfocvAXwfKhJ?VASV@zyp+kz_YqfTFNVQ!;jC{Fiz`UBXGv zLWR=22=MYas#SwlV7o-AJbE93Rg&(}IZV0{+(x<)9yXsF*jMT!WZz`{Gj>7WzXXhW zQ2?WR)B&S!OQ6`BzAXV5KG-YH5*4np77XLq9>=97GR}Z7I?HEhOWGTvG}51jV5094 z0EQMMxjtN{Aaxl?lFkhsNo4P$*_`YWjflDA}d686TVL@{e6>a>XNjk8l-5=DzGfqqJp#pqq-5KL||OV zafs(I45i344oL?ILp+D@j3rAMhWn&R=BOfTi7QAt+4qT}Vd|#?jK1pu7;fSd>qFtD zjHTi0oOBlrU|>Ufmg=t%^nqfK;}E}R7}a0lS++4@Mv%{moG{}6aZHJ4qKCn0#>~>>R@V zAM6~YtYv;*Wuj&5A229Fxn|foGza22l4lU*B7YTBN_H5k4`kgO6dvWzRcev^RTV&x za!5Jj@+@Iu%G@WS2lPD~)m=fjSI&#vn{oss@59h08yE&E`7Y|ZS4l4!hLgZEj1|&7 zSRL`DQjYSBpnQlg)m_h${?uL11cP78FxK73kP_{JVCZ~taAebf#EJeO?)iLC`zC1( zJ-oa=xIL1O@FgL#C!x*B??&30@yE4q>T5P6*KoR|yOn81YeDUsw8P+iknUDJyplGM zuO+#mdUz#XRK8Zyfy&p?IK(mK`Ko*^&HD$sSNU3+;}3MN^0ksbRKAwh0+y!t!T6@N zfK??=BZNo#65(vd3x6XLghBap1YTi03LZqhmd;$|YiTWna;VYv28u9@?-xYdAlt;*NZ`#|?n)|xq*Bw$u&rJS@Q`Nr2BkTH3Qj% zh&+%@h5Smfkw1fcE$J5p$A%xI3Ek8*;)jAEiEjPVyN!VbZ_o4kh~@Q8PMASUIw`0?8we zufc7kcs4SmtV2$i_#8Q5 zlH!+xlx}fc z87txA* z15r4V8!%O5-3ZJVE5*H$6DA#nPCU}_$O#iqgNuk~FayaP^mqM`xeG3*as+R(c^5=k&%@6Yw9jp>Bh#Ap&K;8?Tr%DkSe=6H1--n2u zu$#=t2}_$AFuFSijnZWE14i*W!07I1z$nh3s#InC889?X$TdS*5WRrziC#eW3?p5O z%hIS$5JkSEYXL)aSgZx3q}_>eaA@Q>h%kmx-3VL_>MaF~a#;xclidjz_1FPMcP0Tw z@*d@<6ypJm@`$QTRmQmCNiyBzV@xDBkP~M7ajX;;I`tX>MzK=B5LJ+PhFd{+=2$6Q zHL6_z3<6)ik7K1OGbVMOs)m#L95675d>?WIL_;u8B@d!?gz={gC;2|8H+mKdUD7>F zWf^P1F6a(KEEvX$m;*UssZ%hHY8L>bJ}+oSs$Bq#`n&)`Vp7hllG_r7=3$8gR`6hK5%C`J&VMelqEVOspwhc2xuH~1hi%(A{a(>BPb|>_agU(X(esI8@5i2EJ zaZVTxHpNO+xJ25lfU*8SPFVWg_!18557j3uX+!l1%k?2AOgKPJm~em@q+JJPN%1-u zFXXEMM)5kpC|(B`>u%(Pd3{{_24{idb%4>g2hbis_qYNEK`!V3;aJiEU^nTLKxwfI zL!Yp;i!qM*2pLV%0qD?{aXoZ?AsQ~%2eVVsAGE&or_uUA-}3^D>URO7`dt)qQojpe z$S%sYp!G)bIjV-qw$ceHBPARPtiEd4T7=|?&Wkpda}LIjz<`v4gA zO9Do@Ho)k+52}-b*N4=i+^;INm*=L+6eRsAH&n_TRJF)_1*95%y9O{T`7Y>EA{~XU zG3oE4G9HmpalT*=8P8T3SCTKVEQoeh$Vi?q`hwP2zD0ryBPO1Y-`gr&^J z<&b3mp?XdF#PEKt(kDi^m}-guqg)$cR8s^PsMt{LHCk2RG+Z)tyJ5# zd=}|T8B@SP(j6bT5T9|6oG|$=s!v$jAc*dgT>C?xFq)mIUjr}`$qk53q7BtDMf-)& zmS>J|BGE2#!lZwJ6`40Mt8Wa`-as(&+|am9vI%{{j02?i<@rJbkPQcOh-@TuMlueN zJtiE$-J&_*brN2nX(a#Ri-fYi377NZYk7iBp`u8>Ao3;SiD*|t>Xe=ZkxLl*B^k#0 z136*F0s18+FQC$jaG<_1Ea!k)B^wFpc#0pO5Lw1!fhmfa1BQf(*qbVl6}U&j2Eo4H z$EkdPv>jCmIQckuAK8bC9+odRB#6 zglE`Nl4p>2ma%I{T_k7an$=|o1Ou`N24jj~^gRM~!yCy&q}oU(!gVHEgE!5zrlJpW zUf5ES?vdmppA(UA;x{CLnAV_EXb#v?gj47g;sw}JL{AF`z2n0lTC3}WMYUv;QSGPz-c1>fTKe4UbU4=T*1($ zI3|vqvf z2k~V^(wDGU$!35DL2(u2ohhz@E=@Wkpg`8;0xfiZPDYAe%0xuINGBuhLUamkM09}a zPU3S^+Y%ihi%58e+Lt+Sb!_BUz#SxBRb>V8JdiCT+Xo?YqBZr6VJYjdGom%zSVOiC zz9>reJ+5{jc>q$Axl~v~$fyYZP^A#G4-|wj%tHPVz9vVx34CK%+6TB?p1zF$7|TRl zM?v-#*n@O3R0Zi|MEOZ(<8(&@;P8AN~LjB8;WuCoeKmHpa|u&oF~RO$`b=d zd19zlstpBbiN2_5&n=TmNG&`Jfxh2G6yHg zc@d-~9H@XNjU(R_<{z9UISye4$-l@G6aS)jnD|$W>KJZ6@2Aekj` z|A*dTWcQ&7<+DgwFpTc$z_Zj_proI)l>tL`QO*HVfMKjZaOIx#dsHQkj2{3-bw+?u zt`eoS_*9%+3o=NAD}=8k|HAB)wjNwCimw3%qDNHcn}>SGTsjNl48w(A*w3hhc;okW{@e#XXJ@VKEr$= z8KLfzmNEi2??_u2-WK^Jr~s#)1{IH$ehPvh6wg-gBRogp$7#RV2Av0L?j-KfiX#0%l+aUe zA7BVTi+NRlvg9}PCzA~aE0%0HgkNM%6CoM$2NBdm09DQl0ZsdWi%n~R;Gi{gZ-L5& z%2)eVefWMARB7NUD~l?BbqHP$#t$~PiZMSYckvHnF= zitHCu?~-mrW|8azbpBilzEC5`)o?~HH>@ia27GH-=&B9w}p_YZWBa69Q9 zc*I1zkS@fRa93p<2~vn^tZ_p0eNMpWjxxaLPD;S2uLBBz`Z@q(+Xt6XlWs%{8`&1{ z8cA<~WyrQb2bPS-qHTxnwMU2+4TKW+s1lJf7P}??OOR^no zPO=@<42%QZvqgsZNmRJd|kNcBVTb9fl0LFZz`jcsI@F56>bTaJ&J_POK z4?cwIPnPGa`jhE>@FAq!0M(PMQ*AbM=G>o*^a9m80!H~Qz$hmIXP@fj0i!+vX| zbcN0BlIlaH-}C#x?dC)Khymm?vutiRG~?_0^LiTD))n7th*7ECfY!NmuzYTK}oM7 z@F?qJad{5)pg>IN4mdPn!Hebv3EvL9h|YM5G~zc9lCV{M;NQtpDnN%sKr1>X$}Q+VBfu!$1Q_*r!kd8tk#it`FU}m}pwMX?UMXR! znTKGcyTSUTy8$DA0c9g(HvvXHo`6x0Ct&nF0*Dv-RySazyOkJ{^`h#kdzo_p3>%jF zMOu<@1w)%~1$UKbLv`fH_o<3og3-4iASUTv7r-di2N;-8zE6D^K*AstX27rmhSYC>QN9N-s=EOU0XB&%xCTQ0S>fRj?<@4tSt1}I z=@eWp<+J*9gS34BgApRv0tZg=f)>t@q(4+O%UlOaykM-z_rc($y{V`pt4LjXqKqyda|fXX{d*@?WfwC`alQ@jo^y4MBfB&uEHn)z-oj3eI}FqjSUS=H4~ zFkEIvFuK=8l{?D10u|XL8LPfyEcs2{Sxx&;mX4fP?Sx>IYlBG*!%mF*Lw_=Y#S}9F zjQW!SqnMGpMOemF0An4XOd47Xd?tM3>S4q%E(ngo`m+<+1!-x)C0 zzc3w1mY`x*${e^>tbbwIlKex4k8E$`gk`Q$mG4U5889T}B%Q(op>u;YVHkaz0}G+L zS2V7({zZSX%v-4|r)0efU?__b>jPGZHvaG(V-O?~_w+21ff5F~m$VCupLic)mt+%4 z-(>y0x+Y8dzkpG`C!K1{h35d6wLl4~w6^s|6Uky69Q*ouN>u zP6jZlIR*?xf#Q9rs3iMOwf4z*RVf%h%RU6GT^S!j^oru%fZ^ceTHrfN8bW`v%w-`8 zOyBxYkzrmRKFucOGeYoWKVxSy--yr+^}qnezB4lUygu$vR$qi8{!snNw7x(1&Y*f3 z8$lQeM!Liwd}lhhKlsi@l~|)^5xkKw_|7y3QYUn7NJP-NA*fH|5Fn><@SSN6_|DRw z!Pf^VE`Xdc=~Yx;k!*sMEpu6@m|)yP8jx&PD3x@N{$yEi3#O#+hXO`-vH^x}Zh>dy zgvqypHAHqNs_IEMqIa0$7U=9FyAEVP_){WL;vOd{a}EeQvfYH7Fv%u-xPW9Qx-eue z5UnkU_DLL|B$etOtgzE zv8?k&IF{~Y0}Ps4;y@)V2*$peDgh__f$j-^pnHiIL}SU`g_fanLr$1%26dU9v`Zi& zNZupVOy`E)Ve)5?6Xx|PXI$U_5dgAxVT}`Aq05lsC7?c%_XvZKJ&EHbUjr(h^d+Kj zBom=kWiAkyM}}JN19VU4@dvs`PMGlrx~KQ4ul~t7K=+bAV3zRuK=+b{5E+p-eICi#EfWf5ciTlG8(hQnvoNh zxk^=0L~=tl-$*(|h>T=5SfADh^N-f2niV7tP(nq%E;2>L`zTT;nTSGeiWkE}B$l9*X@)=cyt&NgF6kBYy@tVP2n7dh#rB-5kS^ z;g{pot)m2^Z^0tyNO$T0hU~U{76k)DYfv+EzDQ(CUPZ>5bPu#S;{eG@sXvfbBmIH5 zQ9L3eAtlzFk=!8|=X=mWNj#_m_>wjd_$C@xfn|vo1Z2tPS65@mGeTgGco1GK$uI<= zNdKymS&4fDJ;?S^fd)wjuzbiahE^b(A7Y5k7Y-MlFB~7T`QaQe{?IcoWtb}Sm9&Q5 zVUiJ6d`*b@8Kz)ThAEi5PZcmUIR1`P1(W?f3MTt|0E7AbJDwFx z$}k0kG4R{o6bzb1!l3ql#{tIS(^K>;f`YVWT+Q;kEWxuVfu!{z@bmk*sd2JiK*8jl zo`4yVosetBMP9TPl&1VHBh<6FU`jpg~n3Cg=i6y*1l+ZrlnbEu`u8?!!>MY^As9UF{{>ZqAn%0Nt zi-;*;9GZRQ8A0O-9k0g8T0g+h&?)zZt0##5(5XQ9!*yRoe=xa(4T3qKy5+e+k`R9& zqe=XMD68;4@IJIrN!%k6F7&S&C+p1sL!e8ZFFr;h;udNgf~CLF0S*l%XutCyP>OOD z2}7h*#9!3=@Bz%<-lt$JhtN>RcqUs4&yp>rV6cMa9B>jPAAt-+Y!1)DmZCF)E%iGN zFb>_}L@x-J(0QOro%W0Cj0gr>isnF+oA!b0PibEDfqSuEh;iaom^6d~c#VVuL=J_n zMfC>RQVJ%&$)I4;mI4eDOWY&xO!5?YXW}=g9${1CeK5o186k}=d`>k^>T?B?J)?lZ zmXdhJ6#=|H^)X3_X9T(bS7CS0D&}#7VYolP;xw*h8%HD{1h(Y^t_(^UQn--85G;^Y zg#7wkqwYNS0k_Nhu{%3EU(d|!?&Y^q^PZmt|HwJIF#-Jj9-R_e(JA={TPp8f)42T} zTPkb2B=tPEJqOGW6qx6znXHX1wL0%x&QRZ748uPzqh{v<3Co-rkD-gO#je*CMvbjq$xw!qYz-gCgzp@Ax(iOG2G1)ZYOH#P&e zA~u8C;gQ+CC*-j-y;i^-TMXmg-I$g!;i0S}{fF+kjicY#=Aao9&RHH(JKXsRf`$*Y zFW$3yPmnlB6Yv0#@Xk`g?6yHU} zxAVK(Du%DFh2+{BTGK}k!HGLRX$5;ONyP4vAk^~scLF+l7I;Uohj2!GE2)j)Q#f(> zRPAu+C)*-&QbaZTt9Cf_(^)C=v)agUA(YUMg%lo#+(iyutIj?9JdM~nvZX>lwZomC zWS8Avr=#?fgt7afdNua0+Tp>XFDZ9(Id80Xn6#kCp-Yil-cUO{vFihINW0BKd8j8|B&{SR(T0s8(|)@R2y7ty;?2UkZ42}Z`=-Nw6KQZly6-EF+I zVwH4$$pbYPwPY**;KZc?LEh)~ZbjcCU@~WiHSbE0=yRJFki0J$*fUT{v-QC3P!nQxuo)3CZmD4kzAobU5+Sl*>Li zog`4M_x?jjD{)phapL^MLHx37FR<%4oH+C2g7vw@Sj_L5fDP?cf`)!@;^^ALi9;*q z^XiWiVWEMe&@sUM&IL}Kb%1r3H@Jh@U!jrgua2^jVWK6Gu~KmcLqn?h}6g#Je+uGdZB`)-{HhdKR9vD5f3r8 z3Y<7T36-4K2%I>vO_7_(SngY5n{eXE1-nA-&9}I*$}l~qV^2cgV)Hwx;UQ*3@G|Wg za*i&cCi`~X`Ii5{0im-rqwp!btYbY#tkcd9o6cMWjJB7a%E-H5_xb#cZBNg8F7Y)W z#@6?E4qi5T@IaB}aoQtznX&B&<9k;iweutD@(;b$^BmG=ZDO0l_ZNs;`rX**OEDxk z$@BAu0$17lY<^(6yW#KIn02VroV~nY)t+->Tj%dxId@ryU`ECS20{Z)_VC&{ryE;h z#YUeS+k0=!MV)58cbCZQdM~LmWARBCPLx@mlSr{+)hVas*RL=Zonnl1-|t;@X~y8&(?~nJc(E9W9 zYfK0G39Y~Zp}iy1vFLMA`R#A( zAJ;Tw_v0FHWK1F2jJYCX^*Q!SoZndD{KgU^qfz!Q&M#RRnID`u@*YmSJ{|OJ>z#d% z%ej02Cl3D|PP}_`IPutFUrPU%_Jlz_v9YsdvcF5XKwP!U6J+Z8OJ<& zMv3<5Ep!1DWzMdqTt1+sa|YcUoHHmSy?(b;m!2a?OnK;TYFJ)7r!HfETbuA&EX3ZR zr#F9y?%ui+G+MBO{mW@U946>~TG&!@JP4=-oEO#bC@k)_yX;DyLtS-!E& zLLFOlAXXOUhDn8;}HNEt^)@aLraN>=jNR+Jar}N$BV~QA6-c#|h8Q{dBy{d%R z3_|Pi%izSZX?PWpr#dSwW=P>}jVF`3xu%x}8hOPmiM=MO5_=6!oP85P&c5B1-pdEq zK4SR*61_2G$q`%QX&o={{i%=TaT>3@LEdLB+PST5CQ~I>uZv|4ye^oDRTvY3)_+Z?g$a|5m?M=YFBqT?x$W zIh5qt+5i0M*Wds1;nTyLH;=FW`TPGqJhBzOeth-*> + } + >> + \layout{} + %\midi{} %this creates a warning since custom staff is not defined for midi +} diff --git a/lilypond/score_template.midi b/lilypond/string_quartet_1/score_template.midi similarity index 100% rename from lilypond/score_template.midi rename to lilypond/string_quartet_1/score_template.midi diff --git a/lilypond/string_quartet_1/score_template.pdf b/lilypond/string_quartet_1/score_template.pdf new file mode 100644 index 0000000000000000000000000000000000000000..21591c0b25e0aad0de3e4df1b2380c984be82424 GIT binary patch literal 1328883 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

x= zL1ku;BB;!rm?EeQ24PkrR{=9fKDvKyBZ@GC0e_30E6%fk?n@ZO#Mb#nc4$B@R(`xMTFI$sSOdXOcMtpJZ5->gbl~G(7nz- z0|wz0qMt!!FbJ;@rBE4t5MCj22w}|VgYXJb3S&kegja}C7&H1Hyh8M^p#~U)SBO$j z0}R3|L@B6&J_xT6rJx2Fgja|L2sOYUyh4cnzyFc{*=qx~e)zQkUwHn{YXgl4zZ+%&xRoe{-wm?> z+)8v0;7Vf_fLn=DxYFpva4S&?R~nrdZY4_LN@Es)TZt@bxYFpva4S&?R~oYb+)9+f zmBuUpw-Vj1xYC#f;8vm()W9qNw-TkG24(@cmFTu-HQ-jF2#*vRSRs(J&itw1>R-y>&47U;y%4)!^L=jZREC9C>r7&j9 z0&pu)3Y9Snz^z2=Lk-LVa4S&?W5z51w-TkG24(@cmFPq$j2W{4+)9*!&X@(@R-zQt zz$^f_63s=>8M6S~N|b^cm<8Zgq7>A?EC9C>rJx2nF;17E=?-e(X&490Pzq|`X&7hB zPzq|`X&A@M&^cI81DzNr%}@$zpcCV;8A?G7bYh%0L(?zRKqtnLGn9fF=)^d6hEh-i zofrqtPzq|G6XWa|n&F`ao`!M!45gq3o`!J(4W*z4o`&I8qSL=v4Y-vk!efS8i6UGX zZY7Ga8gMI7gvX5IXsGp|GCDEbN|a)q;Z~vus{ywXMR?3`D^Uw$o#9rZ2&;j!X()oq z=)^dlhEfy)hgW*|Q;zjN53fqw(Ese=6`Lab@QSUG|319JtwcvJ;(DMH!>vRqTn}_&xRvN3 z9@hh%7;Ys>;d-DG!>vRqTn}_&xRvN-0M`SZ7;Ys>;d-DG!>vRqTn}_&xRvPWNZj}6 z#BeK73U?LttwdW4Py?M9ZY4@V4Rm6-l_&)@(23zzqD=}` z18yaX@R;FNq6k-pTZtm92HZ*%;W5LlL~l{3j7|)<5~WyYxRofvYQU{T5gs$#N_4Zc z&TuPHgw=psi6W?sP7Jpar7&i6Vz`y)TOp{7P7JparJx2nG2BX&!kE#C;Z~v))IcYO zTZvxfFlKaOxRodcozaQmR-zQtKqrP~$D^Ut+pcBKbL|@3l zn9+&hR-zPiMkj__iBeDlofvKAsZYBEK9jgJi z5=D5-a4S)SE5ofs5mp0kC5rHv;Z~xRF;qq$gjsR-y=x8Ez$tu+DHR z(V82p0k;xGP#Jv?ZY4@#%;0Q^uKyZpruOXucCZOp#Q6s>c1}uIO&Lf0sL;51#s9AeWMju8nXb- zJE9b>G-d%Dc|<8(Y0LsR^@vh@rQw&N)n~pQ@JmsIE5k2E5x&y!OHqW!48IgbP#LoT zjzFSyZN7)#m!b$?Y51im!efSCiXwau!7oK$;$=19m!b$NV-~=9NR+~uF$>_EFO))M z%mO$Si5A?U24(>qj6^An8M6S+Mxqqdz$}2{k!ax_W5z6i6Ot$eoiPjGkR(b$4a@>K zCy7$f8M6S6N}_KvLk-LVI4y}%Py@374ospH)W9r&Gm~hK0cv0tz_Ce`f*P0waB>o* zpay0E9G*mb6;K1S0M1XM6x6^hfFqPB1vM}W;1nfFK@D_b9Hc~F-i8|J#5hZdQcwe( z7{@753TmJe<3uIeR)HGm#5h!mQcwe(80RWc3TmJe<7g$?o>8hnAC#5pgL2IDL8%6P zP*$c7$}!Uir5f}>S(!d4$4noTYS4ZUDsv~M2r6?YrU)u?C#DE0b0?+z$35UweAVpA_ zJ26F2nHi)ADl>x=L1pg5w5x~8%pgTjnHi)ADsv~M2r4s!6hUQXkhTd?ncqk$g38Pw zMNpX;qzEc=AEXE>GlR76h|0_$MNpahAVpA_8Kej*GlLXCW$uHtk;#?eR-y=3hFggu zTp33zQG_ewXeEkpWgM+U=QyA;7=&AiQcSn4sa0_FQ@1 zfLnE#|*a;?fvtZ z;Z~vuSB6`OB3v16C5rHv;Z~vuDuY3|mB>}VYT#reim)1RD^Y~U47UxKnO}9Veej=G9U2jSH_QUG>&(9!W&zsDMFiIavjF}Q7M)T9)i4X-k71D$1lI$z z0PT2jL3CpHrRaxoJ0B$8pK@H3Ta4V7d1!Kl6Kr0^npTH~tw-Ws@)W9qNw-TkG z24(?z&*M6n1?Y7O5vYM#0B0+a2M20k7Qop`lwvjL=~Y>Wo;vY?E7LO_BCG~IF(Ja0 z>3IYZ9y4`&j$js`7K#WeqZ7leMCKq=#w-B05~WZXotS1CE{IM{93z4;qZ7leM7AT0 z8M6S~N|eHwF$=)0L?vRqr~wAyR-zQt z0E2KVkzh~*48pBMDX0Mk;Z~v))BuBUE0OR}0}R5gL@B6&P7JparJx2nG2BX&f*N2D zZY4T53u>Sf!>vRqr~wAyR-zQt0E2KV(LKUyz^z0P9y8oZ6yeHnD^Y~ifLnW6*5mp0kC5oUjIx*Zzl){+tMhdqQ z(SXY6#BeK73Tohu6mBI-Va#|MhFj_Xqwf8BEJ=LM(_LcI_;8x!n|511-P3e#gVjtA?C=ET1+_0E=CDqx&LHxVyRiId+Uk@iyWf z47L&nE`H~@2SZzlNAaG+8(!lK23v`LC%!q(V6c@Kmlxk0XE4}GJc@6QGZ<_o9>sf( zGZ<_oEJ3VboWWo#@hIMNoWWo#@hIMNoWWo#VNv2e#~BQ^5|83N#~BQ^5|83N#~BQ^ z5|83-#2E~>5<>#xZNwQ2wi1uxZNwQ2wi1uxZNwQ2wi3oK-bS3kU@P$`-bS3kU@P$` z-bS3kU@Kun``ZY%5})+nIoL{k(!V*_N_^7aMzEFmr2o#rR$>rhd~=+^U@P&czvo~p z@kxIh!B*mv{yPU-iBI}_4z?2ZzQ2uNEAdHubDY6oEAc3P=QxAGR^m~7bDY6oD{+&= z+lVt5Y$YDW?;K|^*h)N#w-IMB*h)-)jNduVV6c^V6z@6CV6c^V6mKK$!B95hW{dY6 z_h2X+@hIL#+=Iat;!(VfxCetP#G`l{aSsMph|4qHM%;tJ72;96jkpJcE5xIC8*vW? zS7-^Y5Pykpjx*S~Lf?fSc=q>O_%8goX+!_R!jI_Z?cqn{$-fUjf~|yK5i1>5AlOPg zij@v45NstL#Y%@22(}XbNvw2OfnY1~C{{YGK(Li~6e}H8AlOPg>MI>=B~&H*?;LC; zKIz{aY$ZPFD;;blKIy-6u$A~EzB#Nwu$Azs`bq~|iBJ052(}WR^xrwyN_^7yP_UJ7 z$@<#}wi2JjH-{Anwi1uxcMdBMY$YDWH-{Anwh}^`@ixK=1Y3zm@jHhV2(}WB;%$T# z2(}VVWBkrx1%j=_qj=9@1%j=_qj(!(1%j=_qj=9@1%j=FUm9;CtU$1pcoc6VtU$1p zcoc6VtU$1p&<>5a5mq4BN<50U5mq4BN<50U5mq4BO8Cj~HsU4@wi1uxZNyC+Y$YDW z+lZSu*h)N#w-GmSu$6Gd<88!E9Bd^X#oLIRIM_-&inkFraj=z;OO3Y?H*v6)coc6V zZsK4o@hIL#+{D3FA~fi4BiKrO(tqb*EAdJH=3p!FNq-x`R^pTXI|o~dSR=kUZsK4o z@u7CJwd|kK%WZn>g4?Jc@6Q zn>g4?#7FTq;wBEZ5|83{j+;2xN<50U5jSzLm58(AcaEDl*h)N#_Z&BIu$6ceZzFEv zU@M`88}B)8;$SQBDBecg#KBhLQM`?~iG!`gqj(!}69-#~kTBjx+{D3F;!(VfxQTv>;dob8aMAQ9k1Y3zu`tKZU zB|hoj9Bd^%>2D+0N_^6P=U^)#eH`B$_h7Ju$B0vzl~rk@k#%kgRMlE-`{hv zmH4E;jbJPBNqlqMgTYqfQT)zv4+dL_yg__(+=IbZ;!(VfxCeu+#H09~;~osQ5|83- z#61{nC1juDcaD27*h)N#_Z;_Nu$6ceZzJx(U@MX0i1!@#V6c^V6mKK$!C))#DBecg zgTYoJRT6I_?!jOy@hIL#+=IbZ;!(VfxCeu+#G`l{aSsMt38m|J8*vW?TZu>UHsT%( zwi1uxZNxno^U%oZ#M_8_Fh-*BDBecggTWQzQM`?~2ZJj_b|~IP+=Iat;!(VfxCetP z#G`l{aSsMph)4Zx1Xl=6@BTXnSBOvgHwRaUPx{*kt`MK}-#NHKd=lRr_h4{^$Y}NV z99$tj>2D*rLVVJH=imzQNq^756(S+l-$rnS_$0nL?!n*+@hE=hxCetP#H0A;xCetP zgp7H-jkpJcE5xJto#P%1t`LvnZNxnoTp?0+@jJ&o7+fJ9#e0r>Ft|cIYH#EFs4V1V zzl+Ll+R*=~sEj1v{Q~Z&j34h8{C!jwY$Xzufi0{+u$6ceD;-uK*h)N#Jrq_T*h+}V z$4ZA42(}WBVx_|h1Y3zmvC?4$f~`ctG*&vSK(Li~6e}H8AlOPgij@v45Nst9ud&i$ z1%j=_qj(!(1%j=_qj(!(1%j=_qj(!(1%j=FZhpLtumZtW;!(VfumZtW;!(VfZUsmU z-@h||x!(qVxxbme+`lt_x!(qVxxbme+`lt_x!(qVxxbme+`luq=J@7*6Z1)YbN7b% zB)++CB|eF7?%pt;#5Z?unB;YQbN7b%B)+*@0X~Uu?pA$~d=lT>Z(=@)Z|>eOpTsx! zo0xokd~?5v`6Rx%pFuu}Z|*lSpTsx!Gsq|L&HW5g0ElnyH!+{YH}^BhC-Kew4Dv~Q zbH9oCB)++yK`ILI&HW7WNqlp^iTNbHxt~EkiEr*_kWb>9`%O%VBEGqwK|YCZ?q`rs z;+y+T%qQ{9{S5L+d~-j8)I8#w`%TOz@y-1V@=1JiKZAS{-`sCvK8bIRGZ<_osww>p z23v_w`WXzi5}))l7;Ggz>1Qz5N_^7KV6c@i)$z@720L5n`wJ$N^1i!ZsCwSF%gg6~ z{L3G|ynOz@fBfe!e}4J=fBgQZKfnCu(~Xv_X{(M##MdXv{qPB<&iw>V$ql+|lKk+A zV*UN{C`NbFcrzTJO+||$m*9=#KfZkaUw`?RpTE9L^QUjWoDh9?_3`ig`1Qv>{PNH7 zOWt4Ge&c`l%THfE|NB3G`M>_<>mPpq^Orw=`uy*{eEsn!{{0t*f!zP%cVDazzWe6S z|J#56`oI6f*B`%r`Skf8e*N;Fe*D9qzx?V~Uw-~c|4y@D-~Y?M{`u!$zJB`r!lM<{8XV{IdH^~uXHW^$4{UCmtX$$)0aQJ{6>50!Oy?`yTAMV51;@2 z4}bqj|Mx$A{qo;6%e>{KOzii9rKm7g=fBEAtKmYXkk39PA-@pFhFK^T14GAm$tpBs_^?&%6fBMS8 zfBrvz|MP$ShFM$CVBTI?IAYgVrSYhoz3LLDndTK|d z^})WTXX+J(NL9=S>wLI%*719e`Ek9Z_2MfHjphvkt^xg5#`N>QwqgM~j`YE{#(k-Mq#;?o(UE9C>jp9_eZGoq zMhD}ocruI#dR5mSZHuprCMtTSA)NtwHlY9da{JdSjlsg-9!Gj(x8tnWKGHOmzd_M6 znB|2$UuZ5U74HV<_3uS3@ zT$AO^Tvsi?d%Db|lU|iHAC`NX`FSgDx!3t^UCbiO_0J)P-F_^XwBly_e7g)@>xGR? z-MwwnNv}k9wGXo2($)i`ER6@ID+!z|EOpdZi55_PdPQQr+96hWaS~bRk6GiEgL%HE zw+9Xvo47k*6r*1?lAy4weZFnm+h$zOmTbxqbBe`bg1Yj`%2z(d=i5BL&BkuEteQov zZu+sW@k%Jt7T?!wYqzVd-vx)6fKiD71&*BL<$4tZ2O*4CT5F1)hj2vYgcr|A(F%vDUgJgmc6=)g*W+O}uJQD{@7zbf8rO{P*v2xmxD? zDPr33$ON4)NCuG#dZA$oACpWFJ||t18}A>O^xhgJ=6 z+tV3f>u@2k{;JG0+?rQW!D$(Ml@K+|npfC#)d1fMJk2Y1%Y=)qX7hZJaVj-7R!)`% z8`FFEsuoAohhCYbTNLx6wC?oWShw=jQ_R9hc}UAD87P_}Uq$6mYJ|gF6EeMn1jr7u z&3Vzw$#UM9MR-M`w*|_DG4I}XwR+?8C8knAfufmeica*vy^5n+*;Z$Gt;a8{Z>1V9hUhX#547a6X{ z+v*IpO>&XAIK5DPCWE=~Gnvez+ZrFQ@Y1=fwZQE#hzLBW+k_JMkvIm(UZ@)Wc()T*T3 zZrA!kOX{ooQ!CqRP}>-}kCm zAJT|~PghwOm87Q-BXuQlfF!#k*Ne80YFAXx?V%(?#Ap%&jWhWiGZCWNv*mB%X5y6K zev%geGt~I1n0FHOyYaCbJ@h$dZDQ3Tl<*- z_oP1n*|T1ByuYe9vheM!Kq^anHQMiU&@*}ZUV{Q?`V=5GT6^^x~*5}Ib@3F(wz(+8&9m+?$SOs*(-Tp2D43mtz5#A2^8i= zM84psihW&}h&;bpqC{)|db-F1aOb8X#)XRs;L6<846!XX24du4L=8cPtkoK8Q$sSu zo?YKJbJLMXAJ!&)jT)6v)z>3oRbp$U-H;5h^}EIj z_v=ZB(Q>JLjx)U0>*6+NCu5rX?Zns)$UyvD_jclP@-%bOu_h_NFqKznrw*ef4l+JV zWurB}ZT!#FWSV}(^HasV zh-k2>QID6ECSab+wLd8vkYqoaDpHQO`_k|PAberN3g`4Pz}D3|D5113#LGr~O>@B%9v38a-TjNRXG1h~wj3}Sjt6Hr5Q}Ne zB#mk%#kRsQeQf%&$wi8rwfcCAQ!J75<}3|#vIbt&B&AoR=!-(xUcbqW|s+B799O@;PVKvPqENdBg-^q@dzDtt|x_9 zlNB-woy%2MMFGsjPK?HVp8mKsKrLnzKouT0br24?SjTn4V?kz_H^a)j)lO5$Apt{; zO_NayN6274InSpdf!xBbPccJIr;9**Tf8m!iKBnB^m%4$+nsF*U23^ja=P?I`n8i%QlHLjc>ND+IVD|U4_UG%nTnptV5E4iDje8kKx3`t<< zNlLRzL;4CkAUM@@olmaooV1Q(MGVlL2Qrfl;x?}AXaYKG9hlmHn5SNdKx-!Eh$lk0 zt8P3Y(Bj}4F36PV)lU+Tpc{FmN0oFC1J=p=X+pupxvrMMzN=6m&U#gKVh!sDExP7> zFi0Euo3m-RlVf=i9eFL6w{OMOgx58-2}e)&b^spBjLMV7L04Yp6tQ)`Rsu%FmILw1 z=|(`K^uA6t>Dc)1+1E6$qoB#DR{K3|_Y%HF(5&2XzRz>I(AEcwt-Y|o4|=f&X3CFK zxwgK$V38uWKG?b-aZQ3i|I}2(b%hf`rE*0Sc)H3pO$?*GlL^{gDyR)!dv3!5HW^M$ zxnF6RAszIx5&aCuGUa+!7Uhh!?{8+nkN}_f^}Lk>0)DcDvF(7skqN)N01=ZW<~2x%BQ{sZg!1Le)lKJ~d^14yq3JE?^{beU7`)Qy{$tQn8P7kRva?u`L!J~P^avIM9UslBR2bmN%v$=&{_cZ zLN6GT!s!Jo7h&CE_A@f07sVU5+SW0X9FR~m(e#zzil%%`anuM&sWX;yP{tNd*fU<# zUYbXetqcE~A;MH|__^e9*pVFcRX=`(yN&l1KR*nc^4T>9NxzVO1zC!Mw{+e>Qo2&>4lNGmn zb$++kxfjesV=82?X(Li=K5h&uK}45D^PsG@<*HAVXWU9FRYF-$D-D_Og+EwsFjrpb zWMqW~tXRkgN@h4-xSAT^z?r_JbA}KFeC}#{ySbh$vTeVB`f3rgk4b|qkhL#}{M2}< z*2#*Y@V^udt7OGyr0izX!VTNk!WK`PjG^}S+PV^`QX;`pdT+P1uMD_}C%xo7nF2O6 z89-XrcbKju{qa7PG2y5$^)9Vuyme<`yB%ojkIf9BeY6`(UA`xv5fE+_YN{NSL1+CK zUiV%jv>p%?R#?mratJbJlfo;^`ZCq*2W|dI){((HFk`}J)F%$Oo+{T$6lx{~s_{dr zX`O4ip0buQnGzXNrg3=4=wHFm@3+=Mv*5xTXt&l@4)J|!rHE}_3_XfN)>{g9+Y27| zwKNx0I{0N>P=8H(*?Wo@bn6etWa1336gAos*W1+h5+@oQ60I}m7zboyqUEaT%czq; zI-mnqHx)~N7PVfwpp%=*I%Bd?GgduJ_Ns}Gcqf%jw&pHpV=Sgb^qwYQO%rdhk&8zF z|Mj4AusCeW5cr^uDd(l{5o>@OrmQa#!*o5b6zwL8cnphRi`R3;f_7o0lQewAoa%x3 z4zLDf1z7cXa*^BjmfK6;i~qP_xmoGE_#Si%T2mf1@2a-1@xt5~3(a6L>%4-J(Qq7L zvO~MFMY^$-OTY`OH^4QvHnIO34x#;>7ob6S|QU zC8lm=oc%0!syp8|5eb7z=T=%6Pq;T)VO6XMCm*82a*OTt9I}9%a^*;2o9#W5C^=NE z>L+@J=Y1zk>Yiw#$z+TzSw0JzLKV!(7C|!uaz=OPLBs!TyX*NSElC9Cg(GcqW0)nk z)WD>75SSv6;f9evk-kAITyFJJ`~f>9Q61JJo0aJmu?DYmr(nMz=Wvh-RYtgCFdYdK z27j{69r+hkA+y|zCb^1bLP2L!%D;0iN>_9s6nXF4i{rPaQUKtT46zHWM zA+&qp{?20c%tEiYWv50I1)B<8(?#tnmDyATmb};B8&i2u-%z&Ty$$Id*TwnUP?bOYb?2Du~WjNXIRHuT=#Od_L;iAcDNrk^Zb+Q@kCc{RQH63;ZXN=j&=?Bqc|f-k=O0l z$k*9=VX?EvI)hm3e+5=m*E!wU)&-kTfRv7!nENL?e1dEC@u^`|EC`Rzb+Wo`02OD}Yi3-t2(lSBYoUtM46o5Wvzp?&!5_4Nf=Ou4&(l&U=v zg0=fc+s^hZD#n{Sn|(u$PGKGIlgP7lr!b(`<85{23@@|4El|h!R}@`oMn+7IC|;hW znUc%pxe1#ewo<7mSppaMB#N^K=;GvqOk!>JO8Y9$UdkDe*uO~dqsauvUF4dYt!Iid zvMC{wA2Mee$or&jJLsSX*@J+LJU4!LwNUcrzb120STcuLs#8Rft%{>LU9q>-VhJy_ z8ssi`u=@I>rWTo1rsTZQuGAXN_TvTri`0Iv$e-m#h1_xEnv^GJ$WaFJ#4Mvr>Ffxr zg6CY=3nQ%Z0uM9Zbb@(RQlzK!CmR2e=_#GkS1jqpm}Y|F_kivxeZ|iTs|ri3fq7fr zHq&{9!l}cI3ENIGLvf&`-6AXA_y=_lv`v%HJ@y{RkuZ?n?S%tgIbo292hNlA!D5j` z?G`g35Q|uI*5g9<@Hxe7lVJmMPyZca1=6h|l1bI{Uy9f|U`VKu22xUlf`vLqQFc%t zyc&hKt7?*$$k}p-W+Y9G&7L*qnDTT8jLl431UPKaipxBhaaqn2@okG5>IfqyTh2g4 zKhiL$c37^su1!HoOtP?Ham0`S=2gWg6O;oO-i3WJ*sk9D^y2%0IL@<3&b!1<>t0wnV(W*?VDF zYB5NnDk)uyrh-|zDw#~UV%CjY<+)=HgDuRk5(?XE_H|;fz5mq#kCO~wQE3Y6Hs^?~ z_r*AY?s0b86$(U!Z5IsBB;QF9#u%?jEk@nr)SLm*quA}@`A$G5`A)K0?*ZNOokSw9 zOp&VXaWZLh#_2S%g;(4U0~XT7jO%+nd-GA%yijJryxhLTv`)%X!-63`gdTTk3ns>P z(knVc#WtH;q7Dy)TF}#aJ(R^KnSY`KF{x>?Xs>ANgchOKZg1;_NsQ;DghzdvinF?+ zN%6Ay&7jRS+fM|W@wwABxl?Uju~j`*L{Ve4)jlbrYxUJuQp|9faj)zqCur38>j7Kr zDOeWOVQ1s*HAB^%Z9Ed%Xa{O8>zNkn>ZpCc(d;Gn)%2`v=Q!9JFn|dxf3TVV~4FA$ySN%Zau|kEUYRB$=H0^2{qwB47Gh*r9 zw(gi(y(v!y;04({dc_rLRmCf>J82W0z3RTQfQ)$h2AVeimF}C$s58QW7u*lT(`~}; zZVb|g(`~}4-p+~9wz;tZxnES$a?m2zzNpe=woX`Jj*BWVm%gY{$Tl;knuUn4Gdd*~ zEjr6CDh+8t6{P2qJ)$D4`@UEP4ZB-vp*>-PXh0ltt4%>wZ&OnUt!dDxcADEQHD@I& zueGmbZUW@}VqRG8nee%?$yPUp=T?vpFwV%>+O&8-xRe(Jj4znn^L%NtZqa4Va=w|) zKK=)>GLn+_aR&QIXFTF$^wZT#irwKwA|!^Svs-7iJ6~WHDR7ZQAjy5{li;Q7kE`4| z->O=t7dpipTgbA~B>hyARA0s<4rm*zfQoay+`cTBW~8L8M(|_IfmZ2CXoZ@V{#a+3 z@w%Yr4He(bB|g2_{!Ak1q#uclShwI%}psZ`)>Vy1~3<(Z&wpgyb8W}6djn}KI9v_H4mZQramJx42C zV665!)iYstQs|LbZLhJff~?}^HoJ@}{oEldsze{K>QPRm5T(3f%(h@2G{(}GfV;^t zrck$$3p?l?gj-S#cC2(FP(6F4{h1T3R=Nq6zN(U8!>X_KN_BE|)k!b4&zXlrTcC`&3y+CyqX(==iHu(sEiN4ebRuwhFwi&5v) zY;fKfbyuBXjakKr_5H1hx|LC5$5tb~PV1CKjYGC7sFaq0V3R_bAzCnHM{!5Bd%CAg ze1PQz_D4`D(PEmD6jUnJ-@=co3!RK&oH?#$Yp*n)tdwJQJKfYITfNb$vAM~~<8#|f+WKQF*VQ8bOJ`c@ zaVw*QIS<9pbWXBJ#P!LV?4o|6p@tqe^yVPE!uGz(WlhFvry^>J!amb~(-|)X$EBJD?+hYu$P#&djP;>Ai`*}QG$>RfY5`@ew(-v54H6;d!qTc9oN0{# ze`vZFl^v1^imUgdTuq}d@-*Xtx-pb0s2k9mW}riVq~8Ol1c5*nd{a6Y)g@f^YFqo( zrV&*v={Ms_GN@;6|6W7nv-b5OY8^cuwosuqK$$%llwf+NwLyP z>@SevU<~1S5nDE~)6N*f5!>oXezrTBzu56p zIiEW)w`NMGL3CMXOuV=me`KaPdf}#y^IRPDN|V_-o4bmVvs-KBOi&b?ILZ)v z#Ce`xV_%q+NSZftfoh8;v^vl+u1x4wI|_%{on@aH3(WYR8#$9U2wk~-2scN)VVixO zGqpUgv9EPqanxdOZ*+bDrtSa%Lv)UVx#iPZ{q=z*39nJ+dkHV0|)yjs@ z^ZYg&+5yp`1&yqQy6EI!t7lY+gpDI!yA_8+?G4%DRrcx7xD%lKQwfA^hEdfTkQ9Ct zPM7!*x{FuZSLI!jwkp!?$^t@e-dch>+a&9+3O?9*`@yMCX`o zRxB_x6`M4XitzU`L%>+ia;x0p$sDn*QBG8^_FjE%8#@SNyn?bD8yn_ZkW_UwL54uL z&a$piLw=vY5)y($w~A`ih$WYqFzby4=;(bH-$WLzu#yFsw+Xqe=N_mUi`+uPtHowN>v9uSVU!8M**BU zVvMR!Aj6irBTC916ZxXXdac_cEX=-YwRo}piDhn>$?wZxPc$p2qM?Fiy;5Cw!V)na z)QIgeD;d;kvVChq{XIzQ*FMWrmi$C#+oBd5;uDvydhst_sM-DF^xeY+bQcY#Ny_21h)& z*aEfO62_~R%a`=bm``%kO6B%8lM_x_YA14mGpSPE08A5cA;}z_<@WabM%67|X+LH# z%KJR1GZ;&EX%JXt{oo5--o`Nr*h=$K${0(nOS)iHw%yX8&KPpknjPxKoMDl* zxX7u!P`|n!uoNn55v5&7lE5=7&4zBX+v1jv5m%LO=|op-q0Rdph+~zxu-mk{;;ZcQ zfL?Gv7CF+~vB;&<-0bWGvvtG*bL0U7bLBi>irA*aK>UDbp+0}K9j+IyEG8_xpk}c^ zJ*(!C+?h|ZLvMx^w|K?#tt?3Hgqor zY?}Wcl;5kv&ZuGw(^3s=X&O9O?a)Xhwjz5{S|d>j&{dooL#kQ4;(o}sglnS4S*~bZ zUsX$#*fQIR!*%P{d1fE0eUdwCmwQh708MUwCLxN}Mahd=&2`y~0ciLRwxnqe^`(T; zD})OT`zq~slREHzH|Y;^qyuwUGM2kxE^WzD$lfFCyE~i{R9jvTGNP5;Q0?vmQ+62r zJc#OUrNfLxAT2X?^AMVUQteRReU!k?L>?BexepbPZdt5#OA^tTqCp~5XiIN!^<2S} z+Z|O7TIV~?btH<^ktC(A!il2Pl6lGfuw=2n!;)1_6y=buKPKVNlM%ylVa=_(j-M+N z#xQ06!t$E?19RsEN<9((tuntPM{K>Z;dOI0Us8Q#fb!7@&opct<#M%`KSN#Ny+_T;C?th$=v<$`G@P14h4UircaGxr@!u>8c*e4{nz&F ztPp4q`m7IPRW1lLaHN7w>6eo4KG!5PtmKsa^xDNMC8LAFjkmuXD1+>A^!bM`zx(>~n-#}ab(OKu(thVZDZbE&jr2mvaiVv!Y)9MN5Nt`MA1wD( zuje|}d0XB#ms^FP;T9U|%0;FT@*Rz-NO@^I96m1O9r#-1s$=a$XH7m{BYEwJ_po6d zFOEa)Vq9@ zZTPa%ka}=}nKXkV!Yw5+Cqnt19?j*bmR-E}HFvWg(90A7my;^$UA z%c|=$Hq6B3ULkn}GmQ^mJlV@gC#XSyuGF3E#A7MvgvCP7)7$L2n!G-8fZmifrl7KN zJ#G9a8?ykHa?W;O-nO^Rv30yUGBv{2g(P!eZj;j}{#M4krrm2^8+oDo^dy=JBPJaj z(Z(wqG0}(h$LQWHULQGDx*ch*D^|T}c$GfVK&Bq%%3)q1dBG2RP}3euP5Ey_)vfwc zW4sq@y{#{%DZLx5x?#QPxkgcew@}%7iz=RI97({)gc$*Q*fNCfQT?iI%BBP0hdtKT zBU8ywBM&2QK-(VaRsCEyZy-a)QZM$Zwmw;)4xdb@R~lt1`(&Ud!)m0C7B7)311=tI zcfF-px6(&Y+?2@^HEyVvM_#DsOUWt{2aCEvpx0JT9AtcPl0D9b1>&9t1TFntrB8DT z_`3x8-zEWLi6e@pd5vvNq)|GGPBGhj*uWe|8O&7oOCRMFvF$rn9Slh)QU@@^QSE}l zz}QQECIMKF9)D+t#7fHiFfWS?tLb$|O!OeJy&+0BX$&=iB}&jRI?Cle8|>vhR9df# zJZ)@esvQUaL`Om6OzBpZ4c}|3)6rhe5G{90Sr=@g58{GBdXOx6Ae^G{#23f;fXqPq zW3{YU>~1RrtsY`?O~n$+-@=-WMc(l`&#fHfJ6FPiMwyOIX|vK^7n!moUDntw0;xli zbQtav!rGy7|5+~o87rMNe|TwRQR`MlRm*s5$R(2YN89b#?xK@9q!*D!7+xG%W=k}c z9VY3*qyve(tNDX$DD~N*J3LYSM(Wrd>RIaUx}{~mB_4q|h{y+p4R<2?jRLR-tU9C& zx0oue;icVF&8U@Y&b&`^BH+JxN#w>u!Xp`W8#2}dp`0B@?5bM8k-`fsrBo!xG$qKSR=>t)`jmqwN(a;v(_4Z+wU_l+ZVUsjK+9PHTMi8k_rc)3|= zFgED~dL9tKVKOtW*g=k%g6!*jmUFqAV}i@=-A^x#9Mep1vyBuCI{ zBXV;}-Kg7b4P(}%NAZT1dUB#|V}saprQm!{roOVjtpM8B-bDm^1+GfQx;u4YpD@{u( z^*JgSHvy9HlE%Gir~1C3MRlcA_dJGPu}fxqub3=yelQ;SC+>(i&38PB9N$$KbTWo| z_1ehfv|9%B!Hf4IS1uEh*JY;Vc@J2fVnAafj=A>$6KcBltvjXqpV5_I+2$g0~=jnsygK#=I{GT%xX zE%g$DVajX{boJWE8Yi^KdXp~EIaoC%jW1!;fWM7NYAWyFfvU*))4cci3mtDOf6tfXj;lfwm)(AS;_?eyy zx5gVG6_?YY8+m8$iiT&A6J$>DR!^=>y$Rq=7|nFnIK+cJqpB=(|8TWe%95q8Wzoav zPc&|)S439!Ygg%p3rcb?3=L~6?$Q^Ol7K|ei;JM4#~1@PlK|VOxC=Jj^CPEU`C0~b zCgvormq9K04@h;Oz5^?ojyTM`1;}!+eXIFkw>Pq886vNWj07+`PGD@+oB4&J6)An$ zwYqipH8I>?5SiIhZ8Ae#&N6E^?UmbkDO#dSMm(-w4OtucqW7e^*;ww5xU><&Y-i;g z@fh84CU$?dMujW(pdg$-Xu`~7j2o6mbkl7g*k z%aAN+`LwHzBX;d#ucIJKpF+1|LJjw#n(YqM^R~XBm}vYHoM^Ylp|&1mdvEhiH(^g3 zR5stqj0*MC7{rRDV4ld7wKjhO4{Cdbo4@h9t@Mj%t2y2vy0$<|l=aF)*Br9-!}<~j zOJ9&#Vx8oALJsX9r+B?0a%^c_uGZ(?n*8y=%C@=Zcx_d3)7&>qwGckg)ZAbYDG#n# z8a7ciTOGl3Qorz8$Qx}K2RB&D7ihE&Ezp{kb!dRpQm#-Hn0*`F>Xnd{1Y*iGnD33f zO%&%uWG=n4tvK$Qh#(_gw2>*J@tYU}QibS$jlr&+U2S83E5c^=8ZUw@>IkvKSZB?J zYw9F**BPp(zm&ZQ@<1Hx43`_L#=4hX=Nz$hy8?0eA!4te1lpzjkQ}phy$0s)YsS7V z?1|)vt>-1glS-{!z+pn9gh{ovTYw*Lz8>eLj{|bF|4ve%Ay-nl{}-LX>1{QAu&dWS zW_BYKZSe>+_l;|c4NjVXpE=a!{I79Mc|DfZiykv(1E=%5t2szvbxpQ7VO|q&^|Hsp z%pVfis#<8=AJ>#fVd^le$(aH3yu8hhD8zsq6E6qmkV+$q$U9)K7nVu`^me>$M!I43 z!p9S3M@?BDekV}{-6yr(k%R>?6r?cT+glVMBVM0QVNTk)BPazS2S?WbWB&TdkkQfQ)`S9hS(5HgEo&=TzxEC zwPZnCV@gw=GxNRf@!zU8j5XdLROVRa4!Ll(q7~yZGR5~|&VP$K4zsAs+jmp7D{pQx zygsO6cAYWW&_~c;*4`$VqTh^*i%IG?o3-lJ&IoWEQHL09hOl!GSB#6vx_g7dSpOjs9S$zWb$JIZ{bWn3+*$!T)po>+~W%ZD*E6RQdLfoc*I^n zKh`)hydZ}8SiI7luCkZUADH_+1?DzDy!L5MR@wGOiohz~LM0#xG^s*Iprd1}ZZ2hmm+qZR_YuCzC6)&mau_l16`_%!5?Gnec`Ja_p zGWi-?=PMxh;#910<>FMjsjcUw5m3^i30{!oIn=;k8L>=PqFK5^-!qQLOi2#Vk>_k9 zU^nq_T6qj@pASvl#-^Pn&_VZmKMqAjO-9blYcSd-*rLzlHYS`O}az6c?4)j8L^8vXe{IAhtQh0Ym&Q8(< ziE_Sr_NBk8G9+E>3s@iWK`YNA19WzztqZnv(6PJQ$XOAWEAQ_0jY%)75zdzcgS)IK zrZ@S!KHO%O%(w(+O8a5S$ncdOlJ{Wlj+ijdiks{5m9b*p+4hS0ld4l1%++%vGktR> zrOU#(5!GI^Vc{!I$2-<}pdMXRl|8X!oox!PhWrg_pNM^7iwTQC6IS2amxy`0L~a%1+5!F{gBJ{VeGdAv!_ttJST|TBE!E| zNAN0{Rz%kqys@L|B4?q6*UBgF%S1u+MpT7s!`DvQYVx4 zKM=36t%;(ASSncsw{^>pVa$w6wi}PI5UQ7Ip>iXg$W-pU3&<||&g3Vs& zg=Cr&{1H&swb_7fR8}*SYV|7l19bOBkl*DX)3jgM9RYPVb4}}JuIGjl9{1Sb^zP3@ z+O;=01qT!g9CgI29rw5L=yWc%B^liJ=Lf!8bM>#EYU%zmoT1Vk$f z(3Ma1yl-H3%qTCHADAP=fGNYolj4V(D(c(vco;gEm&=#Q+HpJ=ijfyFnABJ3Hi7$x z9$rnK8%riBI0xCfW6esVA`~|k_cb)Z(px)A<&)%chFjN?WRvdLS%ZI1D8IFDt0Wb+ zd98fyUromt+nO0cPK=2{MRWnu4vwk)?A~_Lbsc~P(^-X9Da~o4( zUQm{~_)m1Ap$=8c6et~(h#78P7Jq_?{eS}{z#2|J(^(?{>cW7j(x`v^x%(k8Mzh8m zEO+94Bd88XhIwO>g~#QRkNx<#P79w6N=B?HKr)b3U`6Y2q4$8H?2JuhzVU%)O^`uj zaJ5~1=f9{+&3Kx#(T3l=B>qf!`BLJZiBzJEP*iWSQLED1*N3&#JY8s==8XEbc{%)9 z4f{0EFpsTO9tlBt1=ul;H~Fx#E1hv1H?N65Gpj~2Nh(FsvFslim_E)0xrq;7(swI{Bv$le&9` zxVfE~iZ*Kh7BQ*Z1-7eF0~3%J{LNEYA;MFvGy;U2nc&3Q(Q;UM?3lIT0BwOXw#@5~=a2iBZTV~RNHbttGVpfus`wX$W!=duDtDS`+?sp|F^I0HQ+?EN z&JZUP);6z;Pezb^t${?EF@^vd`}~2vRN+vP3FTIfLXwPTUyDbeE{i}+P{GJ@CH%@} z3&`9jh^_ZE5aWH#R4W2;W$!D8>~ml^oD-yx=6Hyh>I1W?QuPuB?PAU$TMuldMss1QRZN#Bw$gE+k$WmwpiTQxCfhol(q+g}BqTA7 z1sgWIpXadIrO$I}v~4bIl{&hT*h-P;dLo?KxUOoS=M=Mbzy{`io(Ja2=Q)GyHSq^o z2G*_`aN1-@m6@rpW70~VXJ{iEdY!vr6Ks$`%z1_Zh9GOC^I`B(z?rCL6!u6GMX=4w z;t#+Wum_pH09@ICrHHKywn&c@zQLUiVq>dijt$=OL2J2->Q3gTkgW^0aE1?YV?`&`3an#HS$$~)w+YAYeK^D$} zwc+H@S)oHswE4$*)=QYnl6aSMdRtDz_S+E2HfEb<3!G2fY;nM_-c)T)>T`vyb~4g; zBX{`@yvpIT#vN{Do1NnI{uf%|E2sUo*+Sfd3zg+QPDuNi0lRH>2HDHvD>I-Bw)Svq ztP!D=c&acNifoYQj(Ac0K&)-t`o%ip%6zCCvQL2ZMIMZ(fLxgpm1DO4mqKI>j*N|s zN8TDPDsSvWH)$go@2qvK@?UN2$~3Tik!=Dj7CFvxEOP0yoUXEUyaIBZv~O)q87TVRGC^%DpO4A!IbecVddk)dLgsY)cim!%256oK4UBx zDUjD)G)@h3rMx~hCV_sIxN^A)WE-BBEwfj{55$e@Mi3c@Re^+e^ikLT62+I?7U66U5g23dG@XP7+qGEm4pKQeFyQfFID-HRe?`uu0PZ=yO%o z0^-O=*{)~N@=e>_-dxVh%izZfk30F;+NiM)zLTE;ZiXveV(WMX;$gQs;*Ygk&j2^O zl>)YI*GASJq8E8hDEB*i8~vP3S$qC@tRLofS;P#R@>~(GfG?u12J$FonjrNe!`kN4 zi1x)OlaD&aPw(UAbtPjS{+^}BcuhBmH zsD1s+dUXMv%zPnZW|oUn~ z_lhNsy39aaxh|7JwyxKJ+%1l?FNH&=V} zviF)RHPaCpTU$+^k6Ax|s2gGrb8AtD!0A%D^oqF6O{MWiJGWXsvutNn}J_G)8}QOXTlyNG37vJB46~jF9!LDyIx;KaXci zglB)F^j*?n_o-K|~#e-6xxIOqF|#5V{vR{nu7d{M~j z!8{(rn(zYn6S@lrJr(UlBO)^)QXpclDB5SennlP4`I?1Sz@O=1eyA6pOqi6yhc;Ak zBj)uubym$?rExqWsIOS?Nnx{uuxL0~MGe}@*3_Z+O^e&yHU$=-q3yS8YM#()l;Q@P z{49%k-QQN{e&S)6g_c6lft+|`3lQvOn1_BYn=Q!5A{5u&0eD=_9dPArL5A3C;KvdV z0~UxY8?Y3z<$Vpvs>e~VkzUfG27NWR)|>9ovwl9a>*enyZ+5ELF?`PEgh&ajw9lFL z!+H68v0*f>7w_xtuh`L`pi645iMr8(GWpd+`o1GZ9jLX3b-)wJp~v}QtW_UFrig7G zEY|pBrr4#9IL`6|c{F*VA4pq$2DzPO0?6brTklH@-5q`yrF3R55mX&w=)%~g9=R56 zR0pz86?o12fK2$forr`p=a*N&{73ZN&T<{hvB>N6wz?Ki46<=WVrQd$*u+TJIL!1m z)4E+QfV_j|bZcApOS5Q5@XC6%L2CSEBAe38*IW+rz0Qd*vL~|lH{x!vmkzkJ!A=2N z=c_L#La6898A3fc0%R+9Hj--eca-^z=Ih(VUAd@Co_sT~nB zW4lCO+?i`I4l=j06bnlqWMu~~O`3Y1BG~aJ4eYVdVX|YTOPlO;sjUMROFa&BU+UTi znzhbEId`vvAGjm9P!UpFymD~yF4Q&&7CSr+_S$!N>4TkaaPNm{oER0$;fIm?LRp2M zEBj%q=V}_M1E;O^?x1NRTBNDM%mWhK zth3g?92abWPBOeZj)%Fjx;r+kSa(1xTd~X;^BVYJ!2)quu*!%t1C|KjFWgMfdu6~D zcEhY9Y}?q>+pnsXi$r#I_Y(MO7n|tcrdGqn%poSs*pKCfG!f^zTqdkVl9PA^3x8JQ zfO;K#6<{9}=A*G4;??kF(?PR<@A#l!QE@&&tyu(O_vEfHK~WX^F?ej!2o62ShZ!o(}0=v z_G0*0XiDqr?Q*lP>E`BGPmqSn-d7lJOC#C66uvg~K@L35@`BFDDa$=c1Fak_znx_i zR?auWgOj9&@jP{IQ6*(~9_JQ%6f?KbX5eSGdA+-;ALMib(~i)W11&-Y8qz}D#KQYw zE{-}IIWKhyi(1pQh#y&Uy5^YA107K3TWy1ndL}GvSVDT-nb+Y^D@oG)Yz30+a1Qgy z-6lKsak@#*Dw92)gcp{LwB4ySI@pXmm`E?capk^(ogepAaoA}-&=4>I`_E+S<3= z%0#LNaUFi96kqj7gV~necQ1=iiJf5p=kQFF6O`6Y^Gxb-dhaMB+t^+c9|QJSAXpHb zy`CfyCgRB!PUDusEve>l_oK`>lgTvQ?1?Ns%@?bF-_|AKj7H2SNw3Wc-_&^t&nMD159Fa-0+ioE__cZ$FG{t0OM$h-K-L#X4f* zNpV37@kM4xHJv*}RyZY==r-(L9bY`D2k~Ykh*ovDlLb{wxgJF+kXaErk;kV4;gqNl z`M;guD#6)cH1>kC$y;VDi!n%f zy#w(?d9Dd&-GD0_FxJ?|&WS=kHd=WU?d`46)+NJoCr#?^?PRF$zS?}42+TXOLfY38 zByMhHR$KeL?!L}$W_(GvuRG#)Pmv7|#HBZOb~Dr0Yawd_yONyhjud2WYI)qHjrN&T zGSFo(n1yjCrs9gGolLUB#8x!w;H2!!^Wu1s2L}@QzyL zsz+19yHOW6+wXvF-LZvuV!_)+mxFLa@^Bg`i0y_Hc^u_h%))rHDq3;pmsod94JaE5 z)!O52bY%BSDu-9>@s)(hx*617KtE77*MvA@cw?2>27-+dU!4{ABKq3XD%(UlP@*&l z);9lS|F}^JHsp{M&sW=fWq01UWf16~jJ$8!9gezl!=@OGj4bdC&kC3{x z?2of(Lo|`4RX5nZivFgoFA366H`t&LwQkwM>IOje$v`inACN=cU?ajrJ+8940f^yC zt*#F3Mf6wQPTI+RXRjnrSnAR{y9qyLITNp=FW9h~wY$+jTwYe4WK?X5Yj;pTCud@B zY3q^&?AYP7CqRv?uHQksBf*yQzrCJ*H{QCPxUn$aCo=lVcb61hQcRuNZm*`_0e78) zz*|}8AO~y{V*!`|3r_P`VetXWO0?vVtqVqmm*%SVC62Nt`<%6>l@%?(yiISL(aqbvzJ45O5kAo=4l5=ve4olyr5kxavteFbKLB^P zJFIqdP0H1CT5DZu!Mw8mbRh%DaSx*%G6$8NF(BvE{X|_K*bnL%;g!tLPB_ekeUb(m zv%NUGP26N$F|D;gN4PPg%5x%uQCRs?sCUfv?i^4rurERVi8|%j;k1K`1zvoIBgufY zgEif@U@7MMiLndQJwqBpu7#h=+HN>h-v3Ml_B-NkyJx(V3t~#;lu3@*mMMwEAs%ew zKu=VzG~KE8fnHf;C(GkC_Av(#0#P4Auw{l@Gojdg!2Xqj$GVhbn%q5<84nb}9cQ=u zGV8KZbJhgkWTFYa_;rQ;rxWDyD*KJTEpD?OtnN>1`I5Ry9I@u(M18`-kFKH5Ai}p(Utg*8B zqKL7;G-@;@4~mvcy-@OwB~!dM8>VZD8sE!_4P&_ZSYkZw<8c(dnp4Qu1M7yXk!TQh z2y2vdj0YSeC=8fQ8V$EG}K9m!yul-27^Bq z;quTasT(fmN)CsY*dIH4SAQAlFUI8V8#|p4>#VxS31k02W5z8rx+G*f$13YALvJpe z6wxYYwaA0?Rt{S7BpS`u3>cu1EFr01Zk>mCg^wTcC>&wu{* zRN9u>#$qo=cWhzyvdE4|>bnrqE9z5>L6rqpy9XBwgg8yks2f}WnUN80V#MLq^ao@z z1sB$O0lKE%6wVZYnGphRg23Sw^0rPu64@8MvoZrt z`&(PwWQDx2{y^OsaIwEjbKu^w!-<8BD;7I^+*f^v7r(F44Q|t6vDCqj!Y9EIV}loF zNWEu&dta4$1Q==HhQrxO$h5fQDt{_%)4V9^SHA* z=JIzI0hZ|!J-n!Xh?NXG)=^itV;N|h6jM2!xm7LgCjzqyb**i%?(TlPC5ca*YI9x5 z*_1G#e9xx9eM;!EvfIr9dljC;tLg{hUZ0!s!RXT}?RC@ceNv(`7L0O?TjZcR_UME*YlWgq~@x!`CcVI>8o-_+B#xmt^1r{O*m@Ry3!#fsA1?^LuOqu zO)A@TUG)Z|nVDWo6JDx(K{GT$MOnAjwm6Bp5Trky|yv>O|wu664Lv>|dE!`9Rd{vx?CW6kE-3wmvT7fBJvq0S&>!dWc-8AQOjzIA!-N&y+PMw0acV$@ zaMnigu54*lhmpU2z1DInxi>2*#q8y-2WEAfvLj-dQ5l4MSk8jA-lZI}jZ|0Wz3s@C zh0&%35h8c*^-eaSwOQi88@^Y^Tls=Y0ejc$Uf?*!>kYW}Db5gk zW$V7gd)!VPapg0d0=90~OrL|~$BtbigId{ZW`N_N&W@#A8?mCOTc4i{s-v7S!Ok4y zCItX1ZGVt9S9(p;b5KWsvv3rhuDm3p&kiqXEk&1^ewIViv^A3ffoD>x{H&^9$=)>9 z+4moMN$cq%Oe8x-YZGg$&K>du2gyFH_gQb`MoR(#**741N$a-A^nTFJf&PQY&~S0+ z_>uiaG_|IdAeeo&*h^Xu%sR^C`UYms@>y?QWs8*aWwvgY&aTuwWTT|zb>^6U@o#vL{ zOerZ|${J;mSJVJxvBgNaAj5N!SGL&b@q3HSjbj`Utudb0ZLztIRkz=nFjCrLgV|eb zvheT092Pq;%VO6PacLGiUuK*1Vi5f6P8+H$HZ~fL6{7S~;wMz5k*DZU&WqPx$9jen zEYWhIhQ6{8C!d;uCeZbSO?rVQ74JU0inZq2%~Dol3N3Pj5+PgRnCTs$ep_*+mtns;d-H`>1_e@ysNYtyT z!_j*8vJ+*j%p!&%%X=V*lp5+*?yefJ5%2KI#L)5X`)cHNuz#74ossH^I=WiUA|Mw$Gi}vP8%_ZfGEH-(S&{&37U`b|fO!&;@t& zBxSxQ(q@xNG}Jn3o9{I---(8GU~W0y?=UOLyO+z8+y^>cW?O=s4)iZbbvEBqx)-Q( zNjkE!D@OGEW+c0h)26}(V$SkTPwGHi`7CFdd@o)tDiFDTtucR?eg&r$C$#Vyb44YM zN)#ka!hp_r4D~Qxq=t}yRnDYvRvXHQ_dUIKbwKX6Yd|h+yFlzSVP3j=B@;$l<*;2V z^~e*w#lrgj({+>O_OT-bPlIay_!2wN$_5kK8$85Q7_B*8}JzxG+qDpX#w zdSGscS(r(3Q(~`NZO)h3^1tGaVq+6Qjw`D0zUGcf_O#P6&r3tHPZIy7Pco3xp}HFIZk&;gEcVLb<$79P&|@b@ zqMum)M>!Tnr=n-!GELtOFc?obGCK`;5BS&{9mNyPDEo%#u9puQm>EUMVw;A;)9Y6= zRe|coy}utgA`3#+2SR(LH#i+8*6ox>W!8FNZlyV8gF0r+*j2Ai)yx=>t>r$wfOQj@ zgE!KMQS^*0q&pY271bq1fjFINc6t$Op*%$Y!i>!nCMEfCid#^(Xaj1>sQfWw%}y_4 zjn_qfL?k$M$5W9+X>KL5)LPSJQ?WQF)I0ONcGE+AdL?V2JR7BxD~sZ@dP1cbT6?Wg zF@Wi5*V=ky>U3Snq$p2vaYc{BcIh3yT(k#Xrae&Ou#EwyYZgCLQFE(4tMgn1JCRBOq9)Esnngg;I%!IB%6PFHS2*nGGc+b zaz-p$X0K+wGGt+D+57t}OS`;wK5Wm9vX`iinTH$D`l+cdEJ;m zx{1qMxTOfm9@|58PPQ2`Aj??E2iv%!9{As-n~HMGHXSxF_pYK?=F;8V=`vgQDdMq@xjFXJP3eELWGug&K#<}PaN;02#ovVwdypXlheo~@R+K>5@ zqyZrekq+FY!!?!TM}12JX1p+Y&Sr;i^haJ?X#@92`PRnm(|}&cT9_A6{$$Ep!go;@ zS!qkAoS0SBJ7Hlb>)bGNsxR~sI4g=`aHvB+a%cgVFjc81w&SP#rSmW+*EIhLfu$eV1h zVLj0YN-yZQvrJ`H73s=nIT7VRTEm{@KpbZomcGPpE9Zr1YEKr~`d_ig<1BZ`wa;>j z*?V7onL}+xk`-F!(q}n^Y~8P++KF`c1?_i%Rl;01n%dP7TP{t~n?mW=r3CA^Bt*jR zaI?okcQ?EALT7$9C1nE{zYAe<@zP;wt0w`ynFrl;g-emy!29!i-Ri#;fE$RpnP++> z2V#tPS;3iv_IQ4;#QkqVK2hf0VHmL3FK7#)?X8$uy>#Uc^PLl7m3?kxeI)yOpWu)% zDus4PFvE;GU{#6vN#(v8hvaT6qDPQC0qPph`vgzes^&N=e}6U28Lct!Np{}C;$^$M z$>)1|zLK}^fh-m?8XW_2;qwe;&n)l)`69P2&_M@{xvz4gF)u}F%qe8+gHfhAX8Ngi z=IPS%{KByoIcg`(`&h~)^Y7!Fjx|$zl-b#VdBf=oi~G)RY4BG&6p5wszaPVXW|cVCMohq6Uu>RlMS` zlN;J6!X|Rp)FOwo9Rdc0MLdzJU`z~u9QBX%r8(3Supvy|hD@p?v-s=$T-=h89AX%} zaZJ2Ye!w2KOjGUf%WB&)Qu1%p?v6DO%wiK;Y^t|MdK-y2`f!vYt6R6p2qj)Ce<1ED zv4Oa9dOL+|Q(^%*QepwQc1kS8Y~8Vexu>^b>cfGQM_PCA1%}&aKC!~)EKBBa z%w92nVD1)sU@mO2bI8{FLSqMeC5z2;J!+RjnU~vZU&|#8x-xIZ^aPN*rAP&d2Y1jC z`rx*rD-AVI{Y>GeTxKXB`*G2y*UZ=OO;Q8jmYLz3O7$MKmwAB)x0vy=XzaR$UWg-= zA&AgoG{n{oEjZ43^o)ODwzp6tzRY&+bxG*1l&XrmBqZ-Z?&^|WJb$3Zpl$GA)gTMg zv$j_PeeDh1FOxA~-P}^cTqY;>ZG!{)Y^ck|&TMdob@0c`L_NKLz6$Rb)W~tHqxZS= zu_iN5;oTZD?~ui<)i*drd_;;>g|0g!6sG>g++VZ5y@>vzrr(wuy5mVH97tonc08Ht zvYd&MKJMnxgtmJWO@rt?;HGR8jqy$jl!=f&kS8Sy`kggX!fFkpb-6UUuM(dO_PH_M zb6}1GO>!RKR^0U-O#O8XH+L?l0tM zDuZT7gijuLA^iXx>5`SXKE<;oDPZe}b-+=lgQ6n9l{3Bh3fszL0Pd~N0l4xU>m0H5 zzgDU<5Q`Am;U7pJO39V($`$tuZLVr$sWyXc&v=hsKEESY|FhbQR#fwu-z{RzzLaAb zC{2>%wYjj4cVTi9LWg68B|5Z~%Y-#nMGn{|!U8Z%3N;E#eB~k^wp>i-sy8t6<~-(* zt<$wqQ`bN))QN7)^x0fQ(A6%vIaUYpdXNz ziy5h~$OM_j8_?%B%_M z1(nlqyS3zGN{YMPpeD8LDzP+`yaRQ(-BR9OP(H2gcCT+V9BnSd@?!c6Vp0#7&cl@9 zgKb1*wM`jo%^U$UbMN$u`qDDp;k1r(t$Gc!z{{WOjH-WmMSW@(O(<=pRq~>HnCTI1 zkQ0{|xuT*0HD&kYy1u-oK8;n-3lvk$^wxoTmJD~luIrJ-?z>Au>lCe+F8SO|xxBDG z!#I^N!2RUb5^gn@Az>r;v|u~5QlYH1FUV^At1;pMdByW)Z0dl#QoEvGxLn4lW40Zm zmzUHhiw~(+p(IYI+Ulsm@FQ`vH9+(wIv%^XKP7wA*B~*yH&*h{L zThg1Yoh-9;%0Rfitc=FRaLJ&p9AueoM#q-h)h!Q-0b^^D{Qg6o0MLk0; zseEB&45G`c>IY<9SkS}<%>-reVbTzhw?m~Ea6;nv48YbqVZXgS{9 z1+BcZv#}tTSJn^6%@yNr4#=f!%g$xrJP`M$F$T^UT@ymR#cD&@5Fm!c+>{5CW`8YS}yAHYzo@^(X_bh z^XpkJ&KQ;HOp9AG>IV0MXVOOTb==EIcQ715?fAO5@kFyKEwlA3Zg5_-Joa>kl* zbdW`@k2~|o5~Y2}Q>V1HS_%QK4OU&jX5o7arcIJU@G|>rp8%t!rqJ_3p!NFbK}g*# zo&}d)Vt-W*fr~2gf@`N2tw{CIYFRVDWTBHR@a0AJ7joU2ry8iEx+Bzk!DP{G#4%?4 z&&ai0US^+(ssz*Bl4)Lurb@_1scgYn>Ls&eoKbUgt(0?gQ^}GYWS3TY;Wq@UDqFHN zwj>v1A=7xHVkO&LHXNqW)`}dJS31XR9kY!T0u93(_ZH2={F8edKEgKPksjsPu#Npo z)sn64vL>*4%`$IXP^cNP&KPlg6f$M4m9)0;2DL9zUy0bh(8_Kp=GaIgP{-6Q&CSJl zplUbPodM2r2qXy1I?qaa>ym~kxu7(qUWT1H$)eFc>Inecw^gEUdwHq-fV@C~74(2C z&FZx>L z2eQn^o3q*QS>0KtlF@n>?t;#IT#P#6C|54%06C%aUsxpvn&1(4TmFbRf^F12cXVyf z6VMA}!NsrzrOIqzFHM)ao#<7hLDG~t2+y}+5_@OH)?Mjj z5ccII_mu;?l>3W@J4Kda{}yhzOGbuVUU5GVYa_RoO5_ft*HW3+o+7qxSRjrTm6knF z)}V5WN($MQCOfg$GY@-Z2AxR#X6-pmOlhrY2d`R32b4_V*>`CC* zIKScpBEPh^o-?#%ggaDZp6}+_Svv$Q)&Q=M3t;C z#=Gg5Y-qS#Te&Prq{E=SI?@<`-0tpYS{RR(_GG?<62GJZ*S(zc*)h-1eO*o;1p|Xw z+=>I5XzXoBca)>G*KxpWiXK3Rv{EfsqP5=qqKDUk^Fa+3%2* z7nx``74df_Ac5KqTd=6vPM6uM?8nA#os{Su#4;~ZsyWLoa}a%#Www6Ugg_o66ga43Y^j4Tjj&-ech-TIQrU)O-Y3l$??4)YGpoJKA{jugF#T3u zZeN`@2oQZkjcAG6EsqNq+SxEFb4P3VhbaUxY})x5Pqef|4^Bx|eD;d_Q%Aj! zBiT^>Rl|m24V5;|x^0w+LXX6T%u>g6)$M^U5!*awjf!${?B%Ml&ErlB8D4TfAg?6$ zAM9#eu{J`tG>1Kz#&~(jeIU2#std-fX>#|8vhu*09a61!b9>w=Wb22i#LkVy>D8d* zO9LoLn#f6kaB-A_JoHvf89JL% zWMNr3wNs`MmY3ZR+`6@xheHB^EYZybEqiH!n#u3P#KtT0visQVHmEP+%9!C+Ij&4Z znJ!qI=AsV75myG{(s5-5*(>e`-aeQg3)fc2^iY=G4)TGu zUGJ>+?gjS)bC@vs+v>+D?Rc+m>-4rd0+CnTADg=;5aK+SPJ*WA*}7!n0G{pq-bqkR z4Otq;xp1x8saj1l1kS!%H> z?2U;FnUMAgAj}=jSkZ=BYGWsvvKiLJL(LfwsUDA_DFjewDFnKJ9cpB(l_ybu8I&6M z+-s{(OV}B8`f9;tP}qHfjE3)bKo$p*?%4rZ#v75nYLHtz%{qI?UVtCXswR87BHzM( zZo?UsJy|y@TaR+AgIMJ(`Fq;zSb)~LLffYwT#y&a(Oc21iELPs?}i%XwzgC-zmX2a zb4G}*a9p(~vt=(#QjRw`feU%i>Xg6~u=m351s?tgi7`Bw+U`h-*w!c2Zo`%iYHwfS z%FNyrudPi=VQD93jO55H;!rEXmn9BiwtUp=_4WgCBuCVJ1T(I3ZX`$Sovyyd!J+Ah zD@TtxV()VW;-1(R;+2GN>BM%5*{ki#*-G4j z>C67*^B;cyhrj&sm!E(7{Kp^v^!2xYe-|-`9Q|3QmD^m%3K<{NI8g6pq`fj>bVAdO zqBbMWc{R+^}E7)4aCq$q@-3EacaA)=d~`RK+qo<2bm@eP0Ou* zkbPpnOYE=QO(%NvqR*TgN(Q8V7Lr~Q3I_AXi8egj*Zqz{8;?lYh|OPKVt=5f+^PoP zmA*mHQhud2wU@i42I`f%g-h#tiTyay(`~f-c`j|VlLI|HytqEKIP?qV;f+Y7Vy6qb zv^SDu`oL^GuqMnwo{KW;gW}`M!a8qi$53>+ZyCp=m)6-T+x;G{sSX0E37{1IVQ2ay>1vQZPQ_adJ?Uo z(Pi%KJ*g5~&Um-t6#i#YcLlV~h%M+1Z4}ufQ?dYlp)OzCH%XbEloKB=L!=>I58U&1 zX`zd`oZ^+JKKG#o=V$&&-{CgK0I^>D9-l@I@GS(Y&}o)3|TwW2@#HEw(c0dX4716m^A`tQA@x6Sr{|(82O(Ong_M@$JoRB zL6!tL?sKQ9f8y$T(0&9Da6RhzCN17b^9O*o4%vi~!A_$J4Zt+>1DZ5<0}Z!eK57P8 za=RfHET)?d*w!Tz%oCeEV6(#~{88eL6Akie&UdW!0taw4@)f|gUfG24lC(T|lTBQy zNo*SW!Ikdj8ZQYu8KwYa>yjxSxbjvg*PRmLC(59X!>N|&{#A3O;XKYXCd{TW0oc|l z6U>vc)qs66l@SeFHasIatFAN&YR>iULM<<_FGpLBHyfN+$ZF$+;-3}Y;57GguI<(3 zHTEazYLOZvqp%$$i)OAaG>MvdyRLFhN;7AO(x4Cs8QZS5bp{keYC4Sr>iD!%5qRgx_ zdU~*omU{tHjtcF>rFDvL0=ZX00F{?|_OxaoThC0JS&*AfwRG5T#2j%M^pcJl>F*@4 z_A2`;?4e`1BWnrri~GuDRXfU5&L2HBw4(pj~& z9&9Q*(~P2=PnHo`?O>J}+gNlZ{uN|bHe;&gwOG?FHCUV_qqj^0?xZc&SY$2o5_7I_ zCPh70dHmsp_g9jk)V3OK^!6nZxgY^j9(Nvqc=7!%3Loxw zgdkpkUsG`;2dbQ*=E6ghCF;ig;kcJIY43CePGr#z^HC{BWA>;Ah~fOgQ2g_Xj^$JJ%iZL=ws7kh})}jQG@sD5i2M z?1-6^5>gLZ_Lb1RD&~cR;bIepUWb3iGl4GIL901w;raqJQmADYdS#_oyVUS%%#mI+ zjgi2tnKANZ3tC)!VQn*ZW@e0vox?m&Ec7&1T9dx0`jR?)T=nuO?MN#Z&PvaIrEN)5 zl4C3|XZcz+*$qA=V>o5V=oc>+b2Zd3Gm?K*Q+QFW_TyQYI*25!Ul2eS3YmJV66zoO~{KFsc+I?!2#SaxOL73aEuz-f-jo{zSWIcx<`-Wjgz)`Z)LL%S{rOfliIksVOAc} zaWB5ramBgFtefV=`rA0LHc{qwSu_p|6_o!U0H?cWv3*-%dfk>_S3PcPg*fWym!Fbs)S1${Lk*RA?M z^kv?ky~@0Ff6!>du%U%Q^`rRtPW?5n-5;1|ZZC~0hKJm;OY=tFcrBL}SG=7gwtTZd ztl*l0-atHwB)*l)tMtYXD*%HTvp!ltChbp)&%MYC)Er7&DDahAwd;L@lEDl ztiOYf#`<}psL4wx+*l9jlW`QME!2Cx{-8+r?LBK~CsAkZ{r!#@+e`MVuLXX?IMh^o zm>6Fib&@djg8dUp35WVSaJ?QBwkAomksOy;#T|vhSnmdeLaNBCa|1#hM-Xnid$Ogaj~UqU(vS?%hm*n(B{q+^eHblL%h4e`T#7Bv_h&JACksetJmorK!SeGM#~5V(X9vX7#op zSU)hUQ(z8CTzz|%Q)JSLk?FnlRCO+%B@zcZ%aMD9d9-j%GPzZ zo8H8YWcRqINsT@TFpFQ*nM3AP`!xmb5)GJu@=)eqT}=`W=4H$uH>%(nL7}g6GOrbh z1n-e`(8CK=8WiqiVJ~#fHGfz=v%6z#RAd90E(AS6<2cG@0~4KKR@wSufjF{(fw*!u zFo$eYV*_$$-o$LM()GI>vvtJ4+y>u;j4Xmjt#VgcMK-^wtREK3OxOH2Q|*bJ&9tZ) z!dcPFImvU=mtoA8=0dbfYGM}ir^K;$Q{^_;dr-#?@ACUWDUbwXo6s=v8&M{jb zEHIPpejKppw9G`6FDN~sm{D_Q=a4Ox3}FfA{J5PRvYJ^)la+=n%|)`@E+clc3OHCLwk( z25{_d(jzorS3b{d?iD)GYUxBsav9WN#=_Z-+p4g$om(;Mh4pS7DQVKFq-jF*9;#)h z9?88E`xAG>tN1VE^edqSQ_<#aky8@f5dr*$F zdmBzc6C)Fugsu)v?rhqrYL{PX#?44cri~ipc;5p%+_0G&8>(GpH|#xK8hBzBbwAV* zU6AvG;9EJ9pPp&2+dnKf_mz0P9dPB`Uyj(iVIkuZcAI3j#Cs}RF~a7gYgods!w>sm zT0S*R`yYPwt508k{^^&0`Y)fR>3fy?AC~R-#2@~fA3pyuO+4;@-9I(0@PGYZTKmY< zb<|NX3@{iL%D~%9UJ+RwG%8=$$H%EP!eickn%2LbhpG@&X$We&R%94a^lxwUAOG^l zFQ0$?^UuG0{q*^VFTeZx@|zWNctE<^Xj7$2sOdGTp>LxZefc=5^b^pXVVFg!lb6v( z9OQN3{@4flMojNu z`@o^jPZs*dj2TOXt|qUZJ=QvoG<1!X=B?64I!Q@D{5C5s6@`U=Wk-aQQqYPp%*A=w z<;{Pfg7lMxZif3E*MB0LE&Wj(U$i${$#2EkyJ*4PHLPm;II7(f&|`OJPER77-BBM@ z{hHitM}3eIh_v;JVt7GN3|2V@DClOXxBQkV>VJ%TYXz;I=5!xm$A~CDR`DU(_zS#bfLX; zHkZ@A(3%g5K9uyrC)y)!IW2rKVV(8vn|lOUvC?$|tbCoVH^vMUsQZ0w~t=qMI455xsq1j z&RC>Mdzgb9tIRM#v@np;g)J<#qaF8TJL}z29dmX~*^Jo0ESHlTd0?i?`uM;``X??} zZ+FNbTOX`jFI~*^stxO|aod0A`;`Rqg= z2c9Nm_u*bHJ zup+i~;jkj#*VgsY5c-|FX`EzIfEsU5_#~4X$9!^NCw>xK_=76W z`hl&NPb~KBZ)Ou5q|xuTc3`G%%{kk!u#Ab!q(7(wrYj2N`x$o`L zA@h57xAnkcckjd)Qn*JJ8&2g;gmKR0(w*UrXug&WnMu;WV|OEv3{iu`AC)=j{O_=( z8AMYpf(+CvlwmiY1kE0YI(!l(@Kt>duVh!M`vax@pV+aH&`(E{;I%m1ac7MLNu2At z36gi5>O>E_+wW0>?srz@8Z@9OTDjhq$qQnKDI4^PerXk^SLAKvlh?v#pc29mI46t` zTcMJ#9PSx@C3PI;($_QKx~Ve-kjcwnujrg3hk(yXr!fSjl|k(-RaBQdv?fXz6U+&7 zUvU{f7PJB6b+-|58skhZOm(XDSmu1_<>VEwHM&-*Ule^#Xc#m7AF~u9@W>$&eVFlnveqpP z3hG1)WFpCF%^0;S=oOr3l7d(zo!gyx?`9-Z$M_EOkd++WnR@mQ3WH zIH~ST5?&G)ePBPoqZ2#?~q}(Co#q3oFEjv(tLata890Oyn7jI ze)COcUh;ZTa{}jiFxaC+TbIGkm6b)8Uhq0Fk6wjH@mKCucn@mpl?~L|-l&bzIEk~u zHCDJS@5aB_x%OMEsi3cQft5%}Jrsp?O0Ota;^lYfANw zy!f@MIh&H;oNJXjC=!qC?@@2M(vWezzgc(Pv z18csKBUG$)9UqtaewIlX);n`X2`S8FO~e6Dkcy5{qvVXb4Xb}=>E5#ZqSJb2nv6zg zO0(S>iHB6326Ys!QqPavc3ZzphxlxbI5?xIH7YZ#!ggib4d^8M?Pah7b2nohaWi8@ z=Ucu5u_cg6v+Qi%7fu-mdq#M2;VhKCtpNRwA|6#LW67i*JE6!YZ%o3-=!3D{OA}9& z^v}$sT-%FEKdk-=Wx zHY+pgm9Qn^LHik}xGUBSeT%}1HLSG4Y9sVMc_r*v>6`8yd%HB<8_fLA@&~K6S+Q*RwpX9z<#{WIL+xlZ;rMvVNEi2Tr zaK#HtZ{^#Y;nMHf-a&hg?OmAmoNw;g%VNjo9(`yb(^FZxCF@#IBb_s2n$dyKd&rHg z%;>oA#!4rF_atW`tvH9d3vZ2eMxN&lYHRpWwD3WSokF!a_Kg-j1-Fq@_Uwf zwCDupx-B|6Vw)OU<1hy;G!8Sg&~+*&_Rz0qpZ}b_CU!s$&DXGBI@?7{ci^=XOlL2L zEkVeg9`dkWo5W10WS6#H=yjuV>$)pR^tr0K4fV2s@T-&2%f(!c8ESy zzN#3~2(`$?lj>107xac6&jsZwn&UTOtOwc{wX-D*(IQQ2qG(3#z6-U+D@o8ngm|QMPG=DGm{cq^q-Wz=1?|Ceve^~arp6=GS{$c2_tDB8C zPnJa%TYQH=H4}4h)Tl*qXXLshj;k&9z)gOP=25%Jty4B|N7{r`|DqXNmAmA%%|(Cq z>+M~$&M+DsJ3+RQYqpj<+&5S99h4JM!<;3j5p8Ytx?2r(LnHK}6SuarvasNbx8$~H zDUC>>9?nWfUENt(INXar+}=qG+&w!eEpv4?sH#^tJD8*P()?Wd+}BE(ph-m;XyPhk z2k#zrEYEt6uJzubymMbX(d){ z1tnNgMry@`4-K~;rJs*WDLmQRI?fa?v$_`HgSoB(GXry8H@MydGcAeB2MS?4!F-c} zv%cclYx9E{Vx%bFk`QIR`#SH)u^~M8VX31hnD09}$86JOU{-}zChWplJ%}q&7vyL3 zCF`}!cYPaVaj%I#Zkmh+rkMS$p(2_(ad1^XsDy32fKA07wb(vBj_v*=C*1aTTEz4s z{bHINl7*1RiH9DLcUsDi4>V&>Aon6>KL2d?Lj6j7uTp_f3nB&}V3#gr<|B=frE>dQ zsE;=Pi~b}c=|C+x(Z^A4c2K0j=ly(}ch}ay+;NBd!hp4`_nk6=HOgQDwIo(f8Kno? z3-(XgtOaJy$dlo7H<7a9pwpC*SM6sQ7-c-d$)<(OcwC6!&mCH{LV7=?_JuEpx-I&UqW zX{a=QO61BSl@Mv8!l{HjZR8dF2jreg7?2C665fH?rpoZUH%V5-GBZqwt**}MvKb?C z-$^3D3-}MneVGU3%FCQ%wq99a4l_oF7Qu9VGnOs1b;$q=qURM~*t_xFHZx3cpt(3a{? z{SNvhIkIlOrKb95z0{?5cw)%BdVgRZ`@3VVzP~fjHc>V}$LMAoQ#I5q#+JOus?F<-?PZFmG9O!dZ}ki4d!&3KqEkpd6qZmj~-|F4D?;Vdk<>s zkPXy0oo%9JN6jYx$bptmP~YUi_kgwzS&Z?7a`L(|}&nOgDsz|b)4uNwDrd3|Bt%&+12DYj<@%773T_O-u~ZxAPDf% z+ktmp7?MZ>HUyFoxY6DJo~W+uq3y3D-(BYiYcM(j2_sHbXJtl4MzXfx#SCs_CZn9X zs@Ih~SjjW(4g3Riq@ZK78Kluum_T>~v(3gX%=B%WvWSCj9=UaSqBkJhY%CzNG`=|_ z#4#7958i-mb1`J26jOZ8ko+%LL3(|>Q#5!W`NyhTXHeNNi|9uY6Z?LcA*m>X1eKlt z*2c%nlAxS)+Qstz{nuHyF4owFww~YUdGlPIT`ZYZWt53`@E7O>2e#|IVgdKeoNDb# zIB`FBiFCbjV-rubBF)iq z_2GD~*togiy_Qb$67SwWA`8^C!*#hCDP|#H@yk7C)*qYgQ<9ZaQmOsUIiounRZGh5 zq;fl6(R(0(yLP;{@4sN{rAAH$$=_(u_EOi|)X?KMD+`v-d9|=@P9`%5elZJH_S$(k z@064yKZ+AB=Z4X~=~VR$9pJ(k3(}Y*#nlMJ$ z#qW3wZmC6Ra ziT@;XbNZK&m`&7M8&yc@*u8#^cp5Q`;mS;&VRP0H2{2_0uSA`R-OJ8RFI;HmLSS#= zACTjywMz`ntoBi-J^_h`e?R6Zu5r|C$6UL(&OuwkEDk#2Sb#1a$8yZJdpT(!iJ_h= zHQ~kDAXAH{Cc`Lu8#|QdZTu&?vts_^qm}??fFUj(zw`ZYqqp)GZrMvlVLj2%!@8<` zUY6UGc{BeDw<=8fwiCWSoIU5w&{je3#YQNQk8X7Jw!yyFHa8n&!-lU`Lq&el0(;l4>-{c+r z2YOr!?2?w);%M_hD~~6e8?IF}1*zfFA40&hP(FEEfBa%`NB}rj8sZ4cE^&tXnZM_v z)it>Fb(;@cFOulp01)c zvM7%{Jqnv`Pn!7#@>;%PVj8!c)SSMa(v-qqwFFm)ghw|+KY647QOu|wFNVnv*|QPL zpkJe~ksWUiKL}tSWJ0aN* z!Lu!5=~eW}B6K68bUB&#_m|R$8_gHYm(12Q*(U5rRJ28Hn4X(zF_p)jENGd$wLg-< z`B1IJd-Xy}ti9wX4PN)X%64*3_zJqP%}2Y?Y9;|GCN!0<5;yZEXn9IPJn;k)^m&!n4D zIx8ztO_@_3q@GhB3sv2QdU6<L8XtFS+4@SdTbBrsf}X8tqY02<_p#9MM<2@}-I+>2gs8n;EO>kWIO&U7=qG(L3pG;}h~XqF_1^yag7?M@N|FE#t`S_- zV6?IKrD)%S)GXK4d3%4&@HFPEKi8L1xOOBXMIlYG&zAT1pWqTU)tP#(nQxm|9mqO^ z+Ao(TTn7Hw^vv7)OBpP$5aE`epQ1S0%KEs4-IgKV<6nifWRo%dpel6XUr~&!I>q&d z?Ga(iz1iJ1D;1#Gh$>zi}%xhWH#9Vj< z8^FEO{|_FC^qX;1Em>PR^x_vh-%A4@=+67PmMJhR)9<- z-Y6)EXWHmdK8VlqjndqwhSq~dlIGbgV;Cu~k?n=wUBTwO% z$j)CT_X_7JcyK0pig)}U*k>?{BJI=lOr)Oj|C%;#m*#9{oN0DbihQ&0oHc7+nLd_` z^qkUEp0?Lxk^0D9cGNaIi|eiKhaRqgTzUXGz24sR|H2&CyC1WvuYxXxoh-~j+q7(; z&DDmr;-DED5o~N#*RtKTyKj);tkOAL5&IRFfjKqB3YQ}^|1rBnpUBM zMl|a+v#7i+=<{+1X%v^cG;2g^lJz{O6)o!Xe$RuPG8j1S_j_g!?6NG?j`|AT*>>NF zT>1^zGJIMz;lhGQO_@*J_`NRbdHu0HWjx75`PJ5oUFD} zR>mVPArAVhh0%I-x)?LBhB|1%afSI`E%WcK|F6TQ#(JRbi~w;cWaxP9}xBeL%D~mjeDdGT<`l2Stko>^Bv3tZ*AupsU=sKmf9Ry4%((-anKR819at}4Lz1!ZkvgXxM3V} z>Wa%9(=3G*7@`EHY%>pI@%Dm)TV2iAZ$La_g>!8mUBQMTo2}f2vdMkd0b4%02*&Ys zMq8KUUsQLj=!=6Ck+w9iUkp_<}fvXyli0PVuk{pz1+qYpgYL z=1}9&b}*$WkG6Kb(PYeW0IGex-*83oZvPso4sW`K8+B~kTvGb0W^9Q>#WF_T>>nd5 z8doYRd+~v+Y$O=eE%I`Ron;FVKwf$oFUD#b!S} z-!twoTe)`anUb-_8aBa&ZjD`;C??cW(M9q-(%M*C-Bp+?%NDis4mr$&{0C|0Xc;D| z1^M1D%1d?7_3WX~gMNK3%je>z({Orz3-q}klFXa>lHWn)Px|#a(w28!dHU@hwA`?- z%YAJssZ1gu?N7Rv;sx9%-RbY^!Rl0S?kvr13JQOC?~vVG3*V*HW8aS%n4oyA?Rys4>P+A z=!)PLJz(Eft(0zZ!ATS_U-aaLb?}OP>!f?3ah-M1DE97QTY@aU=;c{(5t}{pGf~m@rd4el)MLwg;2pT3m(^8_5Nw?ng~q$Bc2z-rGM= zlZ&wwlkg2oxRG@e#kakt@R>+9Z=bUp*qK?8W%3dk`N$nagQc3lW0}EM{hshsM{-k- z^FHHPT1tC={{YR7Jl3$FFG6!Nz!Dv_FAMI$JRhwbv(3gPL?;`JVSLjPk|Fl4{Y{U_ zVwTza`UmFtq9vAFa5Jj>MMEA8pR+Bmysv*;?lT9yzURt0XnM6tqFoGwGEh4+(vk_1 zNY=rn(-L0j)Rb-Z&i>k=pIJ>eLsrclVd~;3RQ-FSTT8Bvo1QYfib3^4XGP-rqk0TJQ)F(kk-^c|`MB8Jps? zko-ze_{7B|6i-la>fp=vSKP8>S~7JoYzdck++@u40H*&EFv(ldb5WbabeP zxiLoX9JI~I7%+wXCfQ~k3uAXAC^Y2TM&Eg@chILRf)bOo)S{p;_gjgVL}VSdtl4(x z2l~{nq1;&Qg*uha$-Bg%X5S~tH(^T-Pwck&y74=}A{d+Za+;GLs5xtEEs2)GqNYWy z)kAUC{kx_V*;pr6Eqn)T6w8SF-8?Z<84h*~JtG@AVcbEs6xj=kh zF`N&2B&;chGtg4)tk22uG2KAV=V?*X>v(A+IY)iq^E6=gzF`+q`LU)|J>q~pTK>d) z`46}uAU_}vquP@G0(IF$50eD}@=&dI*eyp{6Jr}>2lk|SpK-=*C$0BNlO1w_u6)v& zu?@0)2U&u-mmv1E422DPI(`NEr0W@-_fI~SSY^wBGA7m{12gTNL zlnZ2MhO%do%=`G0aDLrkf_qXXMeu<(fuyW#-645ZUUzsKe{uX9dhdv4&D@1&?o3ds zJy~ZkW7kkdCU_Tr=C=UN;i8w6|20(zYxCM!&qZrYFcX95@3?7d7E!yeItaoYrMLEy zK)Fz8@l_YDx47d;0`6`6XW95udN;02%6?MQgPf*t00(kPUl2AM==g%{Cp= z1noj$8ISaN%GA_2v{ienT2`Vby8Usc2r~($$w6mXXUemmT;v5!Hq+}k5SX1+by>B0 z61Kf}e+ZyO39b}Z7iOrlu^u(pKj^&3LKByrG+p@fv1(rKZ5FR?jm1<$_Evf8{uxCm zaN~>Cc5=DKiYD9ka{So&O{biRx9*>$wNQ#p+?HiOQypIyO6kI!2}2y&=WIKctjVOi zp{Y_?0RWj_freKKXJHgp>CLuTSYST0FnJf9mn6qQHSs07UnUubgunAvWvB0H? zu{Vy{CSl_WNEX)ilw>3)dy$OsRv*;WC6kiI8KnJ6Z`mI~tbNe~Hs3vn75<_LV(Axc z6R}BqJe6Q~=2xos*52TP*K5-DoFoVB?fNIIG7n0re!nO5HZRN&biZFmIdAXtftK`H zf2?B8JN65w8Ud=U)SK20b_8?G&Z9gjI-?}Wt&QbIv%G7+*8Fke*J+~ z@T#yfM>6JvZHX1I`I1MOQz7!71RKToP;cu>OcS$D$)wA zpa+06m`ze9_CohfWnI-4L1Sp?=4}cGd(=$>d>u4`o2|I- zhfq(XCX>|)!U?$xPNBuG?#Bg(8O0)@_5}xbx_g_t!s9+KIS=(~=?zkZ3K$W7=XL{@s`~n%8OlOmQ*MU%3}N~8_QV)O*g=}sTUoJqnn(M!6_uK#qIU1n zAKQpWs*MGXNJ=v8o#u#Lj`Jfp(quYt{0a) z$h=fQ*7{}5#Ba1}9a?%s=c>km(Wpv7V*ab;R8vz%MJTYo@4%gO*;x~xnQd$axv zaW|&AFqiIN<)Cd&7NFycMi+|mqCCOx$v{~pnmjakvGZzQRA6PsBxA)EI;{rrxQHv5 zuAbdU?yc>Bl^?4;wK5kA@&@S0OUAa}5W1U}x~glvG|y_`m=4Z@Fv_780SLJtPIJts zpeiCK)sW>_jvPDd4WT9pwF@{F@76y_D@I#7o@l9{)v+u5L<2dCW0o5W$Y&f2z?Glq zHxOHDOzUVDsDtZruT)sHXNzBMKdrJbmta2Vka))njYLp08>Mq@eatGctoQGdnDzG$ zI$Czu{jQvj+hhi!)B~O8wx5HSZ`pq>6qrPW?FXfPEP|r^J}X7PJ?;*bh?@) zvFh)bgFToqQluwaIU&d8q>80r{JgPirdr#s%MJz_ zcWx?PODRqC7G{y4r^flRTB~bljdgkQTzPl?>yXhl&m+)TOS>XIP%md4%k{7NDbG|J z&NFf$gLbEkm7c75dPPY?W75n@<%%+s`!1Z_JBT}3B85f6T={jTx=IpmZ_7WDdW{V3 zT%R*{`_<;d%lgx66Tvj_$88W|4&j}%R#LJch?cfy(dsCX3PVu?-1MluFTcz&^+jJt z-S2ndax%Z&sI;$7`ui|_X{6EjKmYm5&%gZi>tFx-3!T_M&`95>W&6S({+;jNz7N9} zejVq3UB9$i{D1zNHb7zs&SnQh<2;0It22@vW>r5{t`gIrPrd!yI{Z8@`||DY|MmBO z{`TuHKYjb{FaP-cKmPR`1L4!zDPnfFz+~}$+4G8N8T#o}g)1|yGaS?l;(c8ic6x47 zHO0-2p263m;UXazy18}Uw--J*WbT<`~LIae*gG)47Ho*|29ypYDqoku13)| zV1o2m5^xJ!SpcV<%<(PWFI(1(n{xP(=`K2f+82qUg^s_(hmRQ`C7`qA-J)CELC1$J z?_`oB8RbAUZ!adYhsV173Fcwfr)|Xz-Y@$KXQ(-_DAgwG39fO4Yu(4k9>iPJP00de zvRu^ej$1|z?hc!qEt7?fqJ}L8I5TPs0;i4ea;q1_&#yX@ba=CDN|*-u3g8`b^#Gnp zI*7&Ypz(trP<>qCo9AoA559KzPVIyjZ<#%J)M(=+#v_2As8_sm9^814e%5c`PLi`T zNzrFKVVV+2$~IsmQ!kqJ$9h&k^D*33u<=~4@p9?(F(0V?bS=y0VtGgJm>qf9b;WyL zR{e@^yw~pC%1yJCnXP)R$wjs1SwEG2t?8fm_KA1QzOHvvTvFYlKP_HyE$ioEKWcB8 z9f{eMgVPP)**R7_2WNzu;=`7i-ZC3a9;@lE=Xzo+eT1kf{9Ma$Hl?M!XZAF4wy-!6 zz$d83U4!U_YmXV4`EzmR=nGnDlbK=ig+&e^dtp7HFP|klty@^@s7bD&J$QJUSCvhYqQO!O009F!^tGr{JtlPhi$ zvw(el*U0Es^trXaYiL4p6f4~F{@L16&#=>vFB>}PfO0MUWoN~;x6h6Ut_wcOVwh&q zhT&oH3!c%0iF-DYTbv1jeBEJ$DYCfR@Wc=Lu-=uJXQ`F+%)E2)+0C}8SU`ShK_uS) zK;G$!tWtao<=D_Bw9OCMreVs$Hg)kt6cMkbN<-tv&sf7Rk99cUtM=umVgBD;yapL3 zAa6Fn%0qXtk;u0#)*HLUcCEKy%K`uITKt}tl9m!cPTyslfpM-gjs1_6@h+z9QQ}c) zv59F_Wp$Lp9Os;Qt}YM(CWPUuN*6;Dd3h5JfDLNKG|LAQ|9Gj ziy#a_`)Q@# zZ0&D5n@WcgaiDQIn;F;F>$OpPVID=zMcoOL^m@*6HhI0UN%&;m!wFBkefHd8>-CaX zW58CP%49*^JHB(rtx(k4XX6+}QbMPa(n~vk7s^eo2*rK2YDCMuzr1sHKvq^d!5#(V z(phQd+qe*)%~#C?Hx;(NVjeEOX6-Y06FgJ4j$Qr#*4Ak6E2N)J5FzON7D4jc-^v+&XS0q-j#> zU9?qHZ%tf<<^-y=jcQ|)R=Mz!^#Uz`$qJ1a({QKVVJkgbxflVvrDp{{?sm(;&6s-P zBjfF~C%$fxsga&dA;T!l8qS~Fr)816pLT-`DJ*h$M}gE6U^}BziFGAO%k^=lD565G#KTdut?J2txkVDsy@~f))XGD;99+__9U&~ zi5lT#eAUH4Z`dtwZVB*8a5hghg*AH+&c^jlf`&^mhT3E(&o(q&{7(CsNDn+@o+wFS zx!!b9m_(!!;hI-IX=Et&m0rD}w)91e&o{no<#p3!;zD3+3E5hfOYmkgt2fkMBtX#{ z#Z8u2k}{(?2Njk}X1)_>XxzoV5KCf(GD)<;!7r)sEMAV-ipm1<)w}J8i}!B5ba3@P z+85@o9mH$AS)f(c4(7*f^DrFVD4&&n_90UiUF<44Di4`b?4S#O@7&^EajJz)=4@1}hn zbeo}$yIp#Qn#-Mj(LNdLUv#)uNmeFhaOoLpt~OJqwubgj+FCT%hAn&2Js^SKQTacd zbl<7`8y%SK@z%Oi^GsimdA7(=+SW+DUi7!-*JB->JH(CZ=8%&V6aoJmkk^$;nsPEQ ztAEL~l*w&_@$Gg(`k*||VJJF{~K`W$RX58C@^GklGtAGKsZXLN*z#*|8* zbGFq4Cs}*BJLWj%z+Cy5bI6tv8*vVA!{I$@kCc>~y0x71gOjCqSGz^+Y#a^OJa@_$ zR8GYpmNmCAzSfSK#s3DFxUQ?bI>*oi&dE$t>_m|RVQQxxG|?_M?{a3^XZ;*xjALzTvl~3M^JeZaiHDMS==p zscLNXHrgteclLkC&V`Q0g&2}-+#X9j?>aZ3B%0t1Cd&?aAMHTBE^Hg)gAUlLr{}qz zYv_eLb1YiDk+wEk49q*g4&zG2CR|b7oW4HtdKYnYhiO9248z1K*$ zQlOtR4ZREA0Xk`0(MdOf3?&Hz)a2YWVpk0L1~L*oTySZ~VjmMmW|#*E6Q_$@|L6|> zf9;BTp%J}0?d>736;40XjC-=VSm5oPL9a1NXP-G@n~24-$T;HIkXiYVvmI+I4&CNc zz965cj046hAN$g$oI|#0SU`>%BQV#lF>=tB4!c12R|?S6!S`SLl}Z>u@6mtNlAR^F zYRSqhNm@#JkN$B-1KCCtki(%L8YPQE@~ZLp;jv26=G$Y|xA6x3fmk|j)>&ML(L^Gx zc`O&Xh1_!O9J9^8v>0=5nomv_QEbe`7q-%qVxOYQjGC?9n_v3;1%2yf42oWTXq9Ez zjTNr<}hrf+jyOA4i;hi`k13AqET`=eXYtx*U>9Nu9}&QfNCAgt@PJoOL9#$kRLW_4+x>tnK17>h z0BhinyL6G00>y>8L0$s_{&orW$B<1!F{5Wah=SybN zKWV7$ws5%eCk^|TQoY`TKTf(CBJxR3{iO9x??=gR`i+CO`ItD)ohTNG$wVH;sgjY->?^gjG?*79y{xi`ZZM{}?)nS8fL>y5*<`B-4S4!hs)(pl&mplwp7P^M9` z3pDN>xRm_=;D+N8=sNG-guj=TS18*VLy>yDZYax3OBS@bcls=*uMhexrstZ)bR?nK zD{a%UpbvUc$?_|h!P*!5jYGD{*eG5XYrOcHn^X#?rOI_Q&s8Ga_O|*8t8Pz+3M}~*}sef;Cx~xBO7?P2JwT1F{i8SR`0dX-f4mu zObLHW{5kMx@2$*`UH4;#2Df~7J7`L)^qaP+*uc_z-EMSJUl-(_qX^JOjPk#)^&;6B z%F@QH_u9We^Fkf)Ljt{G>pU?yN|37-zFRv7d2dGXd$aw4?lZ%&GbYV5e%f89WqS#q!5WM3mVEfb4CrcGDcF`z)U7Y$^2 za^fBKM>@1p0?#New32s(4;<9qk7}8HMrw^ssskdI?AO^|$BjR0O>Td+r~1cXrxpj^Y+rkovE8Au!PpgYgq^#+ zXcTFNRjIeQ@JvzY)P7o92gGpZtSKjPHQ=#6WVz4UaI9SihVA5bTQCD}2{`X`mViU_ zy#tyIWn*A8K&v%RNmBEIzY>~k^Dg^w(5>iLeqgTsR=*O;X!9=nklg#CR%L&y2i|Vg z)DsE$QBQmkUo!D)h6&0o;Ihs(hMZo4GpSF4w5S|p7 zm=U`W9-viP>%;@}jLsW#rm7*lC1<9@+<1D;!*!wV3F(EpbVABwEjfaP?Fln(^g@!N zq+}pLlTD3QQ&y%%8<_y1;52k>Lb0daVT(|}=s8d;Eiagwok1MfSju6uV^7;=XS&on zZ9QHKg73hs*K1zu?NdHAWq!W7w(4?Hi!c@-n;e5YRGd;Ww zEN0^O(ineIaa=J1v{La`(XHv?M@OwW^Ji_DGpP}Vl@P|Gz0k*PshOmr{LfRlJFYhy zZ9+nZOF?5EFF}40wH3}b{O6z)IC9Yn62RBXN;_!Zvu0U_$1MFE-?CagG%{!Hmw+2D zU3GEN8EBiE#X(2a6$jlI(t1MC7RcVY-8MCwM^WI?+R&=zuWPsgqI9d5kG4k((3#{N zlO=b*1(RW{cE1OBbIDOO%%b}}?&adxHMx@uWH)ccuW239(FS~!D1}bnBJQv>@T`Z+ z0$qFBkVtJ{M^>nG?U`U1h<}=Mz#g<*&2`>!N)IvOE+6$~{4=6Dec3wNi7mqtrO@_m z>F8Xq^>Lw|XnssiBfEJ+epOp?LT$K}sfbL`_*QFX*FEjL{(u=Xd5VDUO`d_<8a*$1 z*vK=^^WO?~?XLqzbVaRXFS|)JAMWaffJOT-w5k z&s*Kj%4E_Tm)xdl@nuKpHObRQ!PjIR z!{%N3CHSAELFxqYbdB>=RxHrg|Mg^-j^gU_Wm*mNH5NF-c?*JSCF7eqoh)tDYTC+CJm*Ti zS%2`u;*uIwDkA~a-j!eMY`={37v_Siu0F5=xlAK4uwv&70j{!XL@p8E5XL&#r#Hxj z8n$7elU*IPpV!;GZND<^rp-7hNK6tfkmcn{Z13N{V1O*( znoY$rmn}*ZBNQK%og+qY?@$Z7r53!0|BRLrElNwsR+-=gb;!-%$~v|IoK(f?k-U+= zV83|OjGU>oT8rz`kxdN!rZZOYvL2;1+CED&Eb1Atk|3;JV{V zMPEF5iteTFc%F-UQ~&;O>o0kQ#KT>A7MVMqQ#cCcE;NjaYn{J{SLb*)&b4S2_S;7ZS3&!ge8mrn@^@YQa1E z$5Eprrm~g{OBFXFypdXDF4yhrvQm`OUN}ud>y7(8A1DoR8jky|*gj9?+V~yx`ube$ zNqcAixZY}kY*d+Xy%o^+sa}V(qfT=(@9ZDQ zQC}Z+KkC|sSGkZDX2@M|g7s^oyVVOWImeKZHQh#4rj!Ygq~L=3eFO8Af_Kc7Q}7hB zcl;mvF<)sJN)^p_l}^iY%(kByn6D4IA9L*wItOj(GI=N*k|1G>zGD-E%5q@;kgT)U zrCjq`?^WK(^oIWzV(9Ogr#cW5(5e~?Xs9XeStB_xF$~m9Bg2&-zQI=o9TGlg5-nhY zRdD{Hb$bLGZN_BB*cfmyuv-cOfpChCXnDXR$iYt9qk1KE^th4FhZGk$PQi-tm8F>m5XQ%S)H^JI)zb{Jo&5LL4-Ubw07MarFj7r@7zF zJD5|BbWH22XI6#k^vkAW>4=Ya{cqE2(x5O}KSF#XNf}=4XLfvSo zdOQapwn9hc;A1Xrxn5Ib-xG9Ip^x-26g4AF-<#%Oe5ZW z*Sycm-b_bN9${NqorhYfHjj{0S>EdVZn<}=;WlQ+EFHgEec)GsyV8Si+3 zeq^H@BBa``+0fn@1&pba3Eda!?wSq#i}s(~v~rtc&fCV{Tw0339p^uG7}qZW7zYV%Br@XR zV&HrnQIKjT8T8|;TnZjhJaf+QO8^4&^K}I2-AT3p;08Y-E(*?HlY88Dey+Q4 zY|YvnehGk{=NF}SB;X^)_$xL8r4w)>_(TYwcE_RkMF4^Oy5WJl`VG&A%`wn*Z+5@q z*MavtUi!fEOYXC?j`=!lW_*S=v?!TyIr8W<4MA;`lplT-z!;!=(>8)xX_hd>oNNNv z=wP*u<@s1&`wuEV)(EUJ;iTUKFo1fH?wb>*%`?CUuZ-!H7M1~;gw*C`eiOh2I)r7g zhIEc-p8eKKWRX9VLvpD8A0R-eL0Y_yemz3fOcoMsD zZZePb0C_TEr*SDUc(67~t>Q|I)OFRb0f>{np6NL0;yGD%)HWZxK*uxPzv$A>bpA=( zd~Ak5L16PSXn;vm#;{~8cjjj{W-gBzXRrVH*Y)+6zpmf@{$GFp=WoCM^3%89{_>CC z|KnfJc6H{4?>7Jl^1&#;fz)OvL!TLLc+s>*nlbVBQ=Pm7vcK88UjPu0Wq34Gh0Y8c zGk*FZ-l!g8sf;pNmHZw|)DFP-63YDk!b|vKuD;q4pK^rcZQi}@|AlzgN=)7-+!_ zb9?#bj6}|1<3PC@10P^i+4z`aI;!(rEWq|H7pNkVm%9bj@ ztgAg>i15-Lrhc{cH4jJCuQ|Wlwo!TAZKQ##9KazxcT@-?9lY08?x1@J%eJcR-rYYy z58L+jg}vy^2sL>K{1DmpbCS8c+ucE9cw+rdcKXfcdQa>E)#f~b#s-__bgUVjG&gkp zj4rgE#$7M@~U2&L$NlWFP{jRg4PTwsCQp^R9gBD*hvSRSGz2~5d zl0$Evv{jHz{iO9#>)D#LhKTg!*;gw&=uBO+Cl3Gpi`EvNox&;(`rPOJSR#6hy1iR( zeA6~7n`l4J`lkEso;0h%^HutGOWU69&to;3`>J)rwejJ?O*aSLyE^Pd)xUdx{|Vdk ziT3janmw>Ps{ZPzQ@i)w`}-?5TgkdF-0YhgnbWF{dqGvSxS??^VSnT4y13t-qdd(0 z-bJ)Vcjo~O-riuA9e~y|smw%LLUO|HC9M8Qa=#wa{uE7ihxsf%G6QhAc_)}(&Udx93Q++h}&3cd8cLy}iF`N}gvfHWwasjh<>8&;ico zmV#mY1imNhp3F)_N*KEzbD=4-MjkJk|tO$>PEL9 zKFa=EkXsOcj32$jd%hgxO$sd7y)b4Q5 z5UB~{Y#;hMqcgjJ6V_KmWwP~o%~iv=fHT!FJEN}Md;7O9ncPIZS{Za{Ug>~W8DY@& z$6B{{x4eJYY3suq`;VjcL7tKOfEd0o%baX3_i0A}&z$J6iL_Uo_h}N5fXqgV6~}(X zMHBDKV{)79sHwXALg(Gv`|~6tu@Q{~Y!bDRQPY|3zM6Z-bA9VO*w~g1l!k*J4#WH6BIiBnbGf(zwDL2z_KG}1&(Ruf-{(!~c zgu){#Yz|tVxn8oZU@(i5&bN2(C@stdSC@Wgf127_8xv_W?M+dL($;PJV|2=0FSk8i zgtD~k^0xjH@AQV{sH%>(t6ef^jLLAaVhL5bJKj!PnA44cyEpd#K@gLX64tb9s`14v zZ>g83{D4i0HoM`5-P`(Wuz1&FEgw}4&ugE7(l!2G>*5}_0Sj>3yZ7Gy5{hcQafEX1 zaah@2(W>>?T+uBGMTa7d7(p+i#X`I;YV^WA|yBU&<5 zy<@`l24u#^(JUZ{4ppIK@WW$u*B#{Z@ycIpo0+i`k+FG+6QCBPq}(X3)dyfmZF`Or zPcY{egc!`(qXy>DRKhGU*A4?-EZq(XN*`QPe+Rq2*Xgp3-TV6MaD}=NXng)xAS-^@ z*(~|)js4}%#RyBI5fsX_n}uwYpUZ}_JQ0T;F`-O5I)+wXOIwKHq;_=B`cgi+9*=Qn z?tZJZ&WuHOZ|grwFN`UrScQqNnhc!#PQp@`Re&qZ9xd6{xPedu~^m;T#zQ86o zN@0I2IkA+&JD_b-GY&c!(m3d5d|q%z`?(@`IA=8=a|S-0`LvkOqIsoB!d zcTU>oWFy)e77g-bVex_hYvie#^)t<$@wq^jUGG$5=MDX}wfhB)Fp_yr+91c@I|5@> zUU>ghJLE}8%#onPm-@qI%S72^0*YFro zczeTxJj#+3Z|^^in6VMh;^hkFsJ&ccxhB{7biHxZR&F*iQoi$h3A${eeS2u+1NvxL zAIrgSk6J}$V}#Qtm$&zCYnB=peo_i5zYaO`)g8`ECqBHpe;|&TJ@(OGIXH>RQ_uBX zwbj1q9J0;JF36Ntnk<*WOVL3gWoPO+ zXc!~g*&(g8n7P=zW-(L0NQ{GWrS42UkXbt9mXqw>(O-+I8{|Gyk6Rf(bLwSB>gUgN z=9n=wcgoZm3u*avvnK|v`7IR=V+vvU_iQ<`CJB}XeufIx_^D15v?_C_sqb{d-@09p;a9SM?tKu z9Yd=+%0WIq>m0L9$}Y?i!XZUB6!W!jcMjTW{sVMWmnuFGE{QRFvbwyx+o#4T$87Vm zSo*16j^1pRer{^pmR3WLnJn1*`Ga{KqyUHOmUiyKOlJqOuv%7jo^#HG^7xpftnWDu zRd8SKs_&Uqfz=@QM;Zg{bPuGALJe#&MD3N*rlo8BWz)b<^HZ=BQqXTX-AO5Pz>?3+ z)ReLC6X$OfvyW9AcZl2F&V@GK#eX5wJcVOaRSbUwX4RVY7^y6D2F5{r7O!h8^QD@{yfZ>X;MB%g+&0LHDX-4!g_-t^EUn$Y$~itx(kLd zTW7j-g#~5Af86$hJ@N5p1areNT6^L&l{c_u>cBEWlZfkc-}PNO)=G_RH^-j%_;<)x zsWB4Sn&pvF(0b4Go0vUE7MO!0(=sszSZYVI{Ge?%#vpmS+gGX454v=zkz=;0m_(79 zx{mLeov!PFwL<0ZIc?tIvS*U9fE?d5U9EKy4_ui#$T8c7<%PL_&#}6<_V=8DKD>{A zfR67u4!ZRBoMX1l%1aj7K=U1Q(x*>d-N#C{5J|i8@OJ)}Ot3R!641@cPAc=hNQ(TL ziUWbGiDLmeu6Ka0e7)bmY?-kU%JZNP-6qlWOu>-Jeo}hiiVh&k#dN)sCCwVDGbb(7 z;{U=<6&qQGs7bmj`>C=TZe_gG6w`qg#Y0(& z*gN}=AZ$BmWH1R&CXb)Cg9d^OHsLAi_wJK!m2~D%c6eX^fE?9^Aa8gJm##LTL6ZrF z!@K&A*zV0uGk3b%U*=79-R(zlzxY772Ma_dglW&beAy(L)1;xO?&)#STynY7)Wm!G zUzodUPLhL}Hr~UsxdikweJ=i>eLmK2_w`M8JGScIG@j=+AH%xio$K9A{jcj?I1A;Y z9%)!Q2i3#7`o{&2EHo~7gB}dXrRl*Ovro+8W_KPqFxTdRbI^at%f1iOmsW;+|MQ=}{QS#Lzy9^Vzn~NR zLzC}28@Rvlhg|Oax9`L7r4^|9uj`kV;r)+))B9^gd$Fk?0x)&aJ^SSfpT8nq=kF=aCdAF+{cRr4Ocu#HN+?oEn zzHWl|j7F{c*PU%1JQ7E3nAU->`htxm(#pS58+J0hDz?SlaEWS(+cIlJtU04%dXcy? z{6xZ1_ruQG3EoT_CAmPaq~KaWH`6+in0!+k{5h+$+Z?55qVx7SyS2ziVx?S~qP^f&_+mtPgJV5#4<`Xd-7)hs&VF0b+pT6d2X&FS1=GM7zGrcJVkCOX?4sF{&!4vOd6 z?yEYypLTrO*W1o<~m81Kx zfvhe{QOw)&Ag$u4qB*n!B-?6(+b0&QnU62Pm?omsVz@Ve51DD2?48Z zbhTsnxQbe^y>Oae?uOGVTG{S$ebjJfAH_ptx|bd8%fmrHp=QDZuq&ldn8z zljQ3UJ=rwFXXgWUwAP6FCV%?VxBp8TvY%{Okr{7HPG~X;?KShQq-*Smcu-<~hg)UW z%K!NbZgaEP$R_>>*A)^T=%n*AACH8J-$| z9o|YCgB*m394p~#qJt<}VG>IWYf7G5Ef+oS^)Ql&PHuDR;jOd>>_a9L_d@;BlsZM1 z=+xS%)3uU^H`5-t$1N%AF-+^;+Y<&O&Rs8h){LeTyn!0W9v6= zj0kOZRL1*h>sG6%*0()0*9{02z0{Q>8XYduQGd&QUM4|LYier7QOf~X1fmF6{IuV| zZM&KiU-L^m8TqJ6tto12^colnRf}oo*F9&M*=jSh5&GywD%0Lj0#dSO;flg4qRG$tVk6CI~d_-b&x@%=+x`zS;pfveACZEg7x%Sf9#_{FuF| zHm0P_=51xsw=!@Icg|E@E3QHFQBQPqe$H>8_GB3^wAPDCXVg&B)tYE|tQ+IY1r-W` z`VBI{n`;A75{w_fTdKA+;PigV1@9lgQzqP7Y)g2&Vh*PWzL2ZPj{&l@Z~$lUZ$_dp zTS9>>)5LHEFbxp2XGY7kXxD;i_WhsmYd3d(%hr8C~9r7Ws?mV)$)^$I#=LO zK{?cTjA-S_MiM^L1RDuqE7KwyJ=}uh9Vcp>=|0EB5-4=s4QhSG=x!QlY^6r@l&day zUC@{@Bt3fXtvxcs-_te%Pho;xAsU^Kqgs|FUOW87n!+UCL8jZET*C#!r9MtG;`>5A)OwsvK=gnGg zs(qbw%xc9+7nVWi=WJ6jjibp{`6{6M)KaUUm&aBX-I3kBf_~$mZ3>3_6$z$hcL-b> zQHzwMg8h=FDkeJc^wV0d(yBqRVktAm9%GZ61nx^1OE4Rxfv>C^ z1oO`8wcA!LqGpG?zg*W)b&qG2r>gUKX7jQL;n$Z1xff#fKw1_jtpSf#Ru_)p8BK&I(929{W;K0LOz!A@7uFZ~QVY~cUMAuX0zth>#%t%8H&uh9URu`b z%S0XuWSf*N$mbSHv6xI-*o)g_7?mf}2x9!lgfUQCUMx_r+G0fCXJHicpiJ2i7%`w$ zjRQR!8#vvKpNq*vL2Wa#NsU<&FbJ`U*I5Hwnm?`H&B+v^z_tk)nJijKY32BvO-EW7 z+qzf~D7cYVJM5GCwH9C?w)xnFn3I;vjTRzrvJa>`ftaLYAhzjPAYNDWq2ysY;~fVm z(MRj6TzmGcsB_FV7rQWb;v`PF@cej+*n4VINex45(Dg5wAuaiVi3%^6B>8+D>40=3P>LtR4CKp+4?60d zx!T=$IDy)xV}rcY)p$p4pa%TF_dRJCO`86-XkKD>!-CjnVjxy#zU?gf&@38~gIQv^ zy0=?=3E}&GQU}aF7YodBx0$V!I1s$xUrW)?IUUFvU*1r=?P=Gbe1EV;iQ7f}mj0wO(+^PllSvO(p{9~7 z6SjldCS}(#lWH$g48}22Tn?{t{I3cQ2F5c@6{u}mrcW9>5C@#9y9J$Ef%i^RmjR0s@a7?yciM)H4{X=AA^y1l+TRI~XNd}8x zG249X!W=~wS?DH@s>%dH;!wrAV+pcY`5crt({^p0d1=Z!vF``7rN;tuXluaCCVRS< zbvG8|h;15nA?~f5SnOT6m6Id3DOey5TiifgxUG>Rwi(z+%Ee|T>Z)^F!3ae(l7tpU z3@Mg$D7|mLMC^-b0#z5-8jZ}t%g?3ohb&clW?$cG1yfYTNnf!0NmquQpR`TDF33HA z#pNy>!1BxO-TDLa3iT_Q_lVG$3r{neOTU+^sCwy~p0oGrpCE-cii#N6kPX7fYhHU` zqpg=C`?R?yamswt6U2~)kXD@fcm7cUKWJ{Oh%Sb)+|b5#+RN*6F*0Y?VA@oSZNH~_ zs7LT{udf`+68Cx_+g$8|+(Vgcns}ot%WAMaNZx60*dLIuuR3-VaO!opN9CY>J|+df zv_p&tD>p$TxFU#Yf7J}0AHC2dVm2NWB$t@LwYwH*0(63V<$ zzj|~HbW~Z`qYkj4m8+~AvZciWvbq=2fsP$=<=i`mY_qXZ>rlzHuL|oL5wE+`lERYR z8)lObO?8`#3G^fv)M&y}U9;}Dauc4fd1MEk&AYur{{^~pzFL60Hz%&LqA@@(+vnm> z+LB`o4}2VG=Bg@w*p_G4v5CgM%_k1aL4J^vNP0yGhP=eep4I(w{agL39YCIy}8 zV_;&9s~m6F#E%dO>th7McKrPM%lwz^+u#4|@BjSm*I$16_S;|n@%w-L>y?&dkZpI8 zJz&i+aw2$XkI=-t^vcCxy)%w_k7;V|KsR;C$~?~J;)9sY#QHf0=6=qVA?M_n_vY6+ z8TL!CUmGtMgzxg0+fh2xhDXc)SQGm0nAL<%rxv^~|Abg`6f~oxmo?OlA z028m1V$yr^3$YY*3-0XfgJPcwnpd{{$pK-0j1fx^r;n6(^Db*`(qp#l zcYwp#H+hvIc^tb<)x05ull7+@KuJWjp>=Aiv2wS%Kgx;Y*7#a|@}+IG6U!wuHp#^J z+-%46Gqyd?se7|?wu_O#?6WU=(9-nIG9^du%DSAU2AomQFrM?2Hq2vAxdQLMKXuH_ z)Ph0iz^v4y;5j!vTngFy?+4^4EXYHeaJ_bAk%P80Sb&Z!J3yCCvvbUr2a7cO`ks55 zUHN-X^X$ZUzgHI5-R70vA;L@F?HsdjNp{TF)$W)pU+o;SrNAa-m%PpwWOC4*2fZ?6 z+BF#k<}LRTI`TS`X~OhMq`ymSfzR`dx5aWi_B`J@2I{REUw$Ld`0|eXvIm?{cVoq^ z>3(LRF*7DLQDrr)k1++em|MOL=uEKAB0`gJtESjd!YxpL3#~}GB(Ain*!zS<&10zV z60w$)BujxfXitJsR3I*jbDp{EVuT((=R3p~+nJrSH{6eNR@l<*p2@^;8l$q}c5mg* z`5dOpmI940@3_x!zwE;(m>IZ%{F80oMmqz(SC_qJl`(opaxmLWOd*4G7&c z{PsliU4M_mW1;)@diO6H{|$6;wrk@Z_h&A)bOo=yXyyyPR zGHIr%AhgfreKTaJme9&`jJ;6RaJS=5cK30`ZAvD{vUb%`qXWfGO;QW}T==N-8A1Y+ zd#Gu)Z}=d@vn6(WQLj$vNj>_rKU;60_S{%M>Ug%|sN1vk+)ALkXm&nZZ-DkW*&&}P z8W^}?%9Ur)7FykNIyw*TvbzJ_Kj}9NEx=-KICGQgyaH3cf%ElQD;3?Ba(s8d&UOrs zFYm%H5g^}j+;H@OBxEQer9LPZzM~C2U=Hc#C%p%MEQt@y*n$wi!Q7J3a%1le^c$5H z^SM=vEGV)k}M|C_L-gSwwj`WQtc7qZrg93k%`V;?){C;hmK#dbL>7 zSuHy`U#;;o?NfRW{!!E4KQQln(K2!t=DDbxCV#W&*0^(4ADZAD2Q5OFK76mLA5*d4 zy;LrCmwKR8`ZmcQW&cf~Ql!-QA6-930GeU*Ui@WEJ};>b1r(ba~}(Dyg$*4UWx4SLQfj^ z@5fxPsB8zFLLR*DzS?vL6IpT25??Sr+0L1r+SNYhUSOsC@8F&H<;p$dJs$@xo^Vh@ zUa8n~4xsjvrn!-jp@TQxpBS-{YF`D`Mk*Gd7X+_IJ;g<&uruLl(r!{p7s;@m zN6)LXJ|(Ygg@-yg&%UbUHJvCN;5be6R<^Bmb~Bwy?eJ5BS_`u!u0f!a?0tn^2nW47$rg*j$40(0ph zjTE$Z;9p)e?g(FTwe?`@Z7(eOm2qCy${M@_zo>of8MsiRL^vo?3Zu>}i3ab!ABf|F zUOM8^A9Rk{W@8uT{y|@uOMlQgXq%1&=tw~Ww91dNDQJ#4NyP|a5>rJA8Y+b1DQF@g zQ*KwT*+_~F+o1MMfU2JgBbm}6j8 zn0Ew&4-d@VVHR;PW@C0RXD)IF znb?5vKs#BlyA9>9%B1q$PAh061ttv($k*NOkW1g~9J9^DF3ei5O0+|0(2iM?I@9!6 zCnvevIcVFa4AAO5?~n%p8asM)f=^MOcbL!jIfra1F+^fWrOr{DQS<+h+!ByUIz=3N z3bXHBrJ4iiBlkfE=x9?0Xtkqha8v``+mvshwwYLL&=4Uw5y9!fZ&Q;-(u|)TLw$F> zTRS6fNPEY9Fe7`UIms3~s*Qj=Yh3tM4st)}H$dB5tPT2Ou`dGv?0f?>16Z7MxzehI zv3zL=hQIEUj?1k!RohocEv+p;m8b2r5j|ZHgRaxzmY7=>#;pyH?ze~U%3XL&4?dlr z+A!H{>D1xVm3 z{2iydio{{RBfcVaM_f5n=a6mUg%@`_1IFZXYp57o0b`wsDWvy(MeL*}iI10-d|L7#P( zG55iIDE&Dt6?i>oCpJ%ieZ7wV{LlZr8>B(T{!-I(PT=Xy^>w2qd>u(K4>k+WEE|>X zPiutN=cecGK(#x%M$L{5>&KMh_C6_c7(Ng-rZfKCMFC+v_(oL`r21dMhj08WvJ`mU*WBl$Qw*dBrk3nLuuNhFHHNzL%!8zme z`Spc)FH!tBibm}^FQ%#JqBlNUlO`_`6z1W-F3GzgyXVQ z^nFf!v98yWd6DJ6j`s`g3u%bjiF zlNp);-HK!$@wnXV>}q};?Q$0lV|l{u9rmXkH58-oG%@xSi(lFB=yO0JhMn(Y_8eIh z%jRIc!M;{kEwX!z2Mamx2ASU;to1Qr{vGJ^!OAs%iN!uAyYY|@WF{{;7s%QbAL{{g z>fnI9gffiixvTh5L=W^jYjv6m5Yo+B~_T!)lU5nDu3BDH*?*}bt#1-XollEri=WMlL zQslq%O~>6Put_JVz*hEdr?kE|*AL7=1Wt&-20n+vL|~5CCu4ESogfOtl?kF8vQ5R} zkXdrXQV^=>e#p(*U@!MV_xv*v7(msVm@PtRCsqeRq-7CoQ*8v%UJr7Q*@+~7w@t|z zsmxB}LMe$>*t6fN(SQSgA5&QxFc~+YGIYlbW;}_ZIw4DDIqQ>q^01BcH(p!0gu8Xz z)w;>LH{MWRCoR+iYGtNiK}~`}2|B=zP1|FFp)C2F*Ux!#JuC02uVJc%e`MtE17c;N zceNpT9A{j|*K%jwKj{Uc+cxUpd&~t_U7^j$@lk7ZY@?zq{!zc-5a(U>ubf*OaJ2k~ z&F2eC39*^_IzOjxy}RO(b7z&bchy&6p$VihK(jSsB_S;iI!{g@lE2kHE8}(2wy#ke6SSpH8X^)obt>zrOWO5L$BPnY zeTF1uxE_4qgmUaas379^7#|z!zYE==n#wk!jNV*-?&my&?f?>jN1BJauI_;LtS_=q z6u9K%!W{Y%T9*SES!u%L4bV0t3(!~ps)MfFzj_0+WyS_gb<@ikwH=u|F>H$1RM!Nx zZG5b>Od_|hEcO1H#9M85Z66FWaNbeIsUcmN4CJlKK4{C01?WgXnd~R~)?-yU>&_rs zZY(yng)UH6T`MCbtHDU{sow0})o$Ea?#BWj^48^sNSL+x2bs-CQZmq}#!u`i(6>)? z&$_dNwuxA5e2=t*#>RZm#Zg+au8)x>cL&{dHEBnh<%{m5u@~A`D58PB&1-=&qK;nK zF(EzyYul$z`WAINX)2ARkRH9Oe#Eisjqbs_^c$VVZttldfovonp_|j%@0+SC9;|JC z?3=zj>3-138|_W?`Goh4W)+@9Z3{Z7_6A|bCry)bdaN;f2l@J>vpO2~+I`cp@#lJ_ z`^KNjukIOjiK`$aVH-;Ni?riS6IgtCW(k(X%O?tSYYma_bmEEt=YnS_v>?( z`M4qZM{laH*LaYQ1{RPJB%&A^cBtZks8(>9)j2|$meJlkMV7LQOfUi}h1^pb}a@CSM-D#B`53WzR!~7#J zNsrnxV=R55K`Y{yp$!Q_kvS^^6@`Lsu3W7VZH;mM9PN{pnCp+&X`#1>7Ok2)n-cJ6 zbr-j%9VJ$xK^W=Km^(hKJXY*OxgIT?)F``(KU&FF)e&1XhZ#{3YUdL(1GXe8eCX#C z8oYP43*H|s7K{%5dRu}lgrMeXWpG-wh5UieQg~i|2oi=TwcZGpz~NxPjoWB>B65e zG1o^ZN66-JqDd=VO}C_OkJt7XADA(}IHSvr+ohoh3(WmqNVZ?tmFSZmk&XuLrVo8? zW5yls_D1W0Mq4wvfnlvCGqB@|2k9NKHJdX$#(CeN?uqCdcii%0;Wn>s<&I!5EcOd` zD~HT>eSFVge?a0bArX)VW!@;fU(bzT+2{@AEF0qqHg}t2*7gWB>^5{`?EOAA^`={| zvK-&t@Aey@Z8iqzRqixxP8z92+--IS)95KzW1WxI8=!4IcJcgOXQJK}hV!kSesD17 z_IgKQnXR6rN8v7bcvDAVNe6Q}hy%q7%Y+z&0W%+UV2-jf{J0Bqp>SjgWN)vpkrcCs&a#nidltWw-_WBQtYwkc_1X*^@HW!Pl9ZYCk?aEB( z8+Y5LVpCfSa}Pz= z!G0DFU@7_OZT2H%$JZPo`{A$oE@YofU>38z&;A9u*B6sEO=7_Qa5{1e`Ay6=1rugj z)ikjIi-9IMZ~6HSh>7s|eX5s{VfGW!e5LN8igS(t7FYY>0CtD@3}ETiwh5R#N?xLS zPd7}}7|j8+vd$t6V2Ra!ha4|+K(72U=a78{)*Du#n!36-l`*H?8|W=lLq?M>KIXvu z@W<@4ksPznz|N~3$w++74<{pckXtpC-)x(I_4suia{ri1A94=)4;0w{qHX$6R|(8{&#lKec=!P%lB{Jhv7@pf$P7nUz(WmfBu`cI+YvpjN`zD zgFR(9jwnn6@T1&Op4+&*{WSgU_{;wK-(Ke}&q;rq-UC}rpbeeMO5>Boq}1ie;c<{% zwY+ky>3yTA<7CvD9bZ3g$iMybx1YcL%P+tD`umq}-+%tw?;roJ%>@%82PW2|%g*)Rn0Qf@)|Rp0ezQTr%-drvu)8e2wet7G z6M9T+uxSy&&={f z6dMpTYywRbD>!uAg4i3Q)`<;ve9}GRjxV}!#+`rCJ`L-L*T_kDuV>66O6MdwW}Am; z^~%Hm{kBfnKqE6`^kJpS*qBq?pBv%2`<{DFG9(tkBdLZ7VlrD?X>}@y?ReBJBL+V_ za4YXL8kMJl*xmcwg4q7KY@dr2k|qY&3z;@2+$-n_FDv9&4;RpP=X~a2>D>m7Ip!%4 z_cSaZKbw=>L2jf|j@b4IFh*#}NW9Ki-pECvz0Tuvt9Fu$J@22hb9NsYEq&vdS3(#e zi|xbhW-E%Xug7NT?~eIQ#K!!fZ5kF=I~aq&{48N`hxzOW<&bTwKn%e_zb}%o8Gh_w z;JS1vneLong|@xh$f9|bAOTNiXkr9+V_={%GnBRp5(8|)GfLr$_nc+^O`zvn)>`JO zkF(tgCyOEz!DbOdsvUzRkqW-w?D?^d_FdFIBUmnt_6FEfzuCbU#LX_u7^D|FF~7!` z?VxuNr@V~NeiNlMRpdGp<-+pP*?V7K$9z_i*C`jSBJ*RmIanZ$Dw2hkbgWJav@Elo zWA-T+AM*>o3F@P~_Q!n6GkYxg(+FLfacmUg=TSey89djqxx3e$;LQyC zr-m%cyrt{^CeWhR|UGbB9EiOKau>F>^(cH~TJT_`91R*G^^ zZEu50kFq&Q;+;9QxeSeR{OM2M{y!AVn2{Y&gqWk^Q!igwE;!x!O|ssiT-YlA5h7eu^trsoSe80hkOJO^nc($ErKdtvLCNWSs=AZBq8B2)5OW+vnX*-4rKpZykEi8D$raz?Ji{lqT{f*B9a_Hek6KlC529B6^% zgEKPQzBh3NGQh_=I5r~@=wun#q6in>((yj4pt(EYMuoj`!ZrnqI~}92{Z5x2h0V{{CtwpB zfixf3F|%++6evGe&N{QHgozi(#4fF3)y7JwO$NN_xwEoS8!K0A8))2BofU{}l@ zCzp`0;mg2gN7#ARk%iIB(NIS1EG&!JmIw>TJ=c!gT{_oJFSmEKmO<)^v~wd9)8;dn zk&VAa3T>})X=m@Z4=g-$`_5!h?QVAXuyjs))cy%NnU$F^^sSkO#@kw7ce@*OT$lUV z@sqn3x+R_Y-L@&%WgFH#h6c`iRUKJeW{_5r-r5@PKOD2p_i9}+Yu`s^67;jCZXWBp z-<|aJUZomttdh)EJrCyy#)4<-actNhQc&}XPb=4gGLf&LRZs2&TLGV=yM@snIAJhyo21o=W!tI zb~*PSJ-h#}<`atwO+bSYl|FtuGn!94srk9tEj?9gWT26KI-ARhN5}>49>fH4Bo&Rj zt%E+snkSv!?KPpp7<1-Ho@I84mFuKh3(|1L;B+Zy)!S}&E|&s{IcJ(Xe9+SgTFYcK zSccOl@{+G>J;Ss!Zu358#RjH^4mE>S1MZ(XTIm4gCMF{)p@s^r_DKioRLAWN^{K!b z>M1Tbe{ZaqVM9U13w}OWoORa2@rL>n%n-JKob8#8O)St6b5uGVODRDVe(rt;=AMpS zm`kT)IcS@X#X(0p7N9GqV>x7-j!ja1l8Ur1TG1QANaOw!tmw2oR#o1mCf668H7FBL zeb|wed1*XKgz{rzGI-?So*1KiJ;(iKsM7|e_tcN;9i_Cc_lr`ibSa%Mzur?n9HwTZ z+y{;T>`h?N9UKD6gQHvu+9{JfA@jESVPc_2`2j2#B(A%R}#TkiENFO_+VBpO-uGdf3VMJMx2GhPL8z>QYZiUAGSA>fNW z0sc^vDH`U_#aew%-c^6#h*ySXb<`Rn93BL|QMTPmQC5OcR5{$Sa z^0yWbW9vLG&}Fa31`6-)_X+uxd5Z0p9~;>YqODyK$l!t7@`xEO_*fsC_`k&*ptC^c z&GqA;Baq>9^7W-+L-|;}%YBwu`G6Tbw!7Vc8i~U*GS)+P&@F`5t1sJGr#{`@a-V78cCJ?> zn`M8xw@5ZTw|(+1JGo?`=|^o-vN-4~lJ$dDB!gwB_GX=tEL{XdacpmQXN+2LiBLmC z!pivRSzMNTv|3I^7MdiYbK<;zU?L|uQRqUC{i|Hu=h8xi%wcU#%=bI!5F*SSbY&qz z4%ud8hz{C0Uvj|?xw2d^hio~rfE>D_IOWp1q8zg&$h13z;EnR)R6J{43KN%&KzL5^ zJpYv;q2;Il{Py?%`ujhB`}LQfzWw%>fBgO*|9VnI$;9yNjrRj_Trw<7TA%8XPCU9Ib)+dY*10ShIM1K6qXPL> z?NRe7Z!~B_-#zdtZz!ErW)&P@ILoR`0SjGkF;8+WR^z>Y((^=zQa_i(eBJMn3VM3k zP-Zi+0Tt2N)i?($HAc(rrkboeSP2#A)u5V{H0)3yzD|0hZ%J>u^hxKKZ7z0U?up3| zn4e8d5_%{HZL_feeTA_Ox^x)JG23j6uX$&}R^!qynOApzawTW`s~pSSX~0xvGJErV zg|LBPQbrLO623rBj2>c#U6TcT1G7!XR9axnbG5F*uUf4ugm_PZ&eHCg&^^1?%G>Yn zILRIDbvj=w%zKVkX&V6K-83mJVw;Pxadep(Oj{V3R{u)F8iN`*(umGA8#wu>9pm2a zi4M>+8{gsaSrttJN~W$Im28D5ydIbr8BV{w9UlA!kOi{$cWfC0CvQL zttq`$jbYg_+dM1~2l=VZ=Z7@UbSA5MxA0UYn7!-%g}GntFzT*-wR6xm4-3#yWCZBa zMMjR6hOu%N z#{(4H{hV3ase#$jJYec&JR{*X?{i>|ENnvMyTJU`&BES5Z27Q29AC3e`2~em`fJW3 zn9ag2%;9SR=B3F?D%*_Zplud5_Fj@Jd3*+KDV8avP=9rFhKfjOS(v16|LOuvEH zCS!p(p6Nhb`I&wLvrWb>%#n2WQ!f2ty#dHSG!kNb+7gtua(Wl2KZjgF@G?h z(E6f%>msHHod}4${XXm$zBCBL#Ol+hL!wUz*UxoXeWpt`s?Ssf6i7a<}LVTYOrnayb?CcJy2FX;(eJWonDVkP~P_Gx?DeboV^AAg&AM9 zU0Emz3L+}}y1yQK)z)On4|xavSpzr_yo^J`jgfP*D;qP52KvF=^Y)x0_e_f`xDS(i z#!+MaN@o_>{d#NR+4x+gaR>X&+ierEnN=x+yu~=_iH@waf#=CH4?aDnvABbLYAkr7 zd9w!Fh)x{%dmw9ZOb#`oOnAat9DB}V><;#++0Q>}n~GtpMI}Wc*>CrR>~mH*UH5kD zl+%N@+1Nx5Lfh&GXl64Ie-_7_jh6w)gfSho7DbKNcrd3xDa|%0Ivk_6YLt7in%)3g z_GAT=HoL!rne*Kn%a3C>TN-htAD;6$xci@B{bRWz}?bv5;yg$;nS=gX+9kr~~nx2~)dtT z51gB+IaYo?o-}&BJL*- zHOjL$-fw5h6trq72H)We&I>-WBM2|lYWYc=bzV<;@BM{gS!{=ZtO~5d#U@Qm1m-)nHIE`W|NO7`{r+a5$M$Mh{>J2DA7%EnI&c3dO!L&vULD8 z?JL>Plb^r_j?-hFd3TrlYHFmYZC2K{3S(vwpEMDGd|Yu%Q+DP2S`@z3$jP2+Z@(X) z2bSfC=NIUae+6H5p7478tByJ`FY)I46Usn+(+#s^`+U=2ZJ76hl~#MGGfT+Xd+)ay zaClNVs6puCS)z@cwBZ?|WSFvAZW0Z<{B~QKY*v9qw3fhT#id^>ktjnxblJChZH)^x zrl(eA;=T80*g>_cdiE_JDve1ij;+rP*xg0%_C>!jButmtywk$`LJ&Nl7Taao3>o|) zc#HObJ?C|B2l^CTjLGVh*&FZA?BgT{K{}=Qg+flg?5Iv!q5Yg6QHD8NH=wnu=A>mY zw$RLsih3a_Imk=fBALBhauU}&aiO2R^*%fL=)?Zt1}C^e832eeqU@*^zI*bWbKl;3 ze`KDrebk*Nlez80e8eo^Z^+}1o0owCqII_!< z{LIPN9vz^`L$~MFK-pL(1?)KS$9kA}`+aIkN-W;yrLA3++~!3YuDAQO)}#?qwX^r$ z*9_&_vTaoyYzW$!BVE>(eIL)+dH9pgR!hv@ct0>-pLAg6lSUs}EqtG!^c&IJrev3N ztW)1GznIC13g>Rue5fUs%Vh1|dS9Zz9p}LILDTHe7}kMmR;0r5oI}su?QW$dpeME= zO>XDxOuNb?Wg4Gc850ifBtB}9tdn zq;1LQw~O8we#7?Dq~wGw#o)8l}2fKZ`VTdptPqIag~XB+f8kg+^HnK3l76kJW3 zP}#0uPd!CfNnLAIIL zfVf-BHzWTXlykF&wQ~MRi`LJFlXcjSxnsUAHhI{K7Es}f{RU*4hs7~pH@icwe6!!c zY}2p{bHwa^wIwGj8?)a4ZL_d}V7rsI{9Z3^Udab5{4&T>^VPWuFIEG~=&Y01>gE&H1Ag&z3a>zCVi$jjA z6Q$g`^vSHU5iG}SbFjc1Bw!qKWfCxlY;&-_G-Z&Q9YIknO&KVpm$6(&wsXCo*(wc6 zL-#q0>f07g8TN|m#aI?=8A`v-*>)U+Z|`7^yR8>$rx#!OZs(AF5*EiCB~^!9Icn#S zeG1kg$I~3AT=;2D5qsx-O%pC!%Gg=nktJ>$J-x~_FJ1Sfh-3`GBw!cj9=}Gmd#%Ww zOUJJ_K-(NFKu2{Epet7wZ(z1b*oZ&PMz1QmHY3!Ze76eo+Um|_4LZ_Dce?@2V=O-+ znI4aLjLAi?upbe4QIt4AiCW5UK$hK<#%39peIewp>_;?yF0aqUb|e`Jb(o<-niXd6 z*^G#L*T#gE?b)=8jbdtYPM`~PwS(Q?tLu~QvAgmooj+GL50j92lX5>ym{5T-K3h79 z9UNlY?A6|6JT&w~kgSwo`Se{JP7 zpMe=lf3R;f!((rtmTwIr<_~7U&(`x!9=eqq5)RZUJ;Z#VLroFQ&}e+pI&#LrOMlU+ zREjH`BbKLyR5S1-T2-j`dL7Gs&+0sriDhq<&%(|l4n98+S7wIZfNZlc8EkQ@gY)dS zx;ABypR&!uMpg_gjQ5NEYO@$4BHz>LcB&V%`@K${sXtooe;=kV_^rP_zW@2pUw;1O zr(gg2-(OG${UKrb&Zxo{{^+0i_I((>G-wb$75?YHX~W{oum?s79~e8Sx5-dF zl0uZ^8`5c>{-?%z)}MbmmcPw^`}X($`ujhB`}LQfzWw%>fBgO*@fIXuf>A6J7ca<6 zac$IfGc_u_#K*>f>T})QJH+oowvjO)vtXS;2i9hF$Sd|vuo%@&`IwtgRbK#oK7U$c3glzsZxu<&9<1MWr&T`Ru> zlDy~Kjgo^Nv3R$mYKaLpPm@+7Zar>mE^(n^tsJ*gVPRr}-62QZ4#>6Rb`ILsr&EB& zoec>V^QXubm{?l}EWDm`X}i1JXWY&)+ca#_&H}Km6r>wHfBg<*G`e9pdOSC^b%(hn zCHv?4`dlp$;mxpFT+l`luMe6=*uJAzshf!H@+sH16g@Ecvn z$HwTBgU%zj&BWr1j;tiYSmnGV|DbJ^I$`Fdby*kW9d!~7&&rU~!rc2{Uy$Q{)@btn^H0$U+;5=T>E{_&)IegF3|Bl;{+nZm(uTZj@fn!v>R>bF6dwfrdyc8 z{5q4ntq7Cw{Ha!Dye+BpCfFKy7$Ju))MO_EG+ zm3Y3<;Ab&Swd5pd-%Q1jIbfe#9^3uK97gQEe2sL!S;Acn^m)J2{WI(fviF<%0)qRx z-?LO%Gz-r_R<2vuHD4M-iZaaB7Wl=!eiC1ch#iq4bV0xlXn$G#|JC%)N*Hk z0X~ad^OdZHJz~`&S@bE>H_P&4vSLGS2T1Kz0<<=>@FN`mu$4RMz&uO?Lh;dg8hI0J zjKMi$!PfGXkNBw7lE#s)>QUpX32bNQzho}hWFkxsu&|)H-9Revj`1T_!F$OB1;B}m zs^-&7DvFl2cjD>jKm?B?Y7?e>HjFZkdR?rOM!>IzgSIUlF0VsPV;UE_mU=eSE2$`o z+SZLP`BKX{Hox6AE7L^JqV7Aviw-)A{~vSjvTMna9PQ5e6#EItyk9m1 z0UDkup_vAP)Z{=ygWV8%(rg_1$$mGOwM`nYvnP6;GV%0Pz(D6XZYm1qt%A(*&hAf~ZFdbv?OA>&YfNmH+CqUGCoGM~fDsiqVRW*iqq zLa9NYDh`r~umZCx=8=@zWz@j&s7Wa~-zyq8Jb(!1QVf?cG{U*>Eq{D^v*LJXWA+=d zhch+Rk`wCdZE6I6G>l!{)HADwX18jHa*)HVdZ2t)G^#ao-b2&6V+`3~E=KzRa}4OL zU_QixCNix$ppzo@8ra>+K?{}Sdu&Am2<#04%1TRL-$zJ3&>Op zA6plzF!y3gG(|T67>~^gj<$(eW>sJ^!uwqjgsM!M%Q$n}Xk)M&a`)Ea&>?T%3JGPs zb&lD_V1YTjb(p!Zx6TpU`0GZj{6cs6I@ddUIT${x7Id~uGDmEKFL6H)geC5)%>H za<0iOs?MimVn1Yu)_Zlx_p$Df3m@wov5mUI$a_E50l4t7P7x=n*Kw@FUGq4%^pQU= z>#lRmHuA!%tCGh!*RAf!`stU?wcAKB<&bUcwNm2SD05!ClJg5?0mwA!X%LGI{ure6nidLHBaO4Z8B#&N16yYz)8MrHEj^UXJ_SO6SwzRwFjQ&dk`D>pX3| z#SCXeK^qPrZ*%byNwyPhBcT2Sb3fXm5lNG%a{8P@wxJkH3>)2%5PL=*V4_BH5}EB3 zwwKo)=cnMaIqfv|%%rcmx{}oDDhJZOo|R`6Ryhz65PE@&I}`2eewmd_Zqhh!J!jDi zq`X)zv$}{RNi>qlUSIA@xnn!nT;-9gT=-z;H`&HtQpIftn+IC@TGNTcZWNNT3S01I zGA;$}MCY)D2R#ZJsoG|E7i({;7p2(r* z5uSSeOFnFd4tO+Gv9gadsFP(JTcUeSGg4kG)TWRZMSO@FU}3eX?Hi(}=XoZw*P4da z3`#uDOwGVX}F8D+@mn05eJBK zg`+S1XIo2h?YVy|VLcA7f>QOf)OLJWHsA)1J`Cn~)IF0XL3{XymoJ;I!9DfKtZ zJ=&h|+3MKT4YogsMwn(ckQXZU8q%2iUL7g=N@Z$(cdyP(XeJ9#&>h;y_cTbnP1tlL? z+6H2Q871x?C(^5h_?rwvjEkjYo#X?+HVj*so!bx^g38)f9WFdt6E%jFm>YTWilreS zFM6?DZ95U!@1R!=Nj9}5=BvXdyc35dA#^kd# zvkk=BfFx!O2gZ^aM*8)+t^-5HAc;y6jeQB_3A088xz~u}A-}`NLu0|!PWJUYe}LIW zVqxcqKEuwHqt6d8+c+#ROLJ#pW75o-UWh{VQk^wxBp*PwQP@njOniOcW{@(+U;8!_ zf+fOfTcWTJ=mFZrf;t@cm}y_OgaDd)Akr1(?z7Si$?LEDU+cJS6edeI&{XzkD*H1T zv}lsrfrKPvRn+Ur4hf6ehGH`k4I6p(eZXfzFocUsKUW;^T*n|$+Lv$v&Adn{XB60_ zZ(BO0#F=H{#VUZ9OnPRZ9X(U@ALYR}^c(mLt3)fG)`F6@7yS{~nL|TzUI8JEt`e`P={J_>w2srEW>}}&G=k9E=yz)H?FBejcNhu1t@#5 z2wTtc~aw=0j_ZYtYHq*`P?vGUUInGqNxybw+nMZ-aK|IdS_ZAaB z70E8zzUsg{+;)8(s{`ECMp&zrPUeNiv?jn$@UMIg+VEAbf^x&oJJVi!zinS`X*HXQ z;Rf<>qY#O;)z~WvdrJ0cY+ia_kXxm{`!dmM*72&U6y4zFgTv zRpo@q-dx#QwTv=luFMPXqcbG^TJA7s8#!-jyq?NAY3earSS{(G95eBZ2qwA`b0U1>0IP1*GN|80l1U}9rVE$7^C~H!mO1B5Z%T}Q+O{-!zD81x zL5U`E?M&oV10ESyl3aWJecj<50U31 z_f-U^aZ6n04ei4+4Ks)n%%Rp)i+E>~_*S~nd%fnCo-DPiQ*8tmnYl|n!|p$u=MvwH zgrlGE${A+Et?H4Z!6SZZanrD!31M1htr}reQ3_|)kZ_XBnq^NOG>2N{gYe)GuvW3_ zDmMT?ow#s1bmrL-*wh8C{})g2_@rCtQ@3K6;Vbp6)KZPKLBlGGL=eZq;2A8FEA61 zcErmbZ7EgBhdEX>D%H-^%T9j>(`pV={%%2oO+KLb9Ogo3vbsSJZN@gfJ7BAZvZxx& z(}gtQBBEVQWku@WSbk~-c|h*wSFzaf|#K40UZEHN2s*Pok37w6-TwOEM^^&7=I+_|KM%ykNh?>sdj2J8WTf;8{y?z>c0#^{N-2#Iu#g zqP$Ah(j7F?2?rtq5{(`phMqj-SymsIGr(VZej|d(Ga8D!Ly^cRCmJzVh z(|pkq0`o+Id~TK*y-oT`d4 zT7EEvjxXC032A*?hz%tZYsOkq&^9Oo^fs<)&dajn4O-3mv!12fwFa~wVG?!UoNh3y z*1u?dC-z1fBh{XoO$)rofz7S`UmL9sl(8fbB^55iLAfGyVTg>iIVyxL|_IS)XS@@ zKAG)q*b`O%4ms|#AP-HBvhIC`J(wMB8;nf^VYB*nz4|TvghgVMm0zoLrMw0zjpGi` z!K@0eS$7Xl#OPI2(ZY|FsIgg8!mO~fbX@5;-mv~x1~zLS@BB{N zP;Ba;@8jJ;mp_n^^hr_MsR;7|SM%3$^YA?LmYQ=*5?gO4QiMBocNn@2|{if$=O^qX|uzg;_k+ z14p#q-z3ldLN@>Fas2e>|N8Ua|N58Te);LI|M5S6`}hCvs{OKj=lu2rXed(VUP}+* zU|5Ce>9ws+V}|EIq>)gRv99;}SP!UMkv}a5Lh^Nx!_W-f9aI`HdLto>`19#_Z|L#? zau_<(xZe|Lf9l&iqo$(TXjg}Zv{vb16M4_Wdz8Thb<(6Iyh<#6ig6LU5SciH;omU7 zIhmDU&UQ1iN8O@^L3Gfu7l}&bP)#h&{1YZuUu70$sPkD4FYF(#Y-E4hA%*wu%2?V= z?(yEpPAJZO|bi%r?mB3Fj|$7_A*C&;ZEPq7G|_#TWAMw&CE#;o1kp_hUUXqBYwQEidYSJF{@&;mj&K@!XsFzzk_cX@K0eZnH|V z{kwzInXPlxDlh9VP2i0Jf;56zUO{1j0dj7)raFfANGBF{S~&8m{sB4C4sC7N;P+Qf zJ95Z2ESvFMWE&%cwVSF(NZlXE0I9@)26(xrTE06ie0w#2=)PpoAiUzSnN_bRIti8I zG3p4J7zDjI^Fq&53l1W%3FegU@QmE#0)q^BcOta+bVQ#n^JV5##>k8L$8Cp_NvqOJ_s4dMi3pRn zB{gsIN0G;fw72uUSL)Ya&h<~rpZ<5PTaOfqxEbHqtM&(G5^xAMKxQEatbd(!^e|pC z^8@5-Zs7yWwpAIJ5#l;Fe0SIxKQ>bYt_J#=%t=*JyxfhTZG*8OccY8m zGAogOwO5J_v6?9^XXfzWNe2tBZ0&rF7v6W8iKJ0g1Xgnl+k3&ugQ2@K%jAy_ui3Br z%;CNsE&NwM-0jh-w{re^v@-eQ1IO&_Jky&~HsVngR%L9LnY^hpvQ*9g#-nWi+w#Zd4L=}N>6G(yk5V= zyN{M!Tv=9=qoQ~9c9xZ39@AyL2lYMUxTWJw2O#3Hg`UQy_lg{gnbOzZ=@d4x_u_~L zhZpSE*1Oi4?ZLOyA7=UPmscKUxEDyFi(Am39mLMu48orxmdL&c6@@b!`Exi%RqZ|P z=%wzXAwC~ovmX`7wo9nRW%zKy(1VU{=(<`SvnAhr2{g}`JrkTJ`c&fVt<%f zZ?&H4j+v*Lwsbj8Xea;rg!;(D`lTmG2S*eO)N9ZL9w@a;5a;H(+B{nGk`O9o0 zvK0nDMA?ak#=#T45~hekI+0`U^_T}8^SYp>XdRf1$tE5q?Vt`rYdGuVp?=%Y4}}wf z)>55hozUU+`bR|EPr2z{`esotL*ir~suoq<@`!e%SGX3J*Ent#60}sh`7kjpNTu1# z{H1(jc##wNVCkz|lx1fBX#rEk`Fo-pT1m%I_yqsw9Mv={4_WlwY- zE1Pn zrB>~W-MfbjtD$Dn@Z{NYue6uxr!NcV;&x-f5PaK>Jz#DfoqUq^@FM;5jGzZ<7_AIC zUAymO=*qd(^u6+*YtbHFrC;mNkOFO>M~w|@QSYh;>|OSSv-Fa>G25>U$X9N~z+H#j zlB*Z{l@E|FH+acAhS(Ry!pxo6t))rL%!LQ-KCrS4#KOwKehti(2NpiSY)fM7 z;Y9_Ntb(1q3jr9Ki9}%fO!iT(I@C`#dU|%<#^Gi8wdQX{l}tIs$tnhkw@3{@RCY!d zs}+1Z=p^3uqWm)R%9M12OdxXC#8mk_t3n<{8gr4S7v*1RWN78t4O*nJkzBGg=ym&E z-3GJ++KhRh#R!b3OH~ySZB8{VUv}IN+2qBb%IMt(wH|{R`X_fwxIEdM+S-fq2jtt<9kS}7b#0x4w$WIC zj>shJ{B~sWVCR-+d|+rBjg8PCC0=r$Z3^ETOtZ>oI~$HZ7>zW3EFddmZoV6Fh(EQ| zOQ-LOVAi$sn*57eS48JmFXslWR?bu}p>7cwt}NlzFm+rW@MJaLo$mfR=tCJZ2j-CgY=TS^yT3%~AGY_-_5z|zoK>Cf`{T?yESDrPY(CsJJv##4!$$B9*InuOC6Y_LKc`S zSIBb6HXaMelF?fkMU0haO~_?@uV0y+?xYv*b@>ByRExsSrK?4`p>0Ig&M@hfqR@qg zW z3FqQ%zx`n4=E8EwwpA&}9OkArwrE=p8URgR4qE?myyvw&g<=jun z@tH~mqpGjD$cKkeiRzTHS5P`Jsd+%&;K^@%uM$UD;dN}v(O*>4eSmyblRp4$J>(6V zRi6VL{)OTTUH-2PFS1t+cydEq4;h$u#QbMU7x&rL6612xR_*oA&77!XW4dd`)TYs&~dog1IboAwqH_U zqas6Hm1ADc(EYJujqm>Lg}l5tcGx#8{oSlBmU%mUP|5jVOJk z(_skSB6qqO)04L7Mv+Y}T$^YP1X4ETGA}0R524uQ#-?o0oNj=@+@u#}=LkO$RX~fd zxEsq2?L)DCxZ^%A9dzaUoIyUlEcWffdh~g_u)@)2=EAgS<%6Sr2i#%h!jN+-{~`1K z*T4Vu*Ps67x8MHqw;z7``PV=F?d#v@cd~As=UC#%uuCOS2g`KRX4iUY)%83}*C|mh zI}<(Z8UIX&5G`={;?$tT;lOS0$b#?ht#Ej7+rOhm|HkX8o|z^#Z!%o9ol=3e0Zz0L zcHXO9-@}Vq_Yc5TEur3)G`OX^XD&4cy)ctn`~k5R`0!|`t67{>NA z;?pZ*kA$9c3qczjiIoQE!Vr*3>8fTQ5ci!Swy~HN@2vRu=EPtoak%M?$%RdOxq?Y^ zVX1EJ>E*C*#JDgd>3+vt*@^8 zR};dyj`nq#-)r|;_mxSDGMnUFwhX(_w}}9BA^>HcjnDKz&#G?~ZNNki=JZO~69K5^ zBsL~r2HJlU<*|WY&W6?zk6h6w8rbQg`O^zx$BMv+KvCYLYBZIOK-1}_vvn=i==`UD z`sp8YtQ&KSKySpAop)9vl6H{^!~*f_*+g&#-jq z@H3lYI}JZ*eQbdkr#djJc7k+r%|Hy$ zxnA4pwXhd0YtYumU3RKpYFUKNJYd2C!6-8iA1wdJOMSC$ADJ4 ziYCKSw0ZzFFzwnw5OT1^i=U9bffOi`pv038=9Q6Rq?=VPtGw72piZXtPE=kD>Xi!F z1nLduE6$s1O_T=VTD%&BfT~3(H|kkKnzotarLiZNW0S0_t4&342CsHix31QU^8pXp zWxv`24K;Fhl69vS$ex&d*;&@C7aMv1rhmRbdDe@)@8ynpPqk}KS+q0CoOB#F ztoCH5!`B+K{RFX|Y$kDU%$1+)Y~Rx96|!Yz)NrnQ&67Pz z^oc2Z-Rq%hYlqHsl4kM@e_-fn?Px2{teK70r!5z!TuwaG=sUeUwhSx&C!0xD&1tgj zlPtS1xHVs?Z(o2rdm*<&JG2IxYDhluoUGMiFlEFZ*mF;9Dlvz!>)n?JC_Jb zwLY&^gJ0Ccd3djz|IRD^UL{+`ZJ05-ruc5mmHl_x+kp`Fft_1HfhwUe&x%h~P$D}o2+<@{cq#MOz}%<0yi>ekV@CWl$@0JW&{gbo?CpT#;8b?hh`#pH$(Osrgc z0PTYUN+OTVtf6){X!R>?(XO8!R{LRI2r#ePSR4e5c z@~v${>FX%xyl>cc<-hq%$+Xnz1gred@r^^?Ip7B`qQ#!D zZUVDjZ8i~A!+dSzN&A&vADcX*ZC={3>&|n{Bh|Lk?Cd9>>*+H!%;-@jJz6i14Q4Im zQQX~TKq>!?bq0&~Xs_6pg6Zkw0Xh-T0@7B5NA=BTItaQU$GKGUKw&((dl zshFIKzPq6t)qgL?o>QYz>++KAaVH}h2K4R3#I0c@^LAolUDO1P-%iiA55?LX7V``d za?{eGrB%7aPfnSnS6(C=Ej`jIjc{D$&oLku&M0%pzBJY$Yl5TI#&BJ#HmDz6xi*&B z*~VdE=hm53?Ah&HI`+&BZR=v19X+%Wv0vxdJyckOiz)HU%(g{&L+;o4hFthMr;xos zc0i8n9FS{Y=Nz;pjoqMoDtUvhoJ!`HZ5S5xK-!T~_R!v;Wz!&@y7Kk1s!RAb4@po) zG-24pBQ+rJ9q)5nIVF-@-3tvjF!5n58vg*Y`p(VJxNnvTR=0D8*;d5D%<(*jnJYif zAK2N(VKdFSqm3;f!bO>6o;ND}8e^y|OP@P28;9MXyW^f|*R|pJ!j5~&G21vyL~gCg z3jtVm%!643{>p}i;k+Uy=o&Z_dz>F*oog=-F^mKga)a_;x~?x)ch-vba{N=r+zb0N z+VrB|lrHRpdY!)4*;zUf)`F> zA^Vx*ps-IQncDQWs!OZXqhv>VNoaB)wiU8K ztUZ1^V^@KALDJ@!%MpoM_4fnJHYB?-6LK$*Ews4tq@TM`JX-6GQ(i3o4?x?XY=Jd3 ztpeFWlWOhcAGJZJB!14fjmsA8rDR*d0^AX~wMD9#OrEywN%b~Ebz8&ZpT8%gT?1?z zl?Ch>?FVHW3+rw97xRfILeZYi0*!x%$?u+OFVByh9{V`)VR6Jol7z-z{)j_>9UTL9 z9=t|>IJ8+w2dV+##Ak(QYq@u;vBz?M#@va?tF;D8)1&cl%?B5p>O<*=D`8$>XB(8= znEL^r59Y8Cv8sN+Z4VF7HYN+uak#_Kr4M&*Xd9KyL?klT~M#Zem#C3k_mVl{%d2%*cwdeZO3%$@<-#$_sXPf{}JDaSl( z)3Wp~%BCEdS`UzIOcpo#hTL6uVaWM?w&9r8XK(0L-H{39q7kBSb;q4ZX3m2b>5mJ& zvVs<2(65>Fnpt90O|<5&oLW!6(3%{!m6JrLpOE|D0xPRjG|J^BtJG&kIJSC9=0ZKA zPecrAhOjWT4aY|A@WTAg0s&U+-w8LSl|xEIrnRWt8nCl6FeMQaga&HGBHOXusAp_F zF6F8xE${}k9mB%XHXfTGAGAyw8J8Uj-K5Q>DkLz*fm~6wSNa;m;(Svaw9mAc=@&{` zvrVeL85&WS{kqgcy=`|l=sdIZO8so~BRaVxo(I-&Ot3=P?niXOTtmHU-F#W5>dgVS z5m{X7wK;AMcz0#1SU2@%vAX|G9hnWu!p_pJm#B5vS&vrP^POncV`m$bO$*#HTC{qy z4&+QEh^vvT_GHy_-dAaG&QG^j>JQZSlhsk{$r_4k%mF%ivb;{ePP3|@D0aq{4_b-VzKf#uUl5aIv3tU?FVJW)wb1Bs#KR@R(E-VDM!bIC>*2nrWB|`7 zgNZgjeYyqatX{4bj}ezq6>Se!y{%w3zvvTl+t`c+aEuRgtc9B~Q|+@n+Gm7p3YLLy zbHg(l7u(-K$HScSn{31Q<&INZn5U*X+|D^HvuX)fw|KI>lqcKP%39VzOFlBLl?JBz z1cfs$2u4|Aoyhd#un$^Ut%5>C(`X24bf{8RFU!0Rcxq>_(Jw~PK&T!;immHM3E>sH! zUL0=HC01_RD1|PS^WH3-ShaonHdO6A+@Y{Q*b6WENx!VAj=N`la=9C}& zRaQjd#T2C$_GZZrAuwA)nN~71I_yb~jRR+hg&nmm{U9PQ_fogIKj%A3Kk!w?(5iZC z059Cxd_erCrPth9J<`qP!qcJ|C)Z$(%KweISN<=|)eL<>PSi6w+cqMTP7`WHbmCe2 z#^ILwM`MW6(i+M|p}nQW;t0`1U6SdQ`X`df1^2xo3;=RkJkXBFwXZ#Jbzu{Ux{bW^ z;MMwP2y}|j^;+#ba3YQFjTtL#pK=2o&Dv@0pDL%h(zARwUHPkj31qa`w2is+dzE9h z4a{)eS7DTzKdSW#&MI^0U_fVS7LSoLv_?zGN>*qX61Cy?bU>79vW2=VM-)+9yH%YV zZNo9WOdH3!CDb%{=d-1Ey7sh?q}2|F+R!>e!y z9BS;U@lxfN%CTTU4=5~__GC*&#qe7FI?lMQmS(l;Aj})l?ijbN$TGUiqNyvhF_|hv zM^S7#Eu4??)WegT`x%AMr5{%)D}cxU(2e3wKs{I4yT(HYN+iF-gtg z#<{7_QCI>b$82M=b;ftem(66EN~DR&R>_|%@Q;e_5@OJFMI*tsE-{Aoe}-LdjGgVwxKQxK-Si@Uvee|WMx0_~oxa0HtAF~UmoV2M?u zb-bd84u~kkM$6Le4RPJ^WwTADy?}qLFI7)oW8|^Ew6(TsGBDCjp0i4P#@2yZw&fn* z>xfk^iG?#PNiz^nrIK$Rgdi;J<2^$I2zBQ}-5fI;&CXdUKBoQrN~ zaWM9&9QooR&Shc{qnGYyj&?s_gkSSY`|M8ew-VZY>;kC;Ox35vq`J-GMkV=|Eh%HkM z$(Ar~vkk-oa5O1d9Yp|Eya%6R_Hz9<=3eQ(F;{NmAlH=KW-rwrpyM_N=*qYG1I#uG zn+UmSV{mlM=A>1HZ?hV8B+xE<_B#E7EZJ|#x9<$wY2o-uC#SM}JD?NdlsuAg+X4DsLIKBn z)0}1DB@}Gh=0C@qqV!=wk5bAc-0XD73EtExRkFf#NOa6s7?HLp=9T&6S_14pmaKg9`eEqG#3qOnGDuH1tM zHEnp;qZ|{he(v+$WP5rZt=+ARVbx8fQ6Wq6t3;>2BaV1%>WFI>x^vJr9-9Jmr#RoB zD_`jxvyaC*=7>Q9bLANHgTrmhVlz_Y7c^o~L8!Nxptb9GqqEf; zaF*SJ#pl zr~$#@}pC+>8|oo<3$(s{;* zpZorW_BDgk)lPSSUg_A`zN}a3kK-MtUT|a+74pia{@_Ym0>82f>79>pr6Gk%iLCgQ zPFOWi=-laEHj181N;lBzXE2T=lJkgK(KeMk-r>i_l`?`WoOrGN87`pI4kkEbCoABj z<1(o5Ev>b)+>@n>_m=iGyi|WcM&<}7e#!N$C|K#yKK63L9#?Dj1JFJm>vj$|7IrS} z#&SdZXpF-4j$F@3M4}Fe#EGPVFpB=W)HQT^rM*u7La%Dz)(bYPbl?z)P^HgY^?GZQ z?(?0NO}tcpJk;UI;-RkW$-38#by8%wJsBbQLJg(5C(9mcuhf70?;f3mD=QtH zq^`^h^=r#EYqEY%IK*P-xvuQKl}=L)9cMSALT2oOGWjglJ{~BIor=u}OpzQD>=W&W znkh%hfAuQ;tP`hZuzj^ajPPWHU!x^c>}Hs#q*TS6Nx_bbt&xo+ksU!rgM@vWZ3^Y5^AwQW0#LXd6j z(4M8SGf@|t2)$Jim5A&lZIiQHWTvbGJ(KL&@*4dUI1Wk+-`QG% z(M$BVh6V(U>pC;9LZZcyZr6FAOVZ|;olUQ^t&s`q%-5ppOkotvJlm~Ch9+#)>zu9J z7`-rm*g39q*jd+^o$}SrcE8R!Xxp)j&5m)nl~zksPj6M#!<{Z_CCGGsTMBv#OX6_z z*sIY|`>o2RXGbs0A9jwz9d@pLxO31p5MwN5k&<;JSU5Ar^ICVKZi1DK3?tm(4mTJV ztu5t{ZwrI-YDuqfzDc-PMo~1Wge)kMq$~7$UH-9Ymdns6-g1+PMkk1pd>O~I70oI^ zoN8oh1F6Y0#*tlbi0;YgVjw}H!)udQLX|x0EvdaSEa7Iba(1GqEU2`&#XR7z#H$sV z<@IWnT*E-4HVetTJik~LTBhD!EncsQwT_$QTrhUokSyE7pvB7We0Mv?s}*)`uU1hh z01TaEm5DMxuhvO3AkOyEGAu~nipc7vSiNVNo^2bEsmQ&cYjH(nuxwzQ>}pyTKHKyx zGdm|eOFdR#B;wwvoT%M_I4GZC-xO57y;|})Xd98u{cy*F6^C1PKK63CbbGKcbRzlS zb@}f;)(kmNThPRAXE_0{AZ5XN)Qd25Cf7bfgEPKcOu2#JJN%e5Y$UUH`LS~8bn|2R z8=WZg^Ii?=$#cHdn@8iBy+If2rs^9FohtETi7bD&b6jby52E_17_{z{PPR+BqDCi9Y2HnG%U#_`vjaBqceQ3&+}x z&ifjK9mb2zl)HzBx}lwbpS-&Ejixo7-Dn$)O`?jCJ*!(Om0#I=;j~lo-_D`tBTdEI z@8KQ2BEK*mNH?L4c?zo*S!xe2E2HQFm3ySGOd9$@RyOg9{1QDtWA5%tvuH$nS~2v# zEajf)8+5vR81YYcskO2b*L^)v{8Xi1Bm^4M@Da)$@DKX&UX>s56Zi(dhh&PzN0bn* zkgTKw$CqWPB>=1gzc5D$VOAv^y5v>(E_bneS10x0u%jGk4)jj(H$1o`FHR-_bg|E~ z=mAgV1P|M?)XvUT%p!(WB!4@zZG?+0X3;*>lM}q08BCK*8i3lpvos56Pq+uNG@5H| zYDHB-S^B6qly##{MSn-H&abtdE16Yf9pTEzI`4fp@k-LA6}h5 zqHi(2XAZ?YL9)Wq#HH*gD0ihJ4ow<6ly*O`b3`R!=fY9R2WGZ`*nPD7bZI~?J(KkT zXd8+JXq4u5t@aDh>Tn=mRE-#Vo$c&CPhOm#<{nQoVMxcU2^L=H(vWf7$#XR&;`}Q5 zHdPnqdBjFwxe9K)vZxD~$tvBjiuR!!CpKP~L-m5Rn~@!s=oD7HK=L9{y_gbVe&UgV zI`@6J`OSfRPxaG1qlPd$IRu3CoUK(X5jQl#F=QkMSO}$a4FnbaBRzd4&mWI;sJ2&r zq_c5a_Q^UkhdO`6p8>ga{F!66vDl5d2PQXW^)joU@ z(dNpBVwvQzhHl*H)`k&^LR~f!*&>FLif33bY_hBpRq`D4onawhq)GY88CI@iK8-dU zo8V2IT!1JCveQs|3~QGIF>^YQg-ExvwdSNZqLX;3XBH*xl0&E~I0wTfb7VFk3&;_W z1?1A%MvB?X^xv36WUrgK_A8x(wjtR~k&H5Eqx}}InrvD1fLfRF-J*fc>c;lA1}ar( zHI;W`3PQ~=?UYFLZ%_=z`<0$ZD_h1@xeki&W{tR0=${gW{tI-x(xQkmZCLu1-oBUp zdvRm5bSK>D4mTF3d!}(Y%JQ|FcFA*vqMEtUu{nJNq?C@Sp-?Tp>IqdxbQTFxalB`L zzHLMn>(eQ9LultUo@ggs~lL^L)rT6wksma_7{%rRug z+eTvnIij(ETsj&{F?)^v8*?xIajrFq~J*>v1VnC^M4=cxPqcMEgp=nAc z4o`xzaABhMLnEu^!Y(R5KFkkeO(77x>tBBR<)^>?$N&88 z->(A%87+Ac62a^X@U|&)VcXb+81>N~x8b#29*>9{Vf+Kg);D$=Z%mwz+EXL;IJ_P3 zq8jsBsPlxm@pV3cY+d7wWb)2jD#a$LM|{ofcUT$F-zVJ0TJvNR)uIDr^Or;`TR|-n z13R_zg=;*M!$~@2XUE)oGayfPZbw^W9aiYM}?@Z2&g0G3`J!Vu!piv#n!p`^e?b2tw;6RBe!p(s8D^IVIxQ z9rbN!P%{isIwbk1pO2Kii?%T`1KIamMPSAgUHPq2BTO<+i%T(DDMfXbfBL7N{*M$x zCtEWB%y4PeobZd#jQ5I>#cisYOA0o*)nX?hyhOI7o&x3f{5NF-Xf8WZ_&L5- zX?yhF{bnC13LH*RKr2Yh5nrf%9fY169EMgPVop`li<=mj?@9eX!M+3UL^lIbGK*UD zyB2tZx-F3tvu9IrY%7;W2j-+1<|VSV&sJM~Z(oKs;lv9{+axt_c}HXCTbOwnAq z1`}N?NhaD6d`3OvwbDTnh9+q=bpUglX=%5T7-g{^PlC}2-@Q>w->^3A{%UD-?~$ba z0xyjXwF9sOBhQ+^l7<3Yzhc)oI>Oi8wZ`pcTc&9ml<3YHi!vIUTBg zvg)@SYQ0w^SrfgX*%m;(J~jg`L?Ki_$t_h59Qhj9V%&1en&I~cnI(CIxzuRoV`*GB z=UF0!l9!c&Fs_YiQ900;p@~9$5DEZ;Swq(=3qp~LXxJyr%|aUFy$Dy1a;ICEVdxNs zx_yvZp=2F@0{D&Je>@RV9zJ}jedB+A$z8&PK4K+3MAuZ5a7Y-4qC zjBU#GipBZt>H>0NnUZ1t)T)ND>08#jH? z8Ob);9SzQsNk&dT^Qzd|Tp=2~+U*1G4k>KyIF(Cdd2=<$Q5;HjV7w+al}^y<2Ge)v zOsM+FPO;L4-oB~-@XOA+U`TVLCv9**ShzLd)w9#`rQUz9SMv?uoi65{yeRhV$2tqL zyRphF$kdN{MeIO)(;$0XQkn*tW44{j6;jF3D!dn0Oq*E-)N)=XizQh`@?25zao1T7 zv#1rlIHWPIgK$*DgK~9}mSHFoqafzGbzTWOtQ;phto$;=_tDA?b$+sK9M(or6=FlJ z*lAqfZks%-D$^oc)I50!Y$^&rvWSFCikh}+-2G%%Zqud1x8QJ>d4&e4J~Xi=d?U@` zDv546#!@xMNQON!0qo0TNA`T*<#*HPr@P$3k@PCV9B@V^;#w4O^{cF_JkihlAkC-T<>BQtj1Q3jDP zFush65IqbA*wF8KE35K|lU8gZe$4`swut)ztc5cgT6-7oQ&s4)aE8~$UW_NxdRDXj z3O7gij}$)N4mY0=e-?*$)$4#9u~$HTIre&l{2^u=d)=6Oh4;q%?i=-l`NKEL_96pv zc?Q2D(WGpwqG{J7H3 zYK)orwHm&UHZ#V|?3J#?7CEHs!b7Z?n}Gaor{)QAciFk(-t5J$M|EAP{YJ_>DwEO_ z&b#5r_6w}bUytK|j~ZPS{A>gTrl$4V9dooH1M|y$$S2IY&RY)IhhPEu>R8-}-)&hu zA#R8J1IRuGi<8}z8v^so$;Bh)4?%mWYaXQa(4?RW`PBL~?S9gHt$egIRU2g0+-<%g zUq|~!T={6{kZt)ZAV=ODm}?*H9JD2a-JpBoe1oq1QstO!3sT$dsQ+rNW@9W|#a)Td zY1U|dZ;9no+}YdfN#N-vu8}rk(BcZU2OQbdIK2>PFgLd)`84Z#9$FBVL6=N9yN zO3yuKSJ?(&;k^1hzYKhe7FPLGr#AM&*3xZPBRVQV(Pb`est_yNhcO_v!!K8F`mwxN~y}GG(Gy^^6>u869K^*PM zqDqj15={~*=X_vjr503=SU8NTH7<;S$ziTZ$Il5-vsbl-`>6Z8O6iz7SRC#ZhWcDi zyr74ii?qBA2Wrsx2X3e%y*{1(E5|BuGN^$3UlvBbB%t@#X$;sF`Foy#hSxws3 zFVyWsW($_**@s}1_tu%I5*0eB5^$Aymv%--7AIT7 zk&?8oCp6Rjmjqkg!~91j>wlC|igSH;r{XTZTl0JJ*)1B$54NR&g_ZAO5V7XQ3arv; zGo$;7o)8ps=fH@*T7)eD&y|@AsVbqYb7@b%r@OKr3mw)!{po+VA>&48PTPiH3lGu& zeMFc~9;^lLrA0vtf?30Rn$sEPjJnIn<0qB|X>(s;>3PMWzm`%xf&TDD+aL^j_BG2* zYrS;UiK)*sp~@#t$o*c0S9+v#iNb@OK?0}lv73wAys7_c9p&GIkruJU_Ct@r8! zb`;8PCJqzmdBMaC(mZa${_k-j;gD z`!e)8(!X$liI>j5->W#?(g}kgRrz$Mfr(en56CP1SDF{?iB{1>lL+t0k3602EBs8} z=R`N3LfA%%A^)vAu#i+~9oDjN$=_>5J|XUcQTiGVF=+M^SX`95W>u0O=Tme zp(kTAA3&Dx-uSCaR}0gg1j4_!I?j(~Zg$RubreTh%&`2Zma*1_Fq@DQH^wpuJ`;Mr zSLMwxr)S&8VHozHC%v-l1Cg0_k0q70l51?^Yo+Zc&^>{so^LDLUORu0f;{wWX>c5j zjomp$VIQ_+a+XN3+QW^%7F&ivb`CRkhRKV>FP~95T+3eQ)Qx%he7#aa-8?tR_Ch|T zMQ7`)%Y2f z#CWN1)0CWd(n}_OS2f^YoPB*Lm0aO=GH2t(^JgvbBRGwqWM-Cs9{F50^2~<1XD^$- zs%=B1m#C9!m`uxP0#Vq^rbeXItA!-J37bozT{{RLd2nIp1!|OB*G!L6+LpT4h~ZU( z-2$=P(l!taOUrvRuHP&zMN4__;k{h}k1kBNnTRA^hcSE6{M*ua?hUTZZD~rMC})+k z^k=3{B=Zp~otWp=X&#`R$eW##N7I~S!I-<7I*UKOYW_k(F-s1O{fZ~``~Zyu1avh! zcSn{yR$eoImW((zxz#=St1}v=Jk6M0(Puf|-A{G~*~VdUo1>&3x4CdhKXuz)F@L2@ z(qe1^Pi2tGEcvm1TUH|_V&RurRQovj(Z{q--$&R#_15CdChgZ9dxqv z_qe*UexjOB$#S4qqJHw<3#FzKfl%9rw+j}L7YsPX-bn~_SR+hfP?Uq;~ z%uO@r$7L()&1R~Bi_=r*8M62KLam(Y5Z@I#4FL)oO9bMtsi{8&-jQO>SbBC#PsoiO z=usrNmPuZj<;YmoL96TS%<|z@2kcaAdrq|TNuafqE2tKurp(lef0=6W0Q;^KB`Rri zBA-uKvQhAE$s`3(*1Iw5&$pl!{QeqE?#Y|AP&AWyoVltdL?JUJp==ev1*C8wsU+aa zq-wCgR}r*!&LP{VY{X4#19;?>BlW>kVcE-h-!*fTaNS+%xsq1pTm`XoD?w5H?rM6Waa2I-D<(!)Ey~Yn~o#_;PR(H1O zjb=7B*-bWk3H_0pKKn3umT6tllh&xhM#XzxX1pV+b}A;`kM?D$^4H;dR(#wITDqQC@0 zZ`4gDG7p}w(uO|1lNy;9(3g=ll_I~@A`|wqEh-BtGFjitH=f*g^WVpJGKs_H2Y0aX zUYbHtHa);Dneb|?6yY?=~6-rVpa`ZI<7fiw)pC*d~t8g9Ep^mV^qB!NCyy7_Ju z5VBspm(gbk<=CW4%y~XI77&Y6KvBJaINu|IK7dvXn$t|^z?#zyTYu2_U2|d#Xxuqv z;nymqjd>A$#-H~S@=Y+PM$%`k>U*0(Wv!eol~kwMn3;*AQ&Sp2l^ew(Vx!sHRJjMt zQ98(|a$ZPZG_lr(4tg_n(KflD@@UnHU_$owI{F&MQ2)KVuZ23o#HD`Qe;HCu?!UZ- z{%zx+$)R1cixyd#CYS275KWrP9GF$^oVmnAV(<-knK{?*jRq%0-|Sr57)*Kd(YjR} z=r)oT>!H}Rc^hN_mFBMFxU6}>L;5)p!RRhsmIfBU*kEAkU zUQK`Lm~Y-Iow|)stn`KcKt_x((pkDcRp{d3u_CLAN!vb_`*NGDd=t0ae5#^Tm^5+LL_lg*$eN!b_yq>;d z5q4>|3a@C8!JR7Jmre3Wt(P;`c_pQ2W24surEc}98R^0kl$8I*@5wz@v>iXf+$Shk z3V?$itFyqE1hB35kPo>X|kxR+gA~IFI z^qKN37O$*7qrG%c9gSYJvX4b{c0$v6evdTv@Je^`Yet`11{;sf=<%@ohWB};ybV}N z2_!IlhDx-(N3(bUtz2XJ&X#2?D5IJ4tzxIT&yh%rcQNxdy`4!vRK@zG_L($l4Hhz~ z<4Cu5cx`=lRyE9#QjT!u5GHy(rF_5~#m{v@?bHR{bXz|- z+>ufugV_?Sw^Pao)UtGzQX*sY31W@Pvg?^%#=&^THMDTNIB@oWxRXn=>uf`@@L>1R z*1AQN1Ya(HK0*Evvkk?xlI?&gi8g2p#YURY?AdAipWh6{9x#6zig|VY8+4Dk$CgU+ zCA=GRd(7FEHnAt12SY`FmaapmbdMCtT3Gfjqx%7KKiaH`r0DBtB}7)AppKMM$D5&4 zf|PeT-4C!My-GRV3ymmeY>eERbKQ4CGuYcO5-s_%zB}npq6+r`fiNb}+kb->`dzFh}=KpeL@nZ!Yv#9FvNRejbhoc7ywzyx!mq_%j8{dlR=e3p`0 zJkM{}0X^tUNp0~G`~7g=KD)Emi~H=X4Cp2HrDt(XL*uHs`;ezb^6geY7~Y$Tez5b0 z1p}TS^GF3`MI;MUNkrGDOPr_fEQ;!$*}RpiTL$)Wpz$hQK(Y$0vx=>vqJ5HHzBWbq zP#d}u=xiWGyj6pFl@?;7d5XNwv6f5^-$5g<*OT$M!>}21y9FtW*V<=F0WzH0brW(^ z)VVus4gSNpC}--cY%p!?F57Ie#LMlo3i&{s1?P3iLQ3#Km*Qn{9^=b$vFgRE?FZyg z45S~wF0`+8$+=jsS^5EURK+}(d+~bvV^eCm2)#5jzO+qsW`g`>d9eq`SMf8WG%sFn zKOo-~BI^00+Om}^M4udPUl-G$m6QUzTJ;EzMgoE_i&Z}uI?i@B_m8frd$MA;XgD!u z>)`u}F7~Rn?E~oRbSKqMueTq7qu{G~GvqyZyy_MWGW}xGrcZ1fD+aJ}TaB#i%);=h zUOO|h4aLIDq0AGQUsmRMIM*!_%dKpSVVW_LNV8^)2FqeL)J^*crViQUh2eZRcwq+F#`ZRRiQSy0PXY0YxtttjJE|F6c-_N90 z8k9UV4c+^66Yg5l7nR*MptEX;*V>Qc9qm{xC>*%bh1;of;{*qccW3egr+VQ$wgm> zB}Jh~%)oc)>kpPz=t*xfd#-FC7Lem`2jtR+JI8DTu^V$sqiH^OGfRNzO~KO#Ghb=d z2Zpw>Sb&b`Ge8%PK6A)kZ+~J-?k3_)-(#ljxx1E1R8BJRVo7bt3+~Ugg2yq>KK7ub3agv<%+!6#8ly|$-0*ZG!oRik_|h6mFds)<+hac;Wp(9CuoXMdPo0sdLCS8oMF)G4Kh>uU15tE*|8VZ8R2`gLEKq z9!&{XCLQFEZ7eq7x>zW1ztIzeDThYi3)jV{9OV6XFS@TK$t%Ko!FZjJBtlfJG~@O7 zPKS@Y=>EjY;1LE&#ACOhSZP})At=wT2}^D}carafK>L7nTw#(gr#l<0WAN2rmI4i0 z%`CWQ1e%yk?P%@#((-qf-oMq*SeE(MKTXs04?B~4ES9r;z4m^EdPM<6eg}MCY39seW>mh> zsT1>p`vI76d*@x#a#;aJ$M@J@;~YF5>)xeI%KKhxUn9U9i{Il=OUCv<7NPK=PNyfm z&VC>snSS5laW~;!=+HXl$5i#9%8YCqlsDvluG#F@&Ufj9otxP=DEq-C1cUI3mct6A z1W7Jg8Qtb;-ns{#nVEfel6h~fC7dKRB@s+^j_!nHy2~#vGkfx1dY~Xcl3AUu*6W4# z=Z=}>GVMsuDj_hWQTk|qK>PGM`$D}?7#TU_2aS(;9_YU7Gn3%&Nh3a4`eWWFd9h2O zNvcLtW}WYa0oj?d!H$#%n(m~~)o%24yfc-+#cS-(9rAs=rA$M_UHo{bk`7*DziG56 z@QNoIX5>ajQu~R{hGY=|bcb8IG+CR`ywUa8k8&Ahcim~*ibu%!F|eWPsz`)^Np$8Ek*vDXFub>VD;cj4 zG+U9fc%gkTuN%G^m(6B{FQSuEg+Mwx=eoWYfLcNFZ1>ONh4y1nNIcP6=rvH^reno* zE(bYy`}sT2gAC$CSsP!58sz?^wF=0Tzm;etb{;3*=;?5&w3pkLi-ueA zg$@tBDg-U)zHd~-i^HCY$7UOlQQkC;OsaX5?>W-&AQAn9#c;9s^>wVTdG9A{#}UuU zCSGx0URsiI*!o0}8}SmR{;_>!TN9c1rCy6y+#lu4c(J(N+dyW*!fkqVcyk^5_d1kM zhHlTaqOmOB@QVBEjIIM(2t=K$xSuOU6Iq^bfgi8!RSLx@%A`(V7gGn1q1zg&+y|+5~ z>4XRD53FsAWK7pV2yAD1BS1q1<3~N*YUz~oWc`Ha3rANky)S1r!8YniM-z!8&w4rO zu$In}j@9e#tL?t?WXW~W>0r7J$;3vTt83^ivqUY-zF3S3@3b}|<MY#|(7;QYTFk!7f7y<2Wl11))LY-Ly|eqij4=b9x*)QO;*)He3C z9&AqbZJb~>+hOX`^komu=pVF_85L?b!t)4Q8uo(^|s7HOc; zo@^VE=_GH2=A@`_ZG?IFag~;ymZ`i~?SH}?9GmoP+ki~;sgu-`8C%rzOd%UTM%?R;L)aKU;(;v#birb8=4$6kX0_wVn4_i za8i_2pDOVqN<!LV60HA}0yF2M%S>AV>YIeWl)mkxrke!wXa&Lq>d54fKT z*TO~T#sECgxV{f}n^WO|E=(T7&gpYy1Fd7ND=&9FSkW%}Ip|l*0;Ta#ltD`8~O<6m{0BqyJ`(x8&GY6~Xi< z!)A|VZVFIdTqE*W=Kl0}+h`2H*C>uU@PNNT7p6mUy0a^7qcPmp(RkeOK2;*=Ybn*8 zWKCsFHLouU>&)&_;-2QSp^3yJ|`= zSA3uB9IdPDk@gDw0Xiza+RivInDlPNH^ICt--~5Idj0*_Z3gzH1IlhQW=Y#v(wjP< z3FsuJ^y2#gIVwc4&aH5TD3i}yz3_e@j)W4#Z82l*gfcU-jlyooy*?DH8B5oPax>c~ zY(lC=Lq{Rc6ZGE7@;sut5|ubQAc3U`NCbLB&?0nOwDfBt0j;dt0^*e=b{4da!UA;Y zwoC*lS`J=Ww*~S#n(UTh!Mo!uP1ku7(X&^G8p&147H|$w>Uk^6c+4hM+MhE1P$Ilf zl-&-ulmi)!o*30=KcO}cXF1iDi{G30?L>9IBffnYHPA^tjlwFR*|Jq+w(Ys5s!e_9 z?pRHPowVr-hapyvZ$%%&lx|VN5~)sQI%d0?JjM z5h!nZzJ+h{IPyBXSIUd+2V{tK8u|t@1W<`uVCHozHv=9_{iG7+<@V!f^G2;mI^eOd zqmAQcTCE)BmN@4p+ecswUQiskPxgelPwf(}`$*;f%ZFdxV@Jcid+gHv_8hVgz5+6( zd^S-MZnsBDJ?P=JIOM5HaNXqmmGUb4+RCSQ`M$~*B>`(nF-;kO3 zKZ#ym#{Bd!H_SO?AANPmDh#xP-66kwvY!xNCp(Ai!>@phSP8|xFZ70*mF3QksoWdY zqOTmXjlOiSX9h9o1TZW}tc~B&{2Zbz2(?s89Y4w($$al*`R8%|p$*6W{LlaV!>_;n z@|XYo?;ltu@P}&A&wSJ$@o#?q>F06$p$$#zx7!aiDf!bs{>w!Cn&R3-2#ZJp#98#d zQMcI%05&$J4fAWQuXXy<`5)&o{O^@n(HzKv*+l!ggI@8CBSSnhK5;-Nc!i(x9#bR1 zysT`&a{+v!eGTS=+p_Tzwuvnjj#*%U(Iw{p*0o7Md+qC$gL~hbfBpMkfBorSe*5h& zfBWI5pMU++-@g8>t;b@hDC&ju`xjh8$Vsp9 zgrBZ+kK)r&2QPemgYG`;23^>P<>Y9ud%ZA5JW(mXofvakJBqQjo!FS`H+ku6$$Lv; zG2)VC;4`GV;&I6$Em6xY^y=3EJ0g<>y6{5RzjS1R=qrPmmbl;*u-Wj}s!ValX=pc% zvf^hv7nt-K*fT|loeA48_DsyZz)>k|?CpEu*;-r}>IJZcku_mF&7|lkcC|Is3Qk~J zE=RT4q;t+bm;&(n*RTk$dD9-Buy7lSPUZMS^ukQ^8?K}aztu7zNB`Z_vIB*>?1ew&^wU>|<1>*!u%55U+k6pf^fTJHzMgO0$4wCn`JN z+ZmRCPSa^Ge;tQgPt>Tj>S_Tpb&K{5hrV3S(ds5COBzW%lTP1uj#3dnA*qxO$+qw0 zLT`!mHF8F~H2ixmJtNL`VD7@XbVfYWc)YVRQkqJg<-z!yl+j_SRCi!AYdL1OmV?Oh z1JLqdVkK#hDeUOt9*o&5!fw~om$aTUbsX-9N>DgD5OXSAHpvgSZ5_z4y3SEGnLFar zRg)aE55od;yi~X_Ug^pYRgU-%-H?B*`)X>d)eb;LS`?2TSs@+pE zp|^Rp>y_l60pj9x1)vU>7~pRyNfN%2NhzuZS5sN+MOp0e+G|b8p`>z zyM>p5wy~L7b}bg;d{Zc5F9%LseV)QjJe{cDy!17kgDbD1HpDnN+OfRTPR+ar@qR$Ux++Pd7l1h#Zj59*&A62UAc`C4i7Y!H%^ zeoSY*9!G>{$-VX(*pYm!NGIUaR%(Oj+J8Zl|M4|S@uT++#3KRr$=EpTvJTsE!c=m=LWA;)aPGI)_~O`HU+oYy8EL^n&5 z60mfdUBWbQsOj#U42E5yD3&G`+ZBG7wlq?|dbJEebfYBVV2cY(QXPyN0yBtJwWn9I zfyRw^;}gg&>A=!xLn}n7Z0Q`b<&vrFD=P;PLp8H2{ir*P*f-=DUg=h@Oma`h z?zcn}d(p%lRwlEcVLehnp@XSHDD;!JvdW}xk|}6xi6VA~Ozb&Q;}6JO?PGlHeSU)6 za;q^%Y~!#IY@^03JKbg^%s%MYzD&1y!rc2-|;;6^Zha(_6c-j+vLaF#$f?itY=mkTrO;9Q87e}axUyc zz%~LK+46YGYljO%Pj*mWy0Q=35*4!zDv3MQfjM5Pz+C#N%0b&8EDU|$?WlkY` zE$ka|zs-TT@^#K3+iDnTV^euo9=RhLBJGPSyLh!4_T;joWRP_1yb?B;wZlMhC*AiO zvLcdUeytkz2>DaYHVjkN$ha`|FM2o7EMixM?0&u{scMV8dS;HI-*o(ux6Z&?lI?oo~$VCeTljSSteNkZm9~v22{yI%d7(s3euV zs3wS+%U1N$#L7!z-YwoGaJqa^k} zN<2(q?o0WhA$;550OKb)V{m$pqfC!lydTDWG*Z*AcK&$dWHjFXi<+Zw4#g@5yTLB#QeTv&5yek9bzegzK|9k)fi{IaCGv? z5tmr6DI>(Q6%jZZn1~MXd=-wsKRe>KP1#c|%0)j8JXn!xNzj3X+|?egmQm}76O`En zXFD`}XY4D%yzg~SFsone9JPC)@a>ByJ2|qmb4Pr~C{h^X zm{$%`KEP}nmUCe4RkFETxk*`oAAuF`Ft>~Q;_jL!5z=-KmW z?Z_DvlNGLelW1*Zm%A$?FezrQ^o9ENVI6f{AExJu;-GESGDh_Q=KFAW%#{x}mS!=~ zWETUWgyvAQf*R%&Em~T&Y+Zl?N<7Lgp_vnaY=5=bq?O6EWq}6u z2usuKP_?kyr4C+qrkBS|s71|rKB2qc=NnhX4gx;we7{BpeKNMrce=B1_pI(l%+xX_te1 zHFXlAAe)RbBOjD}!)Y^-151*i1gFjLLe1AX6Y$*=v3>3?F=lhq69bepb(6FAwWhKa z(3x7`?AF-riPnBcE$YRpq^hGqxCyz3PrOm6nVA%1vWoa*ajO9zEBmMar)I^=fzOQyp6TB zo3br(L-AA+R5#z7`PpME_i|nNhB|j%=T&~$p@xb;w$UCg=p47TRsDZ)wZc;c;YOW5 zU5V;`JYBtBq^E1w_n?S$sP}YTz%y~RJrUjSpykk%Wp>P^Z*~sZ24)LgN-eruvGb;k zgZOkJS<%|Uqh3bcBNPfJ1S*b1haic))|mES06?t%lj@k-SX^g5bFGJRh41Th=cR89 zXnYx?l}zYhV>;E?8)hg?T3)WDvIoa~Qz*0L1h2dwsmC3UUa3d*gey%wy!O5bMFTE= zmEr?49i1h*QcGb6b+v@+s-cWrYI~=>_I^M{g^8lz4OyF0P%f$lxm{`Xs5#w>Ki)Ph z3)GBP;?an{J6Dp-qu#(}loLJxZKE=g1DndN23cBbni#aP{SU~4}@qmX5?DgDE01@_-CdrrE;tAbJsveFNCv>wD84Vs3OCqaqqHytBLMo!!gvi#fNz4p(G0(<~5~a3d})ezErz*w)%4 z63*J3>4RyPtG~sCM$2bo&Piu{n9AiIE#*q` zc65B(;y>)656m1ry{Hqm3QEDAN0g3|LO82n3}^-02tzdg-G)plFHDVabK=))p6#@P z!b)rlnb;G75Y1g4geMwFJH_s=J9nwo@LDZw2Ge%0$=^x_fR4e-g-z)Lk6huwOAzx; z`)Arq@~bw+n(W^XH|$ANQu$o>aA%riI|^g&aCgXxM4HG8q}Zj2z7!UD$uZk#EX;h% zopdwHk=Kk2}&wYHK?_d$balKWzF%XhR_sF8fraLZ1JO@ z0&3mpa&?3#1CP{=Y-9o@&Y+G1uDE-b&HDi_E&q|9ZySw`)VODu;W5855}2StS9k_5 z)AEt!jFCr@sU3<1?RounhC z#6O5-^eW;fx#NpxlgZOdyu%rp{;Wdl8}dm#P+!5Tke^r=4RZQGc|m?ky>~JptR{@f@&?y#jDZAOzsb5(qhD8+ZvabBer3FxjWCiCypmZ(3y$n7qxBFs8+7><-C+Y&O^P{O-n2Cqiw5TvE57@ zleVCeKF+CWTJp&Ay*ZVqN2(RIbI3OM60hJSr4Y+|q$a{w&{pfVBKPpx+}RW4D|t+d zo%EB<(FXH2LIYIlcv8p@%rCcWo-kjrNRHWtUklf_$+d_IsT5KobIiq8rpV<~F|W+8 zlEB#qlC=FjFspe1SFRkHCMqLI^TI3hhoMC{U}ND8dR|5@c)0}fI^5|*rPt->Y_DCk zdxm4tw#-ucR{@MYyKmX~={|>xs~A-yVpWX*Rus6&3a9Q0)7>uBegZ4`E6j(SjFezzXGZZ~2bhZV`N-XI|SM_0u29=lOzcF`1gSmymh73`DRG$~Y+AK1hKep! zFft8j!ZheWTvv-9ratq8wlL(S_yh8!<$c<%OV>txN0`1di zY%5_i#obvu-H0-qf-{lVK?Ys9eW95$@%}9FFVW%eupTyiNEi%Dpo*`WZt1#-Ms8-?mkhx7`^=klXc24S9U?hoABMpu zAm;l0HZw;ib%WBkIa`u`c;Wpp^VO_^!u#W>5z%*>RW{yEjqFwTdzIu0wfDO)R5{kB zGL#G=9GT{RD~2I+deNkMWvFu4No6P*r#igge$+hgLP@WARxXreHP6HA?Q0k3z+m@y zY9}Lp3^^8<1$;)lP_i8SPsNu$8iYsLI0$RdVibnU}S?FV8E%+h;?p2Ns+9^YH-^#u4+Biq<3 zAk&>%cNM{Smv=fg$H{)(_r!dkFt?6PZe<&N%`;29h)CmHYmeQE{?^OqdK>@r>p5)u z|GvtX1dT6jqvGgpav)~4BOM#saavBv{)LelQDV@y`s`1LTUL=3c@M9#FYVd^y5qQ0 z3X@!%7~9a{#)FJ%e~liMKwjtX)yZ-kUSxk}#+N=MAi2bn8KO2KnW2&G*~j!6_b&mR zF9+xUcYD0MttT|4HYjm3WPLH#NusF$7CqhBZt#Orhkcw^C^Blk3M=(&z9KbZ-)piz zx$Ns*_Im{mFS5VTc0IYg+}(3iIb>#;E{oB5q=sX9jj#QLxIa>!(RxtkarOl=l-)1F z&0$sEO!nBWGPEI}zjde+$Z0M`P3r^Doa6P-2s?4ufgfXG3L|7iF{il{EpvmJ_n5aB zZ^fBr5elq|rq4ihLV~fC^Ilgpvg}lMU1`^=>o?gbIWIB^ZSrd4>ku%ecX@wL@6?4k zYfmNG`e9~BGQ`}i3bgBylIH$W)XZa|GJho6`c2r9ac)>LEI8t53p=!JzKeZ(&22xK z`O2q`gd3%0FZMcL$%QZyj4lfoZ+jz{3ck||=$=xCtDRnC z_t|UetBT9C$rsA&M546|Rzfq%@HR*ISRi`8@_ zvY*yt&tGiB8CQ@`=-96Hwrwf~m7IP;V?Mm1zGenTh$FhhCnWE&aVZWyQ#pCddp~s~ z12Xg(OC#}e`lG}h;s1@|=QGhy=~HqfqZDl4+YRmC=_!$JIJ}(xIB}vWFHrj2XgE*I zOipB5-0hLe{Fa%YAoprXrrdCFN6l?!st9!0l|HP*vSjWBCYc=fc$5Pjy__I)( z;iffp?BJ#3yXkyuD1+e)36@M}J>0s~Q?>}4gxF$MyO}%8?$);6UOG~P22rT-BbHX} zfVXyr$lKWh%7faqGc_>Per50E+wjPO~g8rLR1Cha06(1kDaC-YqQ^v9VkYp<|vY7R|PK<)!o| zw0MLcNak59amzM59lN_eYjw_hdDjao|O$2t?PIJ}BJ zi>v5Hs$7J@AwJ)RR+n;kO$X3AG7LR2lQFsF0f$%87aLIN1&P%afXmF{AWZMAiGISY zD=o+kw#%leV4D|fk|Z%xNvuiYO1E0~!Y%>wEJmTrJ=5*Q+P>FsZ)6*ei9U1E%v4g@ zR=$u{Qu%(_%uijr%X6oZw^z~E;4|~EV7x?IHJG6ig03|JHPy^4%((1{^6zXXZ@i4Y zdKtTpHiZ(6m#JJfGHz7)wG!;yiTV22j*k|NM#@^6`Myh`l)Yc;&_21bP9(}UCLCT# zUl>7pnqfNoj-rel_iirs3CYQeZkyNxwtK@ z>L9(W+Lq1-qK`yOpQ0xMahJ6U0s<6gaqC)5l~)EJ)S@-42Ki?Z7YN1ipG&?+H6;e zA9FKqg`C@~Sn4CjlSz^2)EQSctpSD+yaeRRINy``h}>TBS-kvG8?1vQP|)fwA#|gBI%`Mk=Mp zzI5_n(NW<{(e}t>_AkWuJUCgks$`Yt!BfbY$bN?$)6oIB@N{$z*+yY;u6dWQ!7~Od zDUy^PJj)^508A?;5xcwx&sNC>iu_!7@GNaeA2$37$T4=NQqcr?wD8y&%C6(Lk3+o1 z{=(dXXpw>WS_=&iq4KC5M6Ykwf4a&L6gc-z^-A}BpVhmhK1Kzh@F3|pwT7jIZ9_2W zOwqpieSVhU^AsbrHtd-S=fa|g#1AbxMVu|fK1dafGvk8L+MpJuC=FWSs6A%X5KB*&@^-05 z>s~IezfX@Bq78Ku2sRogmM@bsf(Y;DYwU}s(pV?BCzI1O+O3^CVhJ*-dI03cD#PAv z$S&8Z{1CIHkcE|F)DZd#Y99ong+~oj#W63vKk{6iuriu%_@WOpi6 zBH2R|&YtYjqA*y+11OM3m}2-mrmZ+Ox%vce**{^z!@66mpUYVj?R+)3?U5Gkggn!7yf#j1 zB@oh$;Lvyz$Or4Lq(CJ`5#mg3*?pUke{VAW3G&6DVquCW9h_czKepY&qT-dNLefFw zx*0Bi?Iu5gZV~qfm~9-Uru$C%tPw*>srM3eHRbo=wL-}gdFubC?oGg{T)*()jFmE1 z6f)C*ZBO<#WQve^DDxDOc_va6LL@Rpr9nthrcg3Z88bB~Wfn@tA}ZhWZnkrN=l}2B z=ew@&y3YQtj)OLFzw3F{v!1o?buVZ$leRK!trB8~LE>rJJBWUml?HD-OIh_#7Nl*#9hR@TbKM85)YV zq1pyaa!3+hfFB9!&_KIp4K&ck1+7V7m4na!+bE0#XcRCtyrp3SG2+~0qNPC~N|>8W z-j*hDK!Xbd%mGb=OeEF-$b|XW|1q)VPj)86U|@BV!VH@d+rSJ3C7`SU(Yvugi4U4X z07QTPtGM%znEx$k*hq{BnwSS9g8q;5fPVtLc0`jlG;AnF5cys22Zu&#pa|W=AXW0; zj@0;5B5>`9CUKR7*Wrf{Lps((opKa7vL&Hr4UkIs@3hL{0h20YK;yk(JlFDCU3=L!Cy@ zczdW52@S<0Aa8&r(>wub$gma34an_2U1LXZvQh)9eCk|d$Q4|s5p$kv9T!5mI;K<_4VL!(Kl z212UiFs4RH0HQtwb6tiX8nZx9|c{OAwC#!c>~mh(j<8=Z_-LN zlJH9WC`hjw)GmnyXozwkN#+G;QvEoPn}YLXM99Rz8^~mWf;4#t-lQ?Z=EI0)22Isq zGlQEX(aeAj3DjH2?WA5ylaR;&!t3vYiX0F>UGv@0I1v;$wgFP|_T3l~T8WbI0{k18 ziLM)x zTIFQSm4Z@YQm`UK&^Cqh+W<63IhYbyh!$FPZ zKz2|PlDvVK(txjq1MPc2Ir_hfYhhtSZEGaf5h!TFI1Hk#CBPUOESUaCv^7vhf#z3` z8)#ykm$bYQkcfrF4GP>i(9^TA#=)H(Pblt_?;0mHbX}5^C^jG@fkZl(s|6&%fjTs3 zxE2R`lOPO$j|aUmIDj38{Liw=uzMP0((zCf02>B?+bGPgCb}{Sh*b)Xe`p3Mko%GV zl~K^dMsAkgAe91%El@>(?fZaN#ooXTPA+Kv<(dPR0Mf~1IEef&*d+VRB}ph_1O5Wk zaJ}Ko05b_o*xBUk%z&jaKsW;fds!!;j7{9!>jyS;mO+(5az3!31rAPZENtipuZ)cb zg>J}?0}d@g|D5Q@feY`xh!uWFH z5hekKmq>u@I&qatfNo@QG}$@xjR*~FZuq?o8=nz9Iq~2IU3=i@0(THHKDa?v3e=8Z zk1rqx@h?n`1vd`R$+Z!!U_s*IzZ>)Vr(lI>X)H+8z*5KIrLi%@NF3ZafZ96`h8T$h z0aEmT#;*Mn=rwXJ$w(Yt8=DAO0^B(9pn^(-ECCJY`R|7E{1NlN1r5vZ*Z@tuX|CIu zti3l$L&M8sL&Ip$AaR2O@i*u;2g7MVuPVsk;ea50j^-KyW@)Eo@Hk#1i15Kt` zOQT?*Ofz9p4f&=~pkNdPWpJShyfF3#XyQE+F!Qj0drHp76*#N#pywPGG^{csadi{j z7;$xzw;PiH1IFM%!-iu-O9O%jkd)=Y00IE$697t$zh=67YSi((5eZff_ZT3 zhcw6+5u|W%WPC`IB-@fu#$JcKam@lXU@)YGyl+{gF~d?jz~o|rnI(YF#(>l}8eqnP zKos0Np!6BJT`h?bC<$^0ygK&AhF;H=0o*{O4_vRwI#~vsH>tY@yhJw9)En90wdGCD z$zYO;n}k9(3MylPEZKT6h5X7hAkPJNsa%Im+|xwZWWJ|K z1)-ph4+k1tGVo9dlW1$e*a8FzGB-78DI7qz`xmH*0T~98Sp{yK+ygSwildV7df5<( z8V~MQ5O6Gb4`ir;I3bA2fDRNO?vdNMwU+sZrhr2ATi9y{yka(_YX$XhLb#y`4^ZEL zBq(o@J8qJ5S|stXi84ro5}KR`U9SM!1F)GOJv%5n2y#aMoq))n5-5awwKyrw6p+$~ z+1n6#2^6tF1P*2{0p1fxl>ssafD9Gt|GSY>e}cT0G9rx`wv34+48vU8)j<1aH8mteiZ+8JCZf#fc*sTk<=0}Gr`>p_CqQ~#aTz@MzV7J!SB7|0_Duab=d zxe^pqlLyWW>b?g=<24!%C~uKpePewulce3iOJoxUaqHL_7aVHO-qz;OX7 zB#`fige5?e5cnMIaS5_xp{x@VfVD|ZDVKy-$_8U>3H^*XFuWO>9!GR%kp1!K|4iuX zPwtFpZ$K^wgZ|*IT<}uaMA(qMF%a1r3$c)(IGoJwP5Q10x?W%oFBG68AOYP9a%++x zH~};>!Zl0+yvJe4m70Jed49o0W)Ns`sHg)LIW(~xI5r@K4>>jRUF0Z|jh*nC*(gA* zf@Hx7(J06Zqk%_5g3()K9gU($Md0wF*&sZ@OMs*r!I?pN;gI$f!R};hXA-pzj5xet zHXw##BtiCcEf$5O;L)IvCjrHxe$cnIVfe*;sKL=9WJL_%3nsKWtDn}F55zM;uDYXTsXia~*?VVPu*X#r!9 za8zMHl@m;L0-bMUeNsWFp?WP0s|uiojl_timH=c?C>S9~M}vYVlnMrN%4EC1Ney8F zxM71aXm|&K3JpSTJY;Pm8-;u`gCwT=0>to1GBAw@3#Cs8i9tx_Y%K>&&Y>7-$nZHb zs9l$k2n358ntlqTc9Gy@C+o#cVr(@=99}bVBO26nNFCAO;DxzCV>XJ^kQO>bo zH4;b0wg!DWXqdho02?+Wg9hFL=mhJEgJLZPBosg%5eGO||Ka!&5<45PltEWDOfD3l zh7HJws3Dp+NFJ_(hW0t?znfL_C(uMf8EC!$wuWEgut6EPXn=zB+D?XSjRguqNFekF z85@HnyZa?!XXD`um%z}lK^X=Z7ztQ8KpP$0IR3IUM9B~*zcj*Hf%;$FFX1(`F(5Yt z&3S<0Z!}1Sg3*D5+7>y--$;@HIJ}59ad|_wMuPesad{K?H)LyUk_#MONqYnInkU=9 z47oB%vb#~QxiXS5IJ}fLw3b0$5JaCK^$V5+Xr(8_;!tUWOrufIWC}Z*2(+WX#$-T_ z6?kqCa3euG8psT8KqljKD8LLmW(;744akU?iDf$?W(jB>mju~eI@apb|H9C);TUmk zgWQQ25;V(ifCl2rSn?xO5%x2{jFo~7#^ANI(V!;_v}_PUP^g`Q*s+5H3Z>-AwIQLq z3)VJlB!(iWoj_a*z?Xv*u?Q^DWySyC^)O31`jll2qWt_l*EWR02wx0MuaQ@d^Q^DuU*Fs z^*WFtv1%JH(03Bd-} zEfj2)5MEfD2pfu1a6sRS2pe*7;E9nN?Dee;W(tBDCg6nqnNSAo4I7{l&us`Obk`)F z+vLrwB1y)8*VaY@Sx3NmUMp;Y%=K!GKH@p_c;(>-rD`PB5AJGAL~X(PLrS^1zmc4bRY^w*doG z+@KT+25bbV?G%_Bd`{;1G87;~qhPe@Ku0{R#5UBF1~CUAR|aLl!4fAh!a=lxBD+H7 zddcHo(U2N#4IgedSWn841kvDpS> zkS+s<-(Q%)qri~gS-p-KRvQ99h7HSzkR^c1BnCVkqM4zq2N|ZzHu5MW4Go_$TVLBy zLoo>N{z45lI!GFl+u|lBQb=S8;dQo&$s_1;1&LN+p?SxU-U~LLU!vR4Kstn2b%n} z$q4mYfSH7l5k?%e9l;rTeQ`?wJ}4ffMTw}PlmcjIA-BV7mq7Rd|SJG)XcYyrMtQx&||gz*Pebdx0iN0;VjXFAH!p$g`P)gxnxl+^~@t z5$}3XLc~kHF$rnBuxYV%yif!JWkVr~ETQ0mga#Oq=T;_-7&ZtaBK}_sLu@MYi#iFL zm^5D4_zN1)goc`vh+7y;{gnixO`sk;GTp1z#lL_I z8+{QWLnS*fcMh~LtXWwC^eLgqwq!@r&hVoB8=(JTXENT*r0oo9a4^9a(5(R_SV+KS z-C=`)P0*-kNMezUJvND)5WHGHkopHz*)>Tf!rBJd*V-7!cx^-MS3u~8D_Frx^+Tuu zVHC)sLW4Mvz+2)_FVw$*83;+@LG}+CWDW{-V6{Wi^<$kR=cYSwOP(-O&1mYB;d9Xn3uDkjTeEX)vO~AgIIx zJ!Gf^PqxzDB=j?ZrD0>S4aDmS1AtgU5-^G&rX;y03`j$UO@$F56SDkRZT1V#vml^WP005S{ckYCXTsJkO&mf&v@{uy z1_SI*u)N_{H+&>U#0*r&uz)uO7=~*?ir~gUekoq4ZX+Ru1ykx_^lvfZ@EZM~r4G_y zB5=Z>GAvQXg^Y70P}oW0Y6WaZVmm`l42J?b^#D82IbNfd04+rc6nPRWAO{9J*BTfa zHV}ghP4L`+EC!eqf30m$cLyEQWS8q*U)}KY%K>QENQ}6;H_|2Rj+?C0CI4c5!^`w< zn3{NvUUy?;eT@d547BFJI&Z*vgW1)94Tu(p$gb;x^+1UX4=9A>eVv2^Vc{{uhGQF; zL314h!GJ(_e*lTLKBMaBt--PixXyLqX5wp z4{@3asX!oW0cf9q_Da_Ck|6Od5{ERrF#iVVbteWOgNFdId&xK#2&fep92g1)m-dC% z*0l0c=v68a5z>B z4jhuVMuXln9C%OO4jgP~Nhu7h5(1F#fQ7y0#fTH|h+CShC*YBGW3aK=hN+Q|`Y~vg z1`??lNMI3AV#UdCst%#T1GY2?85?+kej;WpxNL!rJ+Qn1#Rh05U_rP>e%;|j$YACM z+;~UOE(sr$0Ry9;BY8b>M4Ti<#ykWlZ3H4RxPS~&^M%>bD2RdpXq3eA4j7>XC^n#u z2`rC6l4lnwRNRq5!CkkI@Iw6?pdlSeu&jZ8B2<|NmIqTFkz|+nhf?6c(pan%7T(gZ z#Y@!s@`l=^#X;moT;7nbIau~&m-r{ny(3lc09o#Tv%uGrX6wtFypv{Nfs-g;BH@+# zi3?l;1CnHT!f-Hg=z0lMalw!VOL+*v4yJD$r;+JRrrAH^rSq z%P%hs`jX|)%nFX6h zfE3g=E**MEG#%kF=*h~`*}2-fop7YXKr_Yv`U;@nLIx<$d^+wI>f6h0w5G{}}vI2tx;3?^9l;bkM9|=(_mFKXiY5W$kt^_12y-Shda$HvfFeK&KBpsXshsaJB#Y+La(aXE6QZ|N4B_ zpBwb*+GzLxwF)O4D0(Lk-(Qe~s{Ljzd z_*>%7{>Sh4|L*lQG~oY#^A$W8z;Xrux*p;WPGbG_b@LN^>BjnjR`kXv|J)Kv4ku6A zdD^+!*a5eS#Gw(^o}O-gKJMUS?Aph+Zho%jf4QT-5C7QMD@RrIjZ9PzD!aJYxq_`? z=P8Or{vRt1GT*nnx2LAW=zx|&tK_aYeY#eL}&kcMBIQ85So_1c~yTsSuqY*aFb|<~W4zGWXT>o4J z;qBmJ=jC88b{Kmk872Qsoih>R^Q4~n}Nf79~D09RSXKQ;eaJbVU6ufKz zazOG0A0M`MSG9AnKehfT6d^#bLElP;So6RNuJ+D$V0R((z3p6%gVD5f2u%ksFL0U? zA_Jg$jiW;tt^X@v5;yoq!2{K|^WKNBM%W_k5cUXXge$@m;f3&Wx3;m{PXKVl!PyQC zR?$DBnf1R}-&EkMyzM-p-`(3z$qt<1cHr0e`;mPZp<(B0?|ll4@B#`Jva(*@o_5wQ zbf<6JEZcmV)v!V$28oW4w5lp_c4|N$R@`$wbXUN(a=+~j;i4AlAjin`u(rG zy0<@G*x1+Ne z`Nq}O-XU%dPIlxc(S_vGt9dW(yRSyzTYNq_U)s*vrjX3A?fc4`vekasmj`GMy}Xy8 zmK(37x!iuK;GOL^+k!W?-6I#9l5=u%4&_ScSZW|O_NSpDa;s(Q`os|;o1`ym*C5Rz zURM8_u`-kPnd&*De_5Yf^KA3`*7xo2YeTwavVLf7OWlRlRBYwft1{!Ea-7+}wD%|F zxmygG)2Zj&18NqECXCTDyU{0g&=PIqsU9CrUTIU{=+$_b@>b)-$%fWKbkHs3)}fay z?Ez<#P9Cq$xoTY5zI^ukqu$LY{aR&lD>YxF3s*5Kh{C76K_zFqeG{j`i{Cr@6pt3a zD$Xrlbmo31JE%P>J!|=?vWaonY3ay0n#!d97nSXmEvwP1e$st0Zo);TZY8h2M%-+8 z)Y@*l824e(H)vNLrTzJbRoYBE`*s=7?V;wnAX{xyjk~~2UAg7svAYr7Qyj0xdIJdazxRw`GwoJRV8shZy)W+hK zZRvY8eB*wN_|FOXCkJZC@>lW)@dXJs?mu|x_DLN@M+WQ_6Rq4VU!9fvwLKmOqMPq+ zPl%kn)2>^Rz0&ik$FIk2$0yq<>KiAeCGnkFkEKPu-VGM@H?=kO@2}badVlBsZnd#{ zp3Qy-JRN-}y(fM4c<=FjHn#9~;!M`98|N~zIsya44qdT0`Z&F$yKJJiXxo%R)VP(@ zrCr_oy@f=xUKPbibonf74G5N1x6E?uzSU{j)!!}GS=l+x}GR0JX678Dscbp(TGCD-pgkENrU{`A_Pk4U7zi)i&#Nq^k)pDKIX3Sq57hd zmofTA#>M7lUcvM7Jgp3FkC8pQI%$^N^$+ETv|O=_XW`@&S=jkQi0jTSPRW(K5|x>G z$-j13gjN(~D&61z0`cmU&&=7oR@5A~VyGxesQreIXL9a56S_iYPWjnQAk(*tg4$v>GP2Vk=|D+cyy*6rj*EKvvq!UqO-+yAa}o)wOKcvhKKJp1>904^ zO_zGQ5)86277cYxp7&A`Fq)i)FF)1^QR)bSBw%QFdDV*ZnJW zj;Ua;&s`REJ`tezOyiEuMS-$L=b3prsiNzj{6%E`bAp^JXh^NyH%a+eMg03u2`nI$&7kg4rKA!Jr zd>eQDmL+u-6Yb$+6=@Pe%)`%>z4^}-s_qq5lfK5>?izDwvi!Q|{Qc=jdwmm>HsjU) z;;LRMOL4i&eNv0pp7}{wIdPdz<}M!NK9oGQCBWva#^!xrxnc%CjwY4+1`TWb(SCkJ zUod2railqv{au~#{X!}qw)Q;wwpXH~ns-ea4^uSWjQ5H(dy*eTqaK;`(Hs*YRFNtC zRrSi}J8A5q?acN~tmu#mLB7Z{81dE3IXgIqzTn?0_t_poFfFQgr$oC;QuvR#^mpxH zS34!@q-`&L++o2xEP}nHzJ#^Jnu2yfw#HE-UGVpjLDSOa!lt&x&zs*bhh~*M)A+P6 zo>v&Hl9JJSLULu+*8hRbvA6>Uf!e3WMQoUe z!~=FaPU#yVclN*F@?w+U-In2DaRVW2-t}zkC-VDv*tB9rx`nKxY*57eY3d51Z}#6dMy& z?`UIzJ!9Pcm|J8_;rXwcUg?pWxre7coH`>at8+)HewlYLR~cW`{w~y2JjUV9Sh?-h zuqAcd(#_&={{79Tm1Pbx9t#zWJt)S#G&B*`U0H_ZXlM!(b0ar^hoZq6X(huMD+NHnA zQfrffy?JO!)aheOuD*UsRuK_%SDa+g&H;ViVdb4&AG=h}_w)!$-?s?deq?zjt(z#sXcJy&S;f}4z^g7(+-XJ4ib$uBL7(F~bK&r7v)6N`X_@&JM%Ip~ zB0X2?H_x}c`sVJX7;8k+YUuemW~;{WN`&Hb+d7p?B8h3SySE?n_KoJ#Ov%vO;w8|0 z!+BhreSSeX%P8VHZhR7>qq${Xea>6T{CvJyH;1CTwg+Z8Y@Gi#A};3j(EB2zuNgyJ zRf{#aEiGkpbREeGr~DsgDy4T%(rw@QL%8Sp39~Egj;OS=m)e=t4;#C_Tas`nmDznF zsd~WfWaQ}H9wenmh8y;h(y{Npyulb`W;Y}3InG^ha#*l-=h8Jk z4Ayf2a@s~R@=__aXH&XO>>k-%+#%cNrN@qDqttnKn(WoiE0dG*>Ju=gZarf=9iyFH&)qR;+2m~~ zxtIDyoLMNfnUj#lapRKQL4C}FHmuUoi*jW*%GkbTDjs0i5pX2n$W#3@eF5Rk-WU09 z9uX9^EA z`0fw*U^-;nwvy{^wNtNWb_q})by%Kt{tA47HV*UdHj+H_|9=^0&hww-I4U^G$``)*p+ za%*dZmwG?qQ?L9Rvu<|9J60xp!(X$ROay6oUOFnKv=0&AR8TN9`gJAZdnC>Aw^{p_ z&NJ;|e=x88z~6HCO@+W!G-b!#z;8+0iXJhDR#1*>efBm<>pa!WeNUrEvs!A;LTK@& z*yUif_7z$lGmh%PNkl%~>EOKHuVWlz>OWMz-KaeNLwT6S_g+O|?&Slo)df%OwrzSl zK9ZBLud8H#fYrSx^ONNvA?ib{Oo1)>43*rAr&cN+Pk2k@wdd4P z13kHmc;|YG)v%S6cJVJ?Q&`)|4g5mJUwo|F85jOG9ZA8Z*E9;ow6KcQ7TR$^fRTr)MM}O~X?X3^zZer%kh}+K> z^ML<=v}$RTRYjQGB2Q^qOl|U5OJBn5Xj~}gOW%gp*L-}sM>#rBQejLF_IxsF_$7Xw z{ca9lBJJ4)di@@H?xYWUR+sRspn3QFmHfQy zTi;8isf^3itKk-fSyp@L4^Jhts&v!lnwY;hy|kP=XYX7fQu^e|CSLCQp|aHD zJa|`1-V&Ck6beR%ZQH6uSa)>%#Ls(gqdt~#vdrd*yX*)0AD8>9_AT2QnwpCqGr5{1 zbnIS}-rKW%Ced;O%JXMlc1xIhJ3ETdQ?GpJW8lP31x!p%R6k7qpv7% z!$p<4*I0=+ule3dYRz27+}|_SDG_%gYwK53N+cquBK+Zi-qNEd4SP=%jb5Poko&~P zz*sF+=>gM2&$qc|@-+(fu20`~%NbnbJ$7Kkw%Ee;3x~@Tw^jR1*;x;{8$K*^TUdUwy(nNpt; z-pl+m@rA$DPwgE_LmqY~d(I9`I^d^Rj~H-IAk=gYg!0DCHqEf#%tH6%hs0i?9zhk! zoC{gWF`pXr;&lC)J^S11`+H$Fo!ydGgSP&(eZM<%ap1R}E2{n%gMCNJ+&z(drrl=~ z`dPmoNaGA=ocbk5lSu1hp%AXD){wxkbNA*~HI6^S?@;v#Fw75s#1~xpFj-t5w3{Zz zvr)t#ROf&_WyU>Y5Aj4*OQjIACbh@HV@)9nLhtoTW2aC2rW9kwH-<&A+&6Hr6Mh)X zeq&oaGNz{7(B@~RQ&af6WS!5&Dx4m6<_s4fTc~+WT?9Bb7t_UiB5uJNNOgz%G z)mb|#6ztXYN6sBx2tLJJ{W*fhQW&HB;_Z{#>}&QmenUIyDz-T+F_>)~xPD>g*^;#1 z3(I%BvffcoJtr0C=_ku5*cO&J4Bew7YZ7;QxSg1pUb49_dwjgoF}}s*i97cr(b|Fb z!b$~>(%7E@KfmVdx2EtP{A`hh7svhVkosQk5-v)mTHzB=t1c%bO!GecvF^&P*dQN# zE%J)K&CDJKBTJ>ex@1)?L3=ydtJ_V2e5b>u8?_$A#3b4uL1mg6`JTxxI4||yA;;C~ z;eqDV2QLCMXn8Y_W>|IcpBSx9C`o?wBIOCQJY7Z1#5eyRCI#}D@na+VMo|}|24$;k z=@Ru64!pV)NJAwzFlK|gc|1E~;YH&qgYjXJq@*E>#)85`NlDvct>d>}jg5Y0XQz^@ z^8PVy(Ij&$P3G31Z16ntrid^iytzHDX~a=Ejk5g5E(Vud@cts+py2%ptFy6D zdSa(c&rEG;8SLZyBJ`U@tKqJeMSQfLPGsP7xz@AHGpAPszxO_VD;6ko<|pp_164Z_ zzRC+L6LQGP?dFHJZP#U{4jz0ebWM(uO+Mq*^bS<8>8?)ErKX0FeUT!9*tAjF#w!;& zTUw(Nj0X;(BJnvfqB+8se}vK0w;D3#Hqf6Iw?9bVqChFHys2P+u@I-g{L;k|#Y2I< z@n!9iX=i=PR)%FS2HD!YI=1ST?Z#d7p|X1JOk;9Q%7va^T}unp7FNzif%|suwfx{Y zaa8z*-iw;WOEevs-@EMJWG~O%i{Qz(bf@Onu{WU@7r(i0P|L)7(%Wxw;C4`n^fnEf z&ze=?5-*GP9Y+@NRRv{!XsggsTUB_*(B>Y)B$EDwXJ`LIEwNTSRh_qp(e$1@ODZAM zW6XUlfzrtr)Y4CkG#T7~+oKn2c-69w^6Sju8VN=%+3hYB7cbB=eF~y%@5C-eu&`gw z_(5GImxcBkLSHb7box0KJlaW7gI7CxgzoO=z;cV+&dD=uj6RT*b;P!0q#6EFHpG%vL-&EDDU|2MwI)A`C zk(WRDrq5ZAZwc9tL$aOrJG}9p?ej?P8LlqNHXrmc^fcnwf1XpbdJC$>b;hY0`Sm_V z=F?W|o`hY#g@jb?rd5}X(l!Y*Z!txZ{*-_-!<+z9~~W4@`jI_ zwy1;mQQCl%HUGd3<}IQ}`Wb8M`}9sTJ*!c6=k-3XQd&Q5dBgY{lH%v4cU}S|7ffq= zy<%PjB8E6`vzo==+xF58qFxKtb%=farBarjk@>W>>nna>IzVKCdy1hn>^uGd?XHQ5 zJ3|zz%=H+2#u)E)W41vb{7LHIEKZWs=W2m)Wm*BxZHVfcaEUE z>@U}mUA|5RI_~_Uk!-^Elp^AbGVbKynbaM}+hx?wN_8Q7xH~2fXd3QUK-^4VuA(&k z9d2W7Lvg|5Q7A3G(s4h%+14wM z+u!4-*eH%DW#<3jFP7sMX$jqabyIi^!_z`pmGojRKAZ*q`)S+zgWfo=Nxk{ngCl2? z?}%z#j=KFstnSQLItEkj{f`_bC2sBwXkfUo&%x7VyI@oMLvI^}b1W2->a0{H)Mq%O zB1ObrYut7#ymP-a$@vmGj8;iy^Vc$-fZr{`Y~0ukOkw)?Y7D~wjB_gTxqQ3+4%GJH+2WOfp85H<$X7>0VFzS6$t#PkIWl`-%ovD`x+a2Z9dkt?6sg~QPNh)%l^aHOVhki+aHzB|4?z4`ZkjA z=~=qRqjT2Iwzu7uE$FV5OdV-I`0bJZ+z%_(E$J>6eUd)enr1t99MHOS^p!2!$-Lz+ zGX4yan>s!m>@s@L(t_wfhlTxEK2gA1!55vc+p!xcPe1;#$R)d%x>RpGREHub%Z|z4 z;ipXB6_2RdTJh+H6NV2x@2Xy@2p+tH{`RR|JTtZC>Z5HhhMaHD&(FV2Wy!shMqO{o z=kQ(VZmNX%G%t%qWi?Mu-ceJFBWYV)W@lpbcle{PAbH@7yMCUpiy89Mfe(ydnrg04~B_wkj-8b7l+AV6Ijn0kxxLNTq zL&0zSiIFo#DHZn%STdP&ZBqTby_;6&!TI1#&MMyC`H0bp6!xLU zuX2<5r#MVJ+HaZ3m`ev zk@ony<4yFB+E@BsEM5Xfvk$0b>^N}mK$v26OOwJ4_p6V5o7yz*)}*J`)_zXE9(4)( zPVqaRyckn~s@La8!{K|P{J||zNzvAmWh;i~oa`xn)7;Ps$PuSnZ zbCSJsb4$L`Pu1teGLf31Nyq%+g3I^s!Wc&UQg--y0`c0E^CnZ^CE%D+&M z*i$I|w1Y8!uW~Jx=b((RamHyZWmnKRW_OoEvOAZ6gU(@9RIG#Dho`yh_Ep-oxm7ZU zH{HA_yS%V@uK4A^-fb4oFh|m`lRf@n(NS5S6ho6K+b^454xVQJv^m-1;GNA2=MOS% z3Q*8?r7^4aFSw1&7QqLbiMz2j_WRs^VxILXVdC2Ff=kb2=QP|Owun6*me1q*w4}}! z6vMhYfs*@17CRCbEO0aay*vy25zPrzv$%Aa)COj;;R=Wk7AJf&X{XC zoBRdGZk*xhrt30ZddIT;-Z|^ElaymzEcl7D-ETkR{Q9?u_wMETy{AHUX13x=Y+FG1 z8P*DVD=}+_h2Nc}(Z&0CtkR+nA6%09^=0?elkN-qS3CwZ#(fm22EN;H52&r=eZuE? zU&YWU2EUxuUUavw<$vy*#jBZ=j@vb^A(Z*#Nl5`0?=!#tcB*#X&%WpfV|`}+%+pE_ z?Bh9}*k75REYH#OP>mK#qvSUFE*UFxJ}_WM)ndotM2qvoItn3f5&Z+vx#O~7uG9lb zUM+aPb{eryNwqdJ_&t+5$FsIAyX>@Lb6-Td2IvH;ZuX>gr=yqkX8gX2%Pc9P-!#H1 z!>_7_t@d-PGKz1tm8MLla#7o|gY`B~&^21A@rdU3=d>Qo!BHMjJE@WR6UMs@k_@um z+|826GrV1|lkoksDMg>)Tdsj~A{4GdbS81{Dz~AZ_G%gjRc- zy$Mlscb}LOrFW`zsXo_mK7+xU?!tlj>*>SihtH?u@sISX7*Fx!pVs9pWggyiYy57` zGRwoz&09K@uTfc-TFEelGXIWf9uS`Sb;w;dSa#vQB$KK}q|g#}#|5d`xUjEo zd89NDR89F?eTKik<1yxEvZZLf*x?Y&x{j$2171mqC@v2FG(5;JBODRP^@F2* z;%&pL>8tOT`xFi+ZF`Y(QNQsOUiI`72il=G@&ZvGeo@KvRlm5#`hah{CZ0*DzFt}7 zbsZMd(}Tg(y;9=+++oJXx_2)t>t1H_H$8fW;oHw5Zr(V5r1CyvQ=OqHcGKi3M$7(0 zis-hc;9YFSd#-ZujjINqC=I2}7Mj2DQ2ZRzJ$wE(N`7X+Z$07Xc`2Fu>595yYO?!u zPe)(B)Iojr)}_#s#`Qb*vhJ%M&XG67Bf?z8wNRJFQ1!IwjwEu$$n|1NOo`+f zmVtvI1vZp#%|BLO-xbU0wuyezgFdaLce`g^ex0lvpcF+`%(+p#W$k-@LSz%wkGP(} z;m)ts&PM~21hl9rS*(~SE?yD2zR7CWh(*Y>W!SdI#uSf>^SVR$%?+331H>q_et+W} zHV-*ZS4wq=UMa~+#`C%)OD?@&+~yreX;8kK#Ink&s@_(y9@Ob*sBx&`jMydh0?~w^ zNb=oQ^|8jKbq?vI5H$Gk*FCD>pD)f>>0-Sq@+xlMxwiRSs5UZK<~N-c4N}y)_e7OS zl!A4?aS@M5lJ_G0YX7;4PA{&&!_z}|tBlwKy~za>_fvwu@_JW_M(3*2sLrZ5lv#uCbhh(TJO0mRZ_%L_VU zd(K3b)SL$vV5ymd6Mx$Q@6ZNcg3k)7jn5-HFj^4SugPV6?<#zmPS)J<_Dut5c@oqoTJDnD~vrLsuQ3w?w z2cbhHHMu8^dQGSD4ir14e!>5c%h{H5OMCXFo=k>)b3YO%!lK@gn$P-H?Td)-PL6tQ zZF7{*d{4jsb~iCM#LHyEnukN!IjO9+v@vm)k3~mM@aA8=&np!{kw-NW;yEL^wDqUY zvsd1~y~9E2<$acA$~l{5;cmI>gJ^!M1~zm>+H88;iiEn*RYbc4&gER*kKQrE>$55P zC)=+zwFcUZm7&?%%p~=Z`iBbLPmZ>ZrA(n$QS*x_t4OEjoHzEO*Pr-;%6?}~v$0R1 zMf-)ql2%z6QyFOo-^nj6g_WuF!dH={Q&R7_ip7fg-fuBYcd49;of3TixtP26oa0J@ z-1SwlpshhPLBeujx7y_Dpe9wG|d7IJOG`0NY z;?$$Jzv{pGl~r!bq&vBlliF?T@hz{nGHks-L#L2+Tg6?^U0M9VX@S#1OYc55eEgX9 zWVPyC;V-c>+-LI3>ttIhKK_32Yw>#}et^N3A-eW6!#2(&$sFIe0|S!-qf8@ApJKoA zcCkvAhFfZ{`-1Ow@9jR(p3z~7 z;>fM0e$|*EU4|XXE4xPt|g|G#u}N5&$7+(7Jj0CUO!R4TtBP(c6V&I zU6yXv8LcZf(xl40m)j>^YH=P)9P1W2bjw2FaXPL`dg50Rdg{nZ2Zi@OE^T`)0mtTv zBhR9~rf;6$UFJKGU5;PuUOMbtQW^Ck>Z<5nYi(f3cHsMw3>X&JVr)QK!&xN~)*!3SUqkl-*N7Ysdap>u0}P3@u`|?+6vzCQZlBI0tzLSFf@ z$;X!%T(G+Jj!xsmUCBb~O{#VZ7~PH=mJA;paxLtarcbc;GY@dx9h+;2q?OtJ9c?rgTVH$AGn;VzUH=EBA$K9w=@^#|s~{UNM6 zUvtR)Z(`D=ZxrAD#(a6$aG`+ifzd(weYUHu<>W+7Hv<3zh1=RN0p{Bw$Ba4+S3 zl`#>4d^P1)7HB@QOWI9N+qI{0QA~GRp6xj|Ej7>hSh{$x1YO^t;k5$QW7o8g@0^sr zHYsqSEI&f5_T__1g+&Uf?kZoozM>IJhR1flHn=@~;%MhKF_F9Kv+)PgH8{S1af^vb z$44WD`}S(f<7xEUxP*gp-JYLR8ct%svE;tGx}@<`;9wff3jU?S>3K|-fOY1rQ@`ic z#-5(ERM53DsNk-Tn|4e2Y`CX)cl(^Bh6&r}VXuVkBAVx#xPsx?Qnyo?enufCg{;K(nUFrdxm(SW?wrsQV zvl*pn=sJG3_-9)0N4{?l2Tz=f|FNy`RvyXCbJ~%OHjNz)?=6R+MwUN?XuV z?U+!X(iNrprFyn|27;(^iaq|66U-$qSDA))k8v<7Dj&gNj%7c=B8+uJ_GkT|%E0uG z;@?ExtV4XLTcX35O8^zdP0d@^1c|8NORtD~?w`Nf z8*=dc8IzO(rnBxJ2k(fexNm;OIyOA!NA143Z>dQITfaEh>1|n*bNyqpM!r(&RX*#d zDL2|A@Z9l<`?gCJ6dAp5DH3EJ4y~W}8KhF~z3aK_ooB8Yp3QS0Z{J|)yDr+jVOv9! zOH1$c80p%b;HBZ>cK?h#{MMTno%c}PV8C^Aa1`f+Xc3H#y zQPEneG0k+x?WCn^#F*pGb4TAFzfiX2yWme2Oow>H7(z;AX8L)^xf=P@pzwq9<{DH^ z^JO!l)!(UHtsfk@ z?-U-#mN;+b%20B#R`x@*b4@ubg4;-A`HRoW7*{R>o}=M-hC^cViPLAB?3#4%y+90Y zsa+M~d6p+2r=8wp^YB0sEo(3H>w!jShGv8BYnIQHHbZY5m`tA4 zx<7ngSoq7oR1+X%c;WbjhJg?$Cgy52qa8Xmrlc2+ydQY-qY znXTG&`v?ECvc?woU2K+hnT?Du-KOO}o@AHcKU>A%7N?C8Oq`&Le)`moEpBy1rS;&U zTDGxHy(fZAAn{Ce)9ZX5P{^+RPlI8>VWA_rw`X=Vbe`cqC zJegVX@P6+?gdb;rBGXQGEkRZFGsfr4KZ@qy9*RDViF8?U%R0Wl<@k$&Slt(~YI-7% z(x>}YQDv5ua~GBI{S57yE~gO#9Lcw5_Disz zb!KCjZwtM#{oc_TB=X7d_gOv$Jyq|#Shtv?yw5y-ho9Lx(~EsDxLT-=9t+$4hCfg^ z3m^Y&ci{XWlXoktY(wLGW01ACKwW!Zvua{DCJsmzs=}O@jDqUCJSpIVAR>>nDZ|TC} z;{%@%YBRit#)3oqvww}1(B?+F^G09TmRq zrM^vb18*)2_vt>ZyQ;p^WQcma{xQoI#KI+@`<*bZ9 zBkjYf!NBJ~r5f5A5%P~$6J+r(j6JR)7B9{_`nD)7^H8oD>Q!Gn!+5MIN9Fix%%rFi zx9y%vxs|pF1#1Umf9vytIw^v>_MhzXnU@x^B+*$zR(la5gG#p?rHfb;<3_yvqA|hJqrE2;b$e zy*r}5` zL+3*50|K;e?fO=lFCU|N-9}Qo``XPWItdxpQ>m}MeH+P2c`1;U(z!GvGxEfR8Hqg_2 zG4tT_=8rY3E+aEr;u4iE)|G8uLGdjvoqBr1zBXbq-(61a!lmb$J4Pnx94*7t8$RBO zaOd`ExVSj|&{+KrU&AL5{tL>7Vml9%JSl2sjP8;b>gJo!O&TwlBg|NtS$MW zkoOWLHyQNh!$_Syt+sdgAm4Mgf+tr#t7!31y1G5W>W0yzWY4Cg&Atbfb1B;70hf+v zR9bGOJ%{#vadyall-W#gd=*pFY{PV@y?V(!QmkMfa-wru&U5uOQ&GjsB^;#|o0hhK zV{6P9(|NH2yxJNb4`U16hjxic=g+&cm&Fw}9+@$X4c~u2-Qd`czDSvihs+j@JL=nV zznGs2ksLN_Jbh8#PG@3aq;AOStAx+ug05iwTn=CH$78pX#+toU*=D8V-m@?58dS?T z>FAvz64BVXM@>O_VV6hjri3!L1qFnJ7@y=(mH@twoq&|}vhCGm7(L)YAdh7yKi6$b z`ttoy=u>JT0oD7Q8JY4;#Ycw^*6gpmzmGfk;3=LZX4l5;qwnVQ3PcNd_vvsB3)!5I zM6{0mo;TYwB6RoFVYNr8G$Bd*IB+zN6s^2BPtu&|Ll?~UJo){RXG zQ%Ea96_vS3d6N3Hy!{a4Q`(ZQi-DVNy?ZPm&tq<_R2na@p^BLAF-#Fpkv>lKsoT6G zbE0vwnl&qoP0OdpSMPG7n(p4;=Q&Oo**GT&U1rJ36c$RfHTcBQGf637`QS*Q;IWK@ zUF^ztQTrpQ&xh_@68;cu$Sy=4;IwRcYb600eY4=TH z$CrB)bq-&*7)2;#$YdwwYVM0(;PZ=Oqz&UOUDWcr!!lE4%O600gYu0{y^y)_kGjy! zjoQxM_UUt}2cMRFE9gDfA;B!xOX<><)^U+rqWCOd;xvDn>V)~eCeeX+kmPcGmCw+*F0B%Lwc?!Fyo z@0R4}PmeX|Tcz3Y*L^`TCox{QCm(O(UB_gYv@bf>S=H$d?HR{#sorNX&Fw>4aZMh6 z8n(gLE+wZ{XxZw^)>k~W=r>cFm>bc%;m7A;^wxYk>QN)%2xTW#u;yI}N?N?x^WAYAm?5KAKNEqn-cqlZJP` z`hf$pRtVi<#oVS7bN4)-L|?T%nR#@3m#0g`9{Tf{0yskziqi@E66$UDqYKiF9d;y* zB|kjM$7r$R(3DIrV$w$bmH%P4gZ}mDL7yH{^W4|Ss5T1T^g*^dOfE{O$liBT-IA+}NrD&tu? zgT6=@73Z}1c395b`Jt(yd-p(3+;U*KR?9d&&E(z(3QEzYh`XY-$rV}G!nzxSs*iZY zGVOTW9Ag=>U8y}rK0|tR_rup*c77h}QYgBIdGX&e!pwIR)v^wY+iLXbpHQ+jatT!V ze<-`B_{yTNP4Kakif!ArRk3Z`b}F`QRcuvsVmqlgIkD}ECjCw0>+b3K&0Oq@z0dvL zYn}D}pT|(*Fif-@VzZ0_ZpcNH98?4qCG=YSw;2fh3>py9`ORgtq9oSs>`JquSqXNQ zMB&xbx5+}$w<8xSucd59z3+f_s8x zBIz&iVB}pi3x$awM*=V=DqAe#sq6=8K#Yi*k5B<&zuFDDh*ULNiwP5Ukj}?6_TOiy z9NZS2vDn&J)mIR0Ai4S#-r^w=(8c)}dDv9M{p&T^Xs71jOCjCpdt>W0aP@H9~W*EY~$o z7UaSnG1DJ5(9_1dT_JX}t_nCug8h<=N&!-e1FxO)v8IHwf?mECn2^hQhC7@1`b7Ep zGwC54V$OJ@Ih=PE4jV$b8#1ke#My6! z3ZlDV6IE40p&!)PS5Vk9feS*w9S^EpZ7E9+>)6TsiQ7r#eT(jKcSSDEuNl;H&AH?Tm zRJ&%}fd=kF|B4ZQQsvZCv{A~Jpvw(n1+B&B5ag7Hbk$w_H4L&ITX zJo?HxB;*{QasJb~G;g$b0PbJi7C%ZbFTf&MY+ke?kOe!?b*ag@%e4v-SNtdQp=}5F zx-KH%JcPKk)fj9W^pYjVg*yX;tSQQCc;;5v6?mdnILT%c``fEK(pSq{M7cA{F18jn zA!*!y-hNjPP?c@k0KINpAb03PEObSUpcNM!V!@Cr62yTvUq=NaHqZqFkbot=Hq(U$ z5KDs7ij^-P9|xbjL3G&H5J+Yy@g}^JoY>LBWV(lgbUHEM|FW$G8gyU^E3OCLsChoq zOnNlq3MbdGgS0{^kOV=V?Ye-OvG(TTLDAnz-q0Epkp>x<@{BELIndjZ&Z~Lh!z1w{ zK=hK3^aj}XQ~g_C_wf~5_tVBRQ(#v5JJPl0Q|z8uMLRvNQI!e@2|u#A85Knj`L#A_ zzDnVDC{C*swJK`w=@By|zb|-uDZ_#N;ES85Dsc1SiopIWKsfZNc?M>5C)flIYVtiQ?=L zvK=Ky!?KqUj1MYzWEo+S=3I`!*8C;+_HUHbw+bBf&ZxDv`9nd{& z!OYD(YxuR8qZLdby3CRaB3+FrUM)xB;fIqHp>EOFmJ|rNAvyqJODbrXA#|1j@9GE~ zIm^l{)F2O~MJxZ`mT@>XEj2q0ces28NFCcU1khHTFeuASg||=zbywJ&^(xnhcQ!BJ4k=@(X^2J#~Py>TON0&6-6||tpN|x#TLu~5G2A0M>oM0 zrYr(fPkIz2Q|`g+iR>0c#k`+YS+dHLb(S&;c^@Q7=lN5g*mEhD*9J0i!U_Zp0}mcZ z#eDV$Bhi->&+;@qcwxZ?POmM*UK1Ef_t%sZnXrJg*hJ;t7OA=L`RU%EJfPI@iwx3X z&>rkt1KMoD#Vz-(lM$CQ)VOu+dU#7&LjNWV;)9)&7!4Mh87Z1TxB1zBa-E#Vtt5k? zP46=W<}UaG8xT{}zvYRc^zK*%=h}oW?U<60t5~DkG3hNv9Hd%n1eI%p$To*6DGqAb zOyr8^`j&%-^lKD+OB{{fl7_Y!Og&Bh!<@%9RCpSuCb0|3p_m%(oycWL7VW=8u>4my z{9nUZ{oXcar3HfBOFezh?Vp)BlNI|CihU5B&N&vh)Al zUvvLY{@T>h&e7pN47T;R!FF)BH!*kl*6R*Tjt=Hb&i?~tGr2Lj|1U86KcWNvM+U(+ z%>FM={{O@5@4N@r{{*wke4*9wR@ykh*vV!=m4{$zZl_kA@i(G=UT{0d_$K(GESImU zE%s+p3Zs)GLeM_Y`mh)++MCKbgKzG0D>$#a=MIg_ySOgKk0ri+lI0$3z5UfaH$4B! zNS}j2%;tH!__(~@@EvS!Xlnu9dJUy(8XER^xteA6y>6MH4YN%*vGZtG@?4K=QtiJ` zu8)Y?o0hdT_VUNHy5@4&Mh(_zBY8Ai++Hx$cy*`Y9m1DJY-0A_O%yXEDMzu8i1b%j zFa^4=MX!ybS$^G)U(|v&l8B%*8;U|#fDe$C41kj=WHIq=_^o|?Hh;A8uE>{U>xRK7t>q z-f`aT-{pFfclw1ArUxuc?;D>R6P)R~u^mVGZqGwNM1BGJ`hT!l%>K@EwvarI{lpPt zt3$>XLGBa>ch2jO@DqDa2^vDrL^OadDn#u@6#U+)+O@7Z?wg`nxNaQXSo<(b zSZjW|zc6eyG`+SUG74M}To>#Y-02+)82gO=Kz#GO5NiIK`ILI2fu0gM0iG}~8kdMA zigk$Pi#3Xsh0YiC8*lCkM2sSRx#aG@H0|?_UdBt|3*aa4mKSOqp zVT_cNKr8Mlo|X7=YjSgOD*i^(3?4eCE#uY|ivd)XDHSORsvWA~s`0A%s*S2KsuimB z->0M;6&niAdE4&)R!?0k>hsE_`BcOg4t^e>x=?f`ZAx5`ZH*zb22YWvnGsqO+m1Wj z(RxStMZ5>SC%;!@QnjJP3^xxm#bm^+L~_M=Mm|TCnJ_gLa4X?63R29VpAVVu**JH- zbie$0X?qd7WxEv`?u+`G)COLBk0<6|z9-+49-Ggc=XvrGIX}6^J1Ka~I5Rpide*r+ zcsO|W0{Qp+HeUUYD`tea#jx_>olrWJ7+v*_y68Q$ubL*zdnPe6^ckh}{PYz540V?N zG5d2MJ=w?uL0#aN#xGjW{-O@ehUeQS0D7!6clr_k483*~1Nnh04P@cRN?vA|bqi6h zql-U^->pvjtW289j}$ASs|*dmxJVk-mVX0SO?mk7sd_z+TCQ1U22%2Fk<|LSCj3{!klb`GTp%y59<)GHwOavY`Y*OBjX~ zWQr{;Mb89tr-wfU$hkjyGo{|bROS}3644-532Di3Vg9cB9@5rWn$M%@+;)Y}F^4UG zv1DdIRS176+jEN2IGzIe@=K>H5+sGHkj@zpy3;&eZO1me3!hNGHFmX$+Lqj$>a=Th z3J;9By}cWOmlN}ru(mx1ie{jkL~NESUI()!kPtR7VR6izHiR%Ag8*1&? z-|95Kqc!YsnbqMg03 zzz|ayfMe8H3ye$^Tl5w!Sptd6U^U(dY>cW)=;4XeOpm_Js}O)ctv$P&?E=-H(k86 z9$YE-MG`dV7U8u+-z9WxbRs|4Rg}16C(sPg3$6eHt|+Iv31{K5*iBU0Rb=TEauY zp^l7l&igRD69|eq6jipeUUsk8shvPbgH982pggLMDKjtw+UcnTaJyA&7y2MP?`LB( zljLGI^qaiAP2NxeX=Sc_t;G2TS-EDWAQ50?n;B=Qf{~$D=+n^m>^pmWQxbqb4u@47 z6EvRZ^Y1x>1xIt1s9$!6;k-_Wu4C!1HkWYgS+hcus?6*P2!plJgg6UdE5^@#kGqHl z4{BzO_e47EfsSw*zTSj!45pBXmwc@%Pgyg)U&QRZ%8fDWG5x$~K=8~RaaU45-2fE{ zYS#vH`aPP*tFnpS`-{0UHbXzE;9uudcpqfNU}BSIp;1#tCi-)rUh%*& z=d=qsi5tdJ_Fil~8R4km9pWu?$#hg4D){9*I_bG6!+wS+>f!MPJzXWq@zNTvD{R>L)akgs*18e*j99QM1o?sL`eTKw!-`>_J( zyp{WV$kth)R6h7u6=8+WnSZp1~&7&27oh0_1_XgMj2EJCQJQ zay8aAi=oLP_~c?3`-_*Rb$WkQci{=ppa1vB)qJfCXV}iCk+x6VWX?Kfb}4sLe=OCt z`Rtu=*v0QP;Q3^2d_%1>T1`Z>oS!T$I`%q$%vgYVFnnO{A4vc0NPdUNS&3`tO`1g; zoKC^zKT$fX-OUDn8wzxk;_t|rA*@A^2&Tc}tEj8;Y73abKfw2!e#pVcb!&9s%)Ai70WIjzJi;FA&iMQzLXZ*)gmK{ndi8vQ8NT<| za%Bq(2i82^Yp-x^{a7Lo)*@L_{rzIeVM%X(C1}|*7>76rX>!UXZ4=Zu(Y&q~T56fT z-JFfw*(~IT=4C%aP>YNGNk{v9^=_^0xlH-1HO|Q?tcR`dRJBjP{{Zc+`^hZ@DW zScYVW2&cGw+*Y-E&V!MZ?F}Y8f{a|jRlOWdPbomAj3Wn0WfQ}y6(&+E*=-KL{fqDr zH&0{sBu2Iskrp)QC$e-88&v&(5}0{Xz4XlGj?)ho*h<*l^pnA=d$bEIw2VT?T#Ddk z{y;tcnTN}O>R-C@&W3J_oAI4*&Z{)xO!3Z410eLzCNkrP{32u?JyaAI^uGP=*0562 z2HYPmzq$6YJeYl`Fd{l_S)(N>1|VBV4Nj#K{&iXO4p;uF9 zLwVCv+cK6pGxV$Q-K`ntaH6Su82R`#YgG;#I&rFM>}2G`2C`h^rUkG+Xm!|-^y#tArGX7dfCpY9cGeWH%T3KQ@oWP z=7-ZgdDm*y8FoXLmUp*5MZ*NelJaum>@t%|CdyS*MXz2Gt$ZXnTkSNb;8^0y2k%335;-|-~oEOJWn*QX?BSA+Nd z!`e{Z7L==5S42K)P3O+kdXUc_Zu=|MRM7krS$iph>kO$x>!`@ZD5Z-)l792O>elao z2{|YEz;}+b^{}&Mc{?%RSHP^*<>^iXZVmt}_0; z65jG+IVopU{5@b>MCv+ z-`(G9n}+rNmR&$q^ytKtE?6+HA@MX|~4ABqagGIbcV_v0xF_mAWR$s&6$@G(A^lvsAlDA=CY zN=qGd%gTg$_xRi&4&Y6F*dp|Z$jyj3&hb@n?g_CP0V+h5kFePmPhG_dSj$wbK~)5K z*$iR2)>R_jYw=WA!D zFQn9MhJ&}TB?ww%@U0{mIclV*)B?vV8ShZ4L8t36J7OZZnWj`G0m$Yq#sYD=BtDcf zS{h4*5+ieIwd<2KNY*Jq@*8F=7kj-ClW+9KRyBLe4HK3vB8M7EdDV$}nzAWw0LBp$ zqQFt7L~VN5j9iS$5S1+0nJM4f8Qwn}j27y?8;nf`7KC$n8Rt-$8Zma(t+5 zx=h=XPkh|dqahB$*~5egX#OB|BfffmTlM{dxDfnbqO-4&n8y*fP09cwD6=SbQRcOq zgBoMxjbaT;EHof>zPd0t+Z3%cfN5;5%v@SU1!G?B4_`OSFG$O?dhcf8G9_Acm|Z`0 zurg@kl>tYhebHt@ZAlO=AtA13!JVQcK_w}xB=STlaaxng_5oeTrum1m`GF=^IbyTK zOHLFiHL%X}QW&~$mFygX_ams85ZQqzDvkJ~M&>q{FDHPjGb|rmU$V(vnL)l|6~w6% zR=R5i{$_ECxg*-5l4XZ{(5|p4>H4a-Q|AuzYNZUxoFKs4%6qZ~;LF~!+jdXl0vH!Y zMPDvuZSra5m7=3guQ*BhotT-4T;D%7OZ>V&SPxPsJ1*|$xShx6Sre4>9Frc7QNKW3 zUe5AC>fTt^(wSUqM`LxKfIZ&aF3z2jFRd!hg^rea;lZOoQ(*`i*G)rV2pK!qhQ zQEX6^?L7piNW6kk34^6>RdTv=f3z}ks>+w1KEx!5+nJzg*+CWIGzN0yRzytV>~s}E z+l!yNrfB+fW1*45mjH;hIzt#WF)eReujx3XnS_6s;6r2 z!#~UMbozw{(+yE4UXcV@$}27*bF8XvY>b5vJ%LcG7xnzgf4*! zX|hMEm;vI9aLb0c0>%w)P~Y=cYo}TSj%TI^``T7IN=B2CHhFEO^TE3zy8sagHKaN* z3QSFUMsul+c%TkJAadaFd2t#A+Bh&ggnUv5*ZmqT;%dkJ7>SUm@N%~8P|wBl1nZrt z4H+#Y;@w3&302UB)pUV@VVvVhtl?1zRaF{tzuy@xM7!{))g6Q+C7He@0k>Qvj#DqC zby=)zn7X5*2S}B%1MaMx;(IG5X2-V}fe_qYk}Jz+KhN;AjC4ev`lSO5qRKmz7Vzfx zu8B~bN%hHQf13X5kQVHa-FIT(mljf_Hx%36I}Yd#{b4{h0qTp>8?qNL(=}Fnpb)#y zuBdu!W_GU^{H-Dj?c_6d$`v9xG;1#P@PmUV*) zKG{JL4vSwfVo5?|MOp;>PQ_{>PDLINU?)vc(NTgi36(s7@TLI-_(SBuFmqhT(^%13 zVKENfadZZ~5t4Lgu0Y?{{7Zy88e;wt?Mz~w-EK=ehVBLpod6@1NHW_i2SR5RCyYTQ z81SP8c)@-FQv;!w9(!#N2j0?ep;*+p@+ForD)W_!0K;$vVE%{75Qe_HLAa1%Sq_I_ z<@EEfgh~YekG?E8EYzT+L5voh)F4U&nb4lDP)AYxb+_%lFz$6;UL&B1=88m1q&}sa z=h%sou#@iGp+p%MEicH!Az{w^7(wtzvevKd*=oJxsFB1idKg;9#iK*4lgtHDF`3Q- zxf+&8&;ry2VGS7NVsIt?@V|XwP~(57&xrq&uwz@L6(O7qE&@4?ZEYBfv?03U9#DOteypFL@S21rH{^fvUuo4@w%b+p~drpg3W=hryL1q!VLgGod3$T;EVi@p54__*EWx8gdkU@e)?|`>X!*rz@-|JLLZDXl0B-v8j;lMY)Zh z{GM^R8qY5t;&UxDz`2-pv;#CQ>GMO|a$18?EP?y6iLsp+U5 zyDo)?k$`%s_8=Db%|Xt?Ek}ph*n=FGFIyA#m|eE=xg`v87&`Vd(&%XJKd3h2iFOw! z5r#|4)rRYsHFl+9R=DCI70r6(5tYn^R!43Rp;d|pYpHI$&|`z2srNk?bQ?x2?|}!S zO>c3bI|m~yfco1vF30ngY%D4@v<%8s#0ds(Ebb z0$&+cJ!}zkiOI*X-PF)lNRQ9ph3RobnbR5;Cs8%*9us(Dabs~S>ebj?z+g|`!+^#B z%@9#x0sAO~^6?<5gY6LobT71jnp-IMQDyD)W%#OUw;hCv_Kn&v8vV(4-k!@hLny?B z;-2@%yYk9KXcE?lnxOtcP}n3XaCGh|M}=Y&-#pi^b$3(8ziCisML=8q|R&%6Fbr|2Fdfqms{$7%AZI& zloO+JYPfM2-=PdM*y-=DS?ap-%FVd}jGQ+CE*Y_N-*C(@aMqJ05L$P~FG9(tts zv;D1`c0)-9-@EGTSu*k2kjUM$U6hfoCOV(UKb#AThN-oRn$#}MY!zw#m>3!QKB0%{ z0%Sh|rQb%`ALH_^{VS4D@+rM2lhOl%k)IZNAVOBS48ly3mcTUpe*P&xJ;SSlt7}$Y z%~SAK$MWrIG_?|CP246prJ3iyH}Idh$^s&wH4f0@~h+PoAU` z1$2$&?VUjCVqTow`A~&p2()^sVqzar-dgi{lVza@r1;Jk&oY#rJROezLViWrl!?5` zS?fM&<|t>9R^-?nhBFkNIys@5+e0HPHA06?iR?2z8D%PI{~DmDARVJ1&Lb4c9cT& z;7^l_suzzO!)Q2c2fiNW2c>Ja-cJt5BTs#zUED zl19v78JyYV_FSw{yf!_%&MB8N^Mu35L+ErO-5!g_rS*mNzu7JM<~_52Hhji>Hvbj) zVSSo>+P+JF?z|WpX=E~C2E`eFiOSBRuy`4EL(~J9_DWt5-_wIIrmzs;9j1vby=EhG zlsQX$M%Hk!EH^1Jktd_LBDchOi-^~w&WGgp2(=JN0b(~eFY^|)(;7LZoc)})oUA#v zoSt@q4X5k`ildFu4TP9tzPl@)GwexMGPgol=8#`ftMJ-9la+&n# zA5ii!X@9*Am>739D6G<7pg+Me!PdhN!3v>$I<#+IA$G}oyj_xBEL5Tka|MhI8jW?x=ly z7r_snf80>KBYfrD>E8qriM#jwrD&fkzK{2lvI!kl9H}^~xU=}MSiYEm)N$Tfxr5pu zl|DHpm9+M^k{TH=(;o&OQdXYZJp4Sp+#?=_@02L(yhV!&0tKnO;yjhyro1JEGzEDD zdj$antUSrQk=!eu9@^UozQ4{I>{=p{v?ck;l=7n0>SXV%_q7;uj1^C_uZhhWt_`lSuEoup+PgK*fB7_d zR(RTZ3Oswy{AOPp@3CffxW7)`bM6B>Z}fv=Lg7zIosIP|JLp}sPn*`v>nBODxWJtq zfl}P2bVbf&Qg6jux~oqg>XY2?u*c0A%?Ayk0f>W1Q{z!XRDIHg5{LWowo^tw6)zFt zV&zor#Qw!Z8hZ>tG=W9HX9NsR8#dFeZ-w3p;A5_jD6iSQimeAzhZ*z!RS@UdrR87FAir6*z@ z9|Pw~71V=^_sAb|eEJVlnRWzt=~+WmXz%YiY~myb&tmSPC&&eirv>vtg9#&`LkazT zOkq$pu{(y%Hdu{o&L-c)+8^R^WFGww$n*|ZuQQDXbz3`9FKL$EL1b}6?-)*J6m{9JHw^K>4? znmG=Ion%xey5X9^Hk$!nbDZ;Fv-<5?aF(yCB8{Ip*bPzP7X#Qw!55JTQKn!H0#y%7 zyXkvfr~}3Zjl(JYWJgx+O$>&UHUwXi2VX63m!P{va}6n}bkpNpBL;q8zv{~!$_T-t z>+uU#LloY-={}cYpm>pQ;|u0YaB4M`g%b!02Mb~9P zZ_3>8f1j z1vQFPr}7qaRQw9L7fYhUdi(CD{VlN;>E?{jFh0c)960r@UZvD8y-uf;6{v=F)yzA~ z;6&toPEWODWb5?e%_fLKL7COwQ4!NW))L;^IRR73I8X*9X#jNVx<61B0hmL>w@p&e^6#HWt;)pPW4VqA~9h%kDkzR88d7QwHURB;9ww@ zZRvs4}r18$~74D~nQxCu33}$R+ZjT*5OZG=`GM1Q}N-CBV6N@r>pIW19r!Ykk zb0CoO2h1dwp{tqX?_L?23gVdQ;&xy7X(LT&{R zj|NbBNX0G#9MEh1Bs|8n^N&G0f>SWfaYdVC=YpV2@c1Xo-E%AyE7*xIN!I`LHObNY zY1lEs_0`WIjQpV)3J0hiB05wpF(qorrL-aypIeG7oR)yFkI5>mL=kcQOeq9a@Pz0p z7A-7xsF_M5GM!@j0`nk6XNrXqa@mT?NTaA@jn@ z$>w^5|0+C%??!DS?oZy$KEXOV|FiFPXBTJ0W07qc;@1F<%0Bz6AL_2+`bEE|*8F1$ zxx%G>5O0D_^&lD{E7tv)->slPJGEp*CR+A^L+%!t>m2$9C^K@KY2<1d61dd z8cvoSm6(D38Xqa>AEXRUs_;iRzk|(8U>_k2dBf|apd%K{g^lL%nMhmreX;X&77=mo z)hJtN>CBBhUZmSo8Duf-Lg-HltpV)(AJ|`@&k7Xzhy=}u1kV1QS&q*J8#?%mb+Ay`_@DCIBQ(oFNFqV>%=@QLd2NGj#_p4Lj;CuaGQ`^9mIce_xPM{>bJZV<*Q5 zG(^hC5dmc^pZ9m&5P099Pv8(RsvL#QfZ^+p`G<`bQ!ddPqaisP0;Cr6mz; zq!r@CkrD{YZ7Hyc19}`8@X|L!5S9NSYFb<&JzoMu5x|qMnlaCzKbjxtaib`lbX10(sYf5+idtD#6>>0N05!;SR3)Cg$>Qv- z1jTh;yw$oQ0SpYEPcchXb8X!p8)Fuj_JO#tj$!K~b+OjgKbJBHH3q~14 zI2mtwLX2L9h`n~jkZ#QQgIP1yck@Y)hb>AFN4)ZylIY%F7H-} zRyloT{jwz5g=4eh-!5sg8tLf)yaszagxj8t`xk(r?O^rX`)J5bFiQ8~Pbxiz00_;p zs;1O6Z5&vOw91E|f}oAJe;IX~`@YOAEafG&f4$1tyU0r-Wf)Ye@5#d+B}Qh}tsI{Z zB4NTrQwnAdD$HzID@$YLvTC#pik5BJCF~4Zb!y8%UOz|QC#v|wT_k0pQqs~>k}$Gw zn&3N@oJE(%h4PqNbWhv zZgqr1ZsrS3zJl0q{BDdBW`-R8g?{0M$9s7c&>06k;f<+w@Wh^b>ce%sbV{dx=TdAW__gq8>5;~-=f zw&u2T(_mPT(`k@?XW6*2hZ9HzWj~EbL`cc5A94#3EBcBokf)Iq55V>jtyHeuMg|Io-z0^ULR4 zGeD-bNR(;b9H*(76@!K*wjv=XA*UVa4V_4M9@LB3(LkSUfVDmggqs4kdG~^A- zrTA%c!cM;&kZ63BqhJqse%IZ;e?t?BUh;0wcl>?}{G_8MIusoJ#n2+Bi<>1G$rla^R|JO(9SX~f)Q|74M~Rmf@hx{`XTny>96qK8m>>{vz>h)S<2i9w5QrO`%AiT z#sTMUZiz|H1&FH>Ccaxpav2WXqc^T8i>|Jojw;hBqTRjAqfmg;V&v%l^9Zz{orw)4 z!Ve%TtX_;FKz#@X(jX+p6qeg73~6oJCFWL|--iO!pg4?tFXG_GzF3A;qAGR7J<9^xr%b> zc;BJEj_r!_=C;z}qD)ZG^Y#Y5w%lJ8u!-K-{$CiaC`Wq1U-*Yv29s0)8+8?dT+(r? zt!%LUOfqS8IiZo*d8k(xNhDj0S6rx{a zE#g7jrUzWvGw1tDO7|^oywhM){Hv)_L8`wx!9g9+IbxgIXWSIJsa}V!pZ;o;Tl`tc zPk@GNH=g&}6~PrJhDJLs-qJbABj4=6&4;BSQta+59>` z5`TZ>zBtM~3-1IZyaI<{nP&6Rz{R$m4vh zpCk4J_x-&^BCpw`wOc)e=C&aPoP=0q5ugMom6~XpRnX+gS`q8KpCm?`?c)^f}JZlQw(+Tl;$^2hgMpXn3V z+*q6={}>D!JIRrQ#E#*z{4Bf!^cl6Lnp1#=<0sNx#i%%tMidA6q;(|&lKwXGyZn33Dghm+785XnwWW7ODE`>^&cEaeK*?IdxLNH0mNu%Rh4-Y)fl&t5p zB6sRvxaNLJWTzR*v4$?+IAU??nc{z9O{&m~Zy^t00d!d~4M$-SYu})9U7vzc2Uz~L zuE~Jp5Vw;PH=_^Qt*~Ym=L>-s5692kpy#3KOj7$J6nm9!I^V>vk5moe@XM(-%Q?7N z97bAEQHik3)0#B|Hc=>qyq`Bj7kp)a!)f|{rNH{ZCl~m)minCJ$X4&jL8?qD- zEDO1y>fnof8FqxiWnZN`HJ`Bk+W>L620Mv#3pv5b8m)E%yWHub=>%U$({rg9&tACxk+9BbyC4> zL9VD6-f~4bj}F3pF~ZS%jtu#+qVIQ%GE~VF&YwOjAE4r|0TFjsFyT-nPO6q-3Jao zq)0^J(!|=V+HA&yf(t6FbcxsoWX5o=pxzYVT4bgNWwmM?;(i^NHYy<-^OX(lmfK=K zVji}71tFwBvJ4YHk8(+^O&^1C&1Saw2}J$n)6UHObuTae;wGm28%-ROa)n)XhJD%n zX({wQ)DzdcvtdYJPphRFn)*4-vPp=1I8Q$ce{^%)Z@tfBykpB?E*TEA_x@wm{H`S; zSH{_{jzxZhYH%+sgV6YUL`dF;iZvFGcRnZ9rDveK zP$Eu^i9q5hP~kx1BNdbV2}%0u0$cBCU17z(H!f+*(l*Y%u%fE?sK$&NxLGv#Ir_tk z-3Kw>2!Lq!n{0EGj@j>PbOMv{6j5nfubFx3rkGRuypp5flBxyAhS}Y0`$cFc zC~(*H7D2QMB^_??&|y%ta~zs;=r};765Jt?^iIzHc(D|oW7}$g3qUY40O@F5qQ3`k zX$QSihsWhJQ|S&22LVJS&60#BoZ$l%V?CEJ9VZ$MDEv%LMgS+R0yTFy`#@qhv1)ek zCmrDdY_QGTE2hx8argx9pz*}fV-Whd>ca%15li)t%RDdu^_|OoOY+3i+27B9S`9(W zRKdvTkt83pQg}0{#JdM|`cqE?^C(5VV7uQ#aoCc0@_9~pO~afH7)?@XZFbqIVGyJ$ zP&4S=OgDh1qoFq`m5S+)gK|FRZK&BAGG?Q>Q^@;;lhNsy9^@>T6y= zo)K&ILw|k*e^5>bh7+3EuHF0(BZ#Ytv^R2)`i?o>xu5>a$L+r$%TDb?JPhJhl2OT_ zl_UXJ16t^c%V%fp+$`ipX zAAN>OQoxWCptxfzf~^s~Yti9gm=R|C4-HlbekxmS0{fp>UYw?E7KqR>Gt^bWoAkb! zQNCrf*u^gYLlao`W^SU9qct+!!9v@dQ8qD74oI4djZZ!h7kv(dF1R%^?u&9GOnw%* zQTWXhmpeORCEb-DRw+R~dio6ELdcQu0Y%YA-sj^gELe##`U~}gqi*i<-7m2Pm-A7ySJ6?y8)DwmF2&2 zCKfgp7H0PU-YEI~%YPN$+lI*Np|Jf zziiWJqwhSZkBpq?zti3j&L9j%lGyNzg5G%NSzlP#StP8g)_%E4z1qI@?e2+ayXZLp z?ZUtBdzrj_{FZ+`4lYBPqCNAKd;j(^!^5J}aFYfpDJl8$sk`emv=zE!GxS{m@p|P% zhOB3p&L_z^T-=ex_SZ=z1$_ z>T<{DIFG|uba0RK-T$Ll!@>4UdlRuuzk7*iTwU+pm+TT<7+=|9rTFE*617sg_#H37x^@o_P!xeVbml`)&NN6jp%_e}flxGeE<{(a6!XAZQsV z-~`XFmE0)aT52S7n!HX?!0>7_eoxUypQZc9i003dxiwH_%y1~-@aId|yYr{{r_}rC zyXCuK`YS!W7G{%N)5UO8Amdy6p~xgz`Y3~<-lx@T`(&Tt=O|nkMh~5&rd(|%r?zp^ zHu<=?O|rR){esbgJ*x$)HLJOcsxZ6;>=PUkTs-UrCHb zTZeb}cTGJ<3kwV5wuuXZ=0^+NDWq%yPCjc)`*P+FGmp^=-R7NXi|Nm4zapO5 zcZU1WqtBzFqxmD$d)9m0dy9MJd-x-woG-IFt}oRNEox-Au-F^KUEE!TqLevJY-$9I z5!jSp)&qJ)l3I*4n4B=aVfzC(yI2Di1IGge5;5W}Cmi!R#nZ)u#fzjw%9+%XKm_z0*&a$HV7pc6*@0`{l{^P_8Ocs2bH}idy!mrI@u?K= z^k;a|ycvI%=){V+CG=nz8K+-|cea`b0+-!!RLUCzAW={MW$Kb)sp zvg*?<#;z?v@*N%zG~7XojKarL1B(|)+p|D1@% z?dXU5keMqpa=qkR&N;_7M1%23Q75m(>-W+2WVdv9($$>%i7#-tfU@=Xt7|j<@XBkK zuE9}Nfn8T83y~E^ARA!Xi7+BmWP1Ddff(ox{<{ScY?L(7wzqvu;$}3lRcwqj8du#l zkk*>e&IHEDZe#G#`dm|G!z_+RExpeuXeQ(t`tX$PGPy3oek|~QM;5U`sJKEhM!Zgs zg}|{|w6IODCH%*Y^1a715}<$b})VTuwAT^h#@mgTWed>@DTJgF`_vHC|)trJ9;rg)*Sj?A}h*0^I#@ zBl(mDMlDdALr5L*TQCv;F1kQqwj;i#rsWFAwS*MWeXg34<}?QPT4#v45t?eZ(nJ+x zt+aF`(mf_eCtu&l!6T=Hl#{d3uUsmYE{0UFpdf(D19*fk+xF*?5je`!uub_+G!}=S zc5`#Zns8U+w%r>0VTRL%I+ulX4Y;=|s8dI)yxl9Ab|(O)KvnMuMWq;vJRf{yr+Hwb z4InOj@o2sQLj)_R3)RH>2W}h-zYR9kXB7q=p4V`1U@}+NOJpbGac6fZ$@A2m^5wY{ zy1&Ut;Do!<9#>KJQegMAHyc^s7H)_qcmBh*o18C0MpZjF5&7on2{{z#Zwmk zS*59U(b;(;V&;J>Scp)Z5bn8ib}Q}7jh;hlMg~9bYSCaAfG%%0Znu@qRd}hCFR_xH!_s8{<|-)f%uXQF-(FI;fDb z^1gb<074~eRh`D8iAqn^{++E=N*wpJ$ITWRPOB`GIn*<>!MclwuV04IH7=e4RU&c>O_DJRBoQt^4w z*F9HT7{~EJZ!DVE<(_7R;^|4bl2kFRs5U=yM5Q1QkwEvej`Ig5ZHMdi#|@dWX(x{9@w=Zub-@bhCb5guSX5pZSqsIcA@wj34oPVC~Q$tXd``4n8+FH7lYj}RNE=W zw3cx|N&eUnB`0MnWfs|V{3nW6Qu$N{u1V~_h;7}nkQI89TkvA=P?+B8tX|rx55}~u z6&iuvr5OiaCxCwHvp&Iob`^7Iiurz>n@d4b=_DDas7Q3-#Dxl#_(Kf!bYQo{rk0fz zf5w2CwJHn@pdO4-jUe(on1H{85 zqnz2-P-s5A3d-67rdEyYz$v80W>&h@y#&r0Q0w8NVIs*FjowJrr2?tT*#cl= zVy|#9Z@#?z0MF21gbX?9*0c}qp89u-5%$7ipO>?)i>aK9m!pA8DeXcI5R8#k^8TDE z7?G0@{G-vD`V-gScXb6rBu~^@2wX+~8VrmUgr_j(I7q(Eau=utBpAXfeV$FlW=%epY&$`6uZ! z?X+X{xSWLKbQD64fq$_bjgpM+21_&|ipm8_<{lVrNfV;A%;5Nu6A_Z|`XLKB{p6;3 z$e`O3H7BRpvxqEtFCJVxViPiQVd-hYj+X1L_Aca5zeHbAm00lv@$w(_kgP@>5a7G* z8Co5kBDQ?7%uVS4dd$rxaJh+NM_%e~Mf3 zKWwy=BpgZlf@=?#`>tVHnaV0sOX$bZ3)XNLoB<`A#IR>(D&h+F{CIq^5=&rM0HE)6 zQWqU6Xw&`zBFuwOCj&^=4UM4`dZFuye4)lb<-}koH`qXV-*m44GHSy7#K4cG`C;#+ zh@({(^QofP@ml7L;+p8jTQvKUt9;ZRrOkCgk-nS z#r8l%PrB-=k{&5SxY@wt$<0`q7rmFhge`N;9L%a3Gpkm^1-S$QpWm1$=ffLd7!^yV z#`PQ)6da0-tRyVF8pxVSREw2P7$&@;8B?hhBo8rfg(K>&)G!Gtc-64&ZU)Z!cx&eQ zIgYv$mWq}RXEcn<}A;AZ55&DVJ zajVFPMX-`C2qo-g|HTL0{g!a!TcDu1vn`&fWWa zQ2#NFyWM11Z8}dTEz`qDPX1f3)OG&xjLHtWHCW8TE;bcEhMclgA6<|gdQe-KoJar# zk^HoRv@SRsLQap6R_FR=v0&=->1F!RNPz$m7r~u9-(5^E>z%)S6`=>A+6#TPX(fi6 z(br~D{bQ0byC{t;ExiinE)4o7Ibv*5u5=irWC(nb+Tji$vVraXMSk-GedfYr!^}TW z)L3;3_1{CJwBnc4X`0PD*1h;EODST~!;%q_x7h9W3_!uV2~tjZbF!RC?!p5Faj>GSYJ0MJ3PDd?lT zL(K@qivhCdt4`_RR24`IC6<3M@%xhki7tIZ!8=1D;eY1je1AsfT4q8*;QFrdV*&uI zc;ortAq)c~??(oe`AC3}ZQ?*#2>T2s{qO|`>^kK7#`YhRDx zar+sW3GsbM9IxeL$m%1mXg!^Uusw(TWZg1flys6)T3Pj)5R5H|!+4gmym074jI3>= z^Fs?!4n#zcyijZlhF!Yc~DB)`O-om2LZhS5awye>EVPg!SJWZ)Mf zny!@C2_XW$Q8vLk@8@M~KNJyf!SIYhtXIDA{AG?Pd;QNNm0LB*)6;MKTO3L54v@@% z{yH4vp?HI-_P><56f}n?P%;@%)=ku5Y)OE4OF&YeNaH^0N>8W%O+jd?9)7J+LQ2i{ z&k9`DEJrD&Mtj`t0G6hR^_uE`nxgf^yXzm?Bb&$HxjMwh!^9Y{7aR}!ZnjD6s_pI# z8LhQHs>7(b*IQy}bAP>9@5$`8PH6iHv3r`$!f;E~ZaW@Q`Jla>RM^nQODB{$fW&i; zEaaL+@T_U;a^u*3cm={$h{kcMgReWaX*WJHkUd3V3J8FqMh!AG*t3lKLa6EU@%+SE z2nU*pt<}_ben22RCLl^V+=&@uSmG#q^T=sXQy(kKMC5;%`6P_lL&)XG+3qHGG7UgCx+XGYL?@NE|bHB=JOA z5Bvk&0R0b=|D4#&>f!!f@b69L?3}d0k;bi_DIL4oZA+lHGrHt0!l=dM!}0|Y>Y`i` zWjSfaPzOQo9pCEVpiHXI*SDwHywrns{qvlNZw$9x*t+m}0E6H=fNNGR)OQP?Z9(e* zC>2^#3Tj29xU@fYu^(dU@%SVgi<0^(ipJq7ddiXhEJO|+1#E-@xI={9b&Io1G9a-0 zRC?6eV;M%y-f%!#P*I)OKreL%d5TLanUN6rJ)gOPQGpaStno};Es;L{A4UOo{?i>K zj;$4ql?@rya|x>V8W3ej-lms(xKq2bvL^c0vbykguwh72fz=<^3( z`rqk=v(Q~1gsmXRki)eU19CM_p*w5pwZpS>MClTVNC`GT#kGM?0DQvCxx=i(2;f-~ zkAxlu<7v{03ocyZI*cG&hx!kul;kN;a}SgRyx-J6*{rLFY&srVR?ILIBkvm$x71Di-By{4Sen^{Hp4)X?e6t3~L>E zbxe8O+Gyq_B$@1ojLyrzh#1k%v?>Ry8Lyre-QPFl_kJqAeghA_L12;)4? zr(ezOp7_PF$hWyL*;$E={zx9Wr%q6IkN=xoGA;(N^ppM`p$ayYqV_TEHSZJ#bd7-l zRSefnXy~3pA*WqCgV7$UY87!)f9}5Ih?ld+0hAsLNs|*1=Ld8YtibxIqc#Noy8{Lz zQeI%ppxswkE1*O|4S#0o+zVw>JDJ*#m@S;(ouC!OKZNWM!m&~_lP)5j9E2>7A=<1h z=s&U{r=y9qfWL7_=*oG^Dv8C$0IS6UNtzV{2F~#zWoW7ah8XzYobTl@n?gN-0iJ5M z=}J~*Mnep=SDRuUN+GUX^5NIbFHx?Xf{uCG{{FXh15RK_REP-S_sI^%WK6>W)+uB4 zgE(Tym=!S1e`J-FB!EjOTLv}*!%{vc%K!ayIBPl3Di#@}S~yBpMe-(uttsu=k_Fn@rDNwvW4}6h-QgK{&-$G}0$%>o zewJ2zy32td1GqBuCn6x+a7kUF5b!(+G417m@mH<* z0VEkDNx$%}7gvpI=*VTYYhqwJIPU2Gh=(Q$Hg}1@&sg!N(PCV-6pJ>BaWAekGnf1v zi|3Br>snFFTSuERT-G;Sm9jis{=scod6&Zt69ySw=H;f5%cya(mZU*l>K69Jsh-#` zElDDhOJ`wnF(m<7&JGU@W5VD|c5i_^;FkTsJf_rbywDJ$Hl&WW&YRP| zWWBewvpm00y6U0ya9#jlkaBFvQT|})K5x2;`x271K64-Q431|1V-B^WE@dvkc^~Cf z$Ljs!1=R_`gJfX+2Llq0ZQm8hVz?(L)3Q%Iv@gUT&`-5kU}$eq_`;wq9D09R$y8NQ zQB_oo={GUm9(#(Oau20S6cE8atWT0sZgSa3-={~UT8`2oy%1sR3n{%LC0kqUX!4=Y zq%slQ+xcLnVe%lZu03JTmg>INnUzZeRdaP1iHYC_Okr2OhT!~WGH2!Vr^Am-G?n|_-~03M#!Kqh4<9{Kzt+n^;ON66H_5T{#F}qU zj6VCEDO#9MBFr>zUGPVD9I7umW|Y3Y+&auY#5VV5>}>PWy>a=zo>?1%Y<2Kgo)4a; z&{FnGb%$BFtxk9VZ|AbF31)lft&uh9J^mIri`h$Ee;brEIv9zgnJpk4o&Z4PS?UTkad_Ki;unH9WXw4`m2RL6muoj@u&>2__9wk0}tg8AIh8m{ks|K51Zn4KUI z+>87I2CyQ%alG>z{-GLZttyuvG4VRcICW{bXL#KS(^vdKRtmdR2V8)2eMls9d-1kA zY@ zAv|!ok9s&?FK+};gI1X0FxVkXl++2s=OjH0pz26`WTYAmHefsN&2hbb443tQk={Np zDbo)bKf51+D$ytB(0ZH;pHG;7*qC48;!f{3ET)3_oH}EHWbw!23 zr30lN1+}tndVpy~4~lm1I)oSz@X$eCpM|}qxQqQ!wj00Kj+W>tVTZEN_Qs^;Y4-$JrJ?2&Vj!Fa_{lNyJN89j5Nw?FyH{&u zKOKVMfVQ(yz0o1s>;_?mOdrFUMEXEvc;vXjD9Djp3HDy?&!)YLrb_$ixnCc~cr`3?jqUsG zii>N%|Ey~fNdlVrnu~CiHYRRk#D!YHI6r^;Nr}?={q`re;DF7T9{H-~^;UoZDaFT; z7&)4&s=3fHFvk!~5dm}=z})cBd;Qv>kYPBDv*rAZ+z^0mF5%|#k|x8(&*k|RF5UTO z&!nz;z@j>E>iL-p*uR}i{ugek8v}e# zTdkO{oXe6gA>h1A(Q>?I6)&3g2u-%L`~$fL4x^jU)x*I&?$-0a4p4-CrcpC9YO+H# z`P-V?8*)Id|0yy>aj1#$Tzqed@xs!bh*r^j2Jwt~hVZFqz~KpIJ2|afu58gPk@(>> zj#L;J64P@W5CuvDeS<}ck=F;QioXCHK5bUzC{A1sNUL9^9mw>rQHolD?}s5%6# z9aY0;?ga0Ffvc_5BG!5aXUj4hm^wD8)8%siKI{_-j6O=2qxHFX;HxLHcDa_h#(o9V z=MOQr$8tw@2GXquB}K>HGGr3IiBwDKBgv!f;95SpkX0NkNtL2PXRW@`{kXL-Uc4x2 zOV{1`I9V*4%A?CM3GU_i&ELbc8^}A}+ica}AUv5u2d#nDO>L}x>frQte|$3uoFYma ztDDos<=yV$e*LfH7!4b!$k$KcW;b070^(~MK!W5v1k;%Lck#<$r!_RL`F{^i|t z8>$aTYd`nB9v77A;j$YsPmJt7(<|;LAnxL*5722dV+2=n0YHJd_wCiIm1R`lhtX968l6r4f-54z#|p z^{aC*z_-$q5;D+`B`55_%mp!m0RgKgn*Z=9KJ09Mv#Mr@_mc9V<*xmN3!d*YOssW` z?NJ@~IYH3!SgsgJSk=>$M1mw1WEUV+FtY6W6c}7swjvw#DMVR35bt!|C*beCeHD8E z@36-PWAR)g8;E1y9(86_Q~pR|gt4eN$>kQB~%`(O*{e27wM5uF7}yi)lfe>JcSaEK!&qE17EFuE8`8iaEL$E?=8Jbx92J}(n_ z4j>l@j+>be0~F#2f&fn>Too7`h~M4ah`ihAyXvcg-fCFRE-~A)n=Q4(ueh_LsU=)p z!=@2pdv zUxkQ?goPmxkQA&uJfGXiPof*gr$cLvVFZqL1|AfWwX)(y=M&#sZ0I*v&~ zici(jl37|waZWKqRVQ^4(akA{$-<$wzS8s1yqL)*4Kd{C#xi$_Uw6}tD|8GH6hc78 zMbAJHg6h$?vi;=?H3XT6xqO38vRE#nziZBWf-VnLtQl8DmyYtQh8};Nn zQ!{YQ;KF-U&((HYIh#?dRivp=(TP>#p#(vEg+zQf)KwTGD(I4Mb>Qtv0Ehl0u-|-} z7CSrhxI<(b@&(UKZ_M*n)4u8MeMVw+sKA*voCoY9@k3ZJM*#&LR*}$!*s&n@$EJQj zV$Rat;f0Irhq=>Q#!jLaE56yZ^5J*%o1a&E->;sd4FWr{(r9?|X8Mcbs7W`9=X6C2 zZXsHr+{f+}zvBXX;}RuvEd*AnScJS-l-!MJ4+mY?0mjL&5wMZxbqo5DyQPfo5b{&+ zYHJZko>HSQp460r@kkuTk|8zsD2-HhvT!e!9RuZjvA3{|yCbs*5>+a?ZRs3@a7JeG zU(ts#1jGOjvCroFnYG3VET%=L_tXl#rb%wEY#pvUQQ754>h)aPEzsinn#R0z0JXRW z0e8|f3XlCbw8tvDopZ`qIFQa?j0SArB^~u`=^(G*C{7F?eg};v57v02`ffs|L}vl`eF$CPxYc5I({dz~3F%XE z!6Y$t)v)-NwzSx1MV1$p8qU`I9Qz?}J5m|cth~QCiHYfCtr2&^5*`ii657$K)8AM~ z@Lyj!79+1}O44Ldd6PJ-k)^kxsjS&Grsz-D;I~woVVI#&mtx8oc6PuGj&IU!9!Ue6 z$X-f}KYtW4&L$-$)C`;4J7U{hnqdn5Au0veM)}QWSo`ua(-){c%V(A%w)Ux^YE!zA zC>R5{aLQ+`&lPjheruk}V&ORc6e4ON?sdZ>R0${lsdoY!iv1RGSPZ1IxD)Xz6(Wz% zon2itjiDps(nNwD`t}xZv`z#J!KRpUAZHfLfl!6}?Kv zE&zAg=~)u(dznytUK~9pIRd|~ikphjL91G%e{Yu1@%>nTn_Vv-ql$*Y>`~M4jtEHi zM8VZ3PJy&awvymzi-?76l5WNMnWeX!=dVz8cWzD=t&GXIK}y{ND=~{iGKSboV*pHq zqhq9|-Q{-OW9vE)fWxVG)`I+{8VYf2iyxC2PE1M8TtVKVg2goSjakkWigf2wwUmj6 zI6ZA$NQ3IHEwITBCYQfnmJkvr3|&G{3%=w{G0McRQb^(l*;qlF;52 zt7l`HkNAtT*l?YFUx{Bo*+OVs5p&>?7zIT_Bx~lJ2CU0N>Yzv3J8$sruy+|Y-65tV)T&3Q`kT1?&(@|OQ$Zw%F7qS*Cz9~n{Rxv9@)X|jGLL(A5AW20B% zwjlQNQ;ibJqsOr*8W+?$e@ zud)TptQ^TZbcW@pooEnyJ~kg-FW>Xt#ATF6)FoSBD50#!*oB?^6N!!H>2@ns9rGf( z*m$HA8?A3B)0>6NYxWaFl-6{(F#mXaSwi_q@kEu|$N5H7#_oLj(Xq;omStWiU(9g` zMmT^82TR%X^09!D7{fXDqB?G*WU`RSV1Kt>zYWlzssm3TvC*jf4>xrcSAKhUO}-^T z-f>j4WuFPB%%1&)jI_Ogbr0SW(;fSBEnwJoj7|=)l6ZW@f}9f76yD#kJW5)HrMO;D zp#E7qOjqzz7+6j>zlfAh0i;fDWwrUqEpzN1`&106vpdUTVhe$07vsWU=?oWK*}H2Y z%ZFDKd&<`=L1OLeeTBAcqt*JCd&#-pPm3H6ElLXkd?&D*mT<0P)F~m5j$#~gP~KYm zk=lG>Q>LQjsr5AvI?(~1g&9siICvK+F0daN)T5*_2}~Xfxwd$-pdWt>Qw6NBsJ!g< z2&Z;|nmC4kMPF7I#?%nWAH?*y%B{pZWu_i7@qaioC&>sv4u587gEW0vnFY>ld(r`X zQl7^}$GdZ?(Vg2j3VcewKPaVAJ-_K*zTJG5krs&4%A~`Vcn9f1+-?^Y<9_Ck6Iwo+ zXfD4SqJUW)Jgj;!J>Wik#eeLuP1K}E3~?hh;^XU+LrNBCVlL7R72K<&KG7E?rK88W ziucA@6_kV92{;je7nPHI(EdfYA3cHlL`kRF0-J15xiQ8pXbb0`Rc?f1PvOVf@PQ!> z(ec2;XBBqJ;?o+VCtUUU=D5>0Ra>cx9yv3wWBLP{%t6xNiKvM_5<1S{x=%!m-M;g-PQajHKkaSljmpRRxe%rvIxuvNTP*PjYdR8RumMV;FkZWmU`= zJAjtq~$Pt$Ewqt@!MhJw2^YyY&uMpsZ^^K-04LCpZ2fN}xlAfY@ABO1gQA6cB?`zD6` zJ6&e(8K77GRkfVL-IO|+-`2|RU?AyF-iF?>;t68F9wq4kRLQ3YzVXWGU`2m$z;xga zt;Izzy^Gt+`WnQ=8;7va#|)Ot$%!;WDQXzIDDOFt&86MF-A!a=AgGHvKsWCxw;yji zNXzYFlB{49S`j&$61D2g9M<#m%0ZHk38-s`&STozIDX~hLXJI6%l7dN?26SV$y#;< zbN^X333@>_b8=V^JCk#XLxw$pJaBkiPYL*A?t{b}LKLYc5I)HhcK4gC5dpGI4wE@7Q_5{P+b(t6n^0%KFicN3HVzEcvT zueSo2i~M=+mnHfXpbjqMm@T-k(H2}dP&JI5=@Wv8GOaR5%0^;Yf(P8evZZ(`%&`W# z*OjL zj&JUVIqPtl3xm>10>`0Joe}W@iunQ__%ESC&;4Qym5o9u3FFbKv+gI$Lt{BFx;GwD73~iK%Uq zM~f~aeL;z<7fOQc@$Fu?b$U05J_5f7`}r`-dC4*U$67#1Oe)M3@`p<;!7?1{Hr<5~9R-<*D7N9ahM$4VITOw1LU z5lzNf6ql%LgN4um!6{>;rX=6)ozbKnxVA<)W#ujW3gjvd94s^eaaH>|&9XirGj?AUw5 zP*@uCi;=Afm1ZOtKlolnN2qgRH87D}%(If1;ldFErhOD|Cj=m3S#*;gQ83^YqTm}1 zDDYi&dO8f$Cm%Pze-<#do$w&xJ9>5?Yc3+wzNt04z>SS6h>nS$GzMQa@-l!15f8=3 zg(kMoUe4P?j4cIa&Jc{`DSIt%3pSS6-8*tWRvW&`UKSZ2i2P3i=oXDuC?Nz_NQkYF zg12!zLr~r%Aw|Fne`fJga7bNfneRf9#pcoC5NL_)JG@$&nKEIoRL0s0!56i+s zZMX|C!VD`&x8T4hP-Wq!vin?4!c1m~KM)uIShN!Pgs3n6)0UH#)h+HFHv0Q6CQx_q zN`bjAOY6hTg`4Z<$0#kLqRf}d6?RpecjERGZCXnR^bJ5bR}Ley!D;C=z&Fm0UN)?t zO|@BUOrhQSI%Uoj;om*yRFF;G&>TH|Y!poYpYk{ytr$NB%-$cyBZLU~7!vXJ>E;@rFp zydkJ<+mVdIJAkd>qwnqfZ6%v|STdY6hoh1lgK^nF~YKktJ zNv*7i#nK?Jl<297ErPVj=-}Ywvz;*O4z0_&$ov0tmPM#?$5P@$S@Cy_{DA-d9KxWM ziSQ~qg#fIK+pUI(=jeY`cJrn4pT3!z0={!ahC0n_rxnSzR$VEwa(I3{1!)q$W^HpA=P9@Kql)aFyg0E~`lqi{h&dG4{`xOuvfmsCh3oM%wD%TQxt;bLQM5rNH} z`YeHL4noCUN9w^_YB0^bbMe7g!|c%W&C2g_xmfgEuPRZ7N1>40xfxKj94=%23K8@19)&r-Dr&o^ktx5)83U;y3~+6L0@1Z z>9_&yYQG%}Mny&}{a)DkwU``~be(T>Wb?V@p2lY}7~leounN)OsNfL8KI9wah7*}? zP9`NWNx#A-u|KHs#nzS=D(pzTupQObAStP!C}nP8zD7*Q&8R?u;t+`(wuX-%oB+Hi z+Z!%1yr+}2G2`bC#Gq7Pjgj=)Dxm99-qK=eAhX7xyyJO80GBkoJbp2B@mq+@WN4Uv z#$IiH?OVKk-DOiNdTCOG@-gW2RO}sM17fqjERmPeayUt$4CI!8mn^195p#(A`ytN2 z)X$DVSsu8QB$&w$Pv5#bp%*S7a2nzsOgSLLmZ-9&Lj)9-?#!?BD;YGnAc1Ia%0`Hf zrI2PN$sEGDL?ee(m4fj+NvxhL>hU-a$df&!4-tW3I#-jW@Fn|!yDcm>_oN1Q?_HV6 zaVrE91)(!U%Z3*_Gw39w8vNAjsfQ^NKpt|C0s$SHcv=!v;;9ZHuWA;Y<<0e;UJ824Vja_v9976o8CtjNzUF za;-W`u!P)WzGs|a+~#{cHE8DyuLTxUNVETDaR;neJ*)D!=ow3JKi|Vh*#|s892h&@ z=KW<*;#E1MwqbiJjdrJCx;wn)vO~{jJW4|?>62Aq zq>fQhm|Is?TB6yVu3DPl*!IK%uPX0u745c_XDZ?K?4(RVG{SOjQV=sVp*07U?qnhK z78v}N92q9Z=PXdBf^QZ>3-m0S;IX`6?9O8R`)3*SUad+{%G5SJMf73?>UkISFlJ7j z=#4Pu4iB!b8iaJZ+bFg|0iHqwpoqnK``CUU$kc%fV8@Riy_Bog5( zracJzr?Na2KuJngitD8bd9HVkqL!~KU;@4v@R3PQK{0~N-QvtCCV}+HV0cVJ!0arQ z?l6N1M&@yY!5B*IH6U7b0@>;^%2~k;xs!C_ApJ6oAWzn*MwHp$fCm@AS^hRQr)eOdtqh+hGckK6gKfsGfWao;lpn<#MZf=GNDb8;SVMf0 zQbquubsnvWH+bYPFr5ypRMRqv$SKHw_jt{Kekk168Z9Me?qL0fSTBO9_+-NAfWOfX zPIJrtZw``y@qgLNl-%u%>Hc3Bq`bVSimWP?AUz8^je@PUzRiEV`QH>I+kdQXe+k(B zPpjMiA|QWNZ~r?1`CqsHlYso)`Tsy58JPYrsoQ@T$p48wiqomk>C@?3+x^Ph^lgmj z^c@^*U5#vAZT@@ZY)5B6XZUM*Gd4FhbD}dbcQK|jv2}K!Gov$ew=*-gp);qmqO<;w z0M7hZ`)2!J@;5tu2V)y6V-qL3|5`cx*H$|#XUG3Y;f$@#|JxkrXzcR4-O=2Q&e2Za z(D+vq=Vaz!{Cjn^rE{foqjRV8Fm|y0{}7q%zh7G6{~$8|^W^`(Iqok53)BB*j?3-@ z_d-_P`D>xiOnjn;sfm}0ISWI{CqMv60w^GP){Siky4m)kb4=SV)OQBXSe(IYumFow+~OfFrDdiH|NwhhnO*weNc)7 z$sg9X=i%nyYd>E{zoa!Xx@zom~~T%dsBv^W+PR?aE+shldBkQ{e z_gA-W>W_M9z|8zOR&n1r{4;RII_UWsqZl%J}~q_BfBjXeMzP+LD(Jvuuu ze*&N+e?>i@Pq3&)M-xG4Cg_y|(#T@8N@^FiHVq}sB}vS4$px(Qq~df^b93Xjn&ZEd zY?CCD3X>L-CX+mqhbe|BzO7kVhJV4Q(wR)3vS=J#rrGIF?|zkSJdPpNLMkaLq19Ve zT~(h|?^QAy+4UT@PsPivD!gi62G%CriykS>!B0Atx|ax-W|z#D)R&Gc_!>XM@45}i zzXP1LS8Z3K>GLBSA&u*%hbD)%$i|6AiI#zPw#{WX zQsIgQ&)hhoSLR@ysJz2_WPJsHBz^+FQ>&Y+vuSKU3a&3(u&r9xZt9&IpBiABqno5# zrt7R5x~)7XpQ$!`*S`%o%|GZm*IoX)L~UzzzFf7eU)OUQotmH&F+ZgMw$jNEv3_HKUZ+Zx}R{RDhjKHF~IHu0MIF72%HO!3U~Z1arn=LVyTK*0686ybkUA56VVi5*wZ{-7@|vbf)k|!tM0~(lJE(Q5cV5WiGTHDX18PPJx8w_w|P_F}$4L`)tGEYypLA8JUyaBQ%FA_M7crS=guJZ!!vrK98AM z_xQPvqL8{x>zl5q;P`FGwST<5(CX*u{pE1MzY-_Sf$jN~Gc~ga=Zz~IwSQACQw{BS zl;z&Rz@vKTQ$nCwCVfN|(AK*`rsf462UsHsKL)4kZ$pFpH10KQYv-z~8g;kLisIIC zmJ8X*=V2CcgP-12z8$npHz}Y0?d2!$r>pk^+0_#2l=Ok0XX{6MNyb^e!J&okayRYN z5_Xg}_SAH9brKaGfEZ&`v9GTu64oAACPo&-((@cihV!e}KqB!KV#?9Y%%7S!OIfSS zv+Ax!GJZ*h+bFw>WRq+0khaJ|A@rW!`cR}zE1_KcV*$+&$j>9`ElM+wsf2|Mi+JQ+ z!1sE-GJ6;z*kv$c;{g(m^XqJsQMG1P=~**yrazm$~W?gf-m)twpvcKYk&}5hA=mhes%E9g<<4 z9p*LI4QbHsK+Z-w}1~`Xn zQzD0kDLr!5maMgHhIcxWXMBUACE$~!3GC{)sV&%wiwf|`xg}GW8ZC36<(;E5sgz>D z`8Ln6bf_Z&4)}z*2!v zL`OoV!_P#PMySiaQ%zsaG9nF0TU?w_S52b`Q?GJl&i)0=Kux)XI>tuF5I)Bfnm+eP zAMdnLl&j~x4KZeYNFk!Nq5B*|ytPueWQ->#Jj~RmclR1VOfK zf+0=XTRq9UPjNpHi>6KF8e(h4W_RA1L+*|<*XtA>Ok_7004nbFQ~U85fmaIP5TfW@ zwf@O5QmMnBq(o?QnDw~;vQ8`B$M+e9J2Tej90(#h8*qxN4q(fih zSvg1L0v=svy7`vyF4$I(vV-P^U{9;C?OVZEId>$#4E=g=^`d+%Y-~(=S_ZXmHOn5f zOOzO%>k08l4JXuTA18uu*x*c7fK@`VJvig90!y=GV#%asykB*#q8_EFUwN)h0RZ>Y ziT+J~1)JzG={Q-V_@kLc?pj-ll47j_6-#>X7t9#aPX1hKUwZ|mPZTb$r6twG{2xaL zrFJO(^a3U0`h856!-0d@bMAIZ@pvwR4`q#Fa$uziWfV7(AE#D8o*)T0MmsWQKFM{; zvVh(>^zd9qux`nDnCeDuq|_&OuNfZ(JGdEIgs!k@cJq8hD=CquYZAh@z%^U%m|6z5 zzEs#&y6-8zgXT*!me}tTI9Gec_@YVVh`90m3`vsSQ@u+mE@=1DreId31jqy1LLFO< z(s!W|NPBrvg>5x@^WOy6CHNp5XT4M^4(%=770UKauZCN+P33qzQmJG^@Yq5)5|oR5 zFk0U%hLl8U)M9N_8PFT^1FEmboZ%1BwHfRH;6b>?N-ds{#=OU+ug>df%JKG!Evw?V zthmJZArO|~$H%|Ta620uJt*rv-#{7=d6$n_H?u`3q!wPrb;%#eT*}NE8-1}rO>CkK z(QAoxtLTpsdi8`D*e2&2cfg)O&DgO7OhWW#Cy*rXPRO@TZ;N%k4Re@1K&kx%7=Akb zRB?%U4yCp^@Z57$kJmK2(bSkE(%aXqp}c#yC>`1J%P93yA*Adc^ZHN&N85*vDis9`>Xh*}{7L zB4w`8=Pakt=-De*D!=NrRJk$WD4qP+0}XG%Z{}=#2N?P zfg^PZzSQgREYsKvcnGKDM^VnBq27ZE$vD?nL|t2-V{qUt_&DXJK8Un>tnSDWR(%Lf z7(d@Pw>)qQJm{St=VH8)<{W~9v`O>#@wav=S$VesNaeI7Ird5r`YxQ}rY>Dy(b^8m z1;pv(s=7zM-LK18j?Q_%T)-}I9#sUiW$5{szK{d@n(}^D)EpK7Tu>Scv4e)Kvin&E z1-4cH=8p%{oC}YBT82MM1ak?-CuC4xdD&je8W`Kl)W=ZLt-9Qyc2f-Io=sMdT|ExC zo78g?AtIk)r{Lt<%e@Q!=&&OuR#VDBsB0LDLnZ zw~t`}Tb4&+iJxP19P5A`E5%T43DA7|50tpyfr3_)S2JkQF$n~Oy{DyAqp(To6Q?Nf zWtA$Oo-o!shtqUROM3?rO521647!ZJL6FEBbr&WGc?s_=sixObE&byVQ!FFM;*{vY zm8nTi-H|~-;+>|9UzIRwb`F;>&abjGTVm)PDeF0T&v*w?_Lm?pgIaofi&CjvPCi=@ zjC>7oe=n*(l0MJxzrPiOEB&)@WF=rBSSojkE@^Yk?a|ZS(^Q|Zs*0MD8pHCVq5Vdn zy3iJ6w`%{#*rkP$iD$I$7(t;B-}}gYnnaoM&oC0y5yGZH8_x+dB(S(!TE!d+B}LIc zI+B_rG*j@tW+fMcnqG+mNvmG*==cnY=oYzBs+##{as{#fLD@S7=N3Ngp0REFiJk1& zw(acLwr$%^c6Myrwr$&+bIRcLm6IM9M#y@$m04 zr$~DE{;Z%%Tf#uzM*Q{C>1k8s#Fp=QO#S(=3W3jx?P~THO#CiE+&N(Pz3139d%lVL z;QQ8iJxuzx^E2{&_%PiX`7FC(IP(e+<(JW1n1!bAXP|Hgq8EyaM_rG@C|a4zRbO%5 z-t4#Q+WWb+f^hbcjo6i51t-TYLdA#F9#q!v66u39iWv00JkSiR7tyuPWU|bsbIJC% zJB%f`64C|$&4{t)#-!+cNsKUVY|_WoApJF<57{OL{#t5)DeZHmWzMJ@H5Nk?+|QUQ zEj3#r#CSTk-tV>z&QLi^=xG==rw>h;|V~P zH`cAKiNWzfa92z8Fc)6GU_wSI5lTrLFt%ujy5P!MhxcA_zj{&V?@hr%P&MLc9O5+3 z>4@J5r?Dv)Io?hn&^j3KB#e%nnLqvod*Fe7mnn;KC__Ms&UGygV@_rU)hfhtxD&CQ zL5&bw#ELMx0%Z=a9{B}r%tWa9NsB;KtkqPCWLDV!xoIt5n;`tMNdtB3TH7*$U^?(d z8fT|(gHL!YNPQ%Y59kA0fz~f%bAKlf6T2lAvMh zUJ=BJ_PjAtBDE0R%3NSI-kBDx(gSJmyuvhs5 zy(4b)jqUmD+DuYD*zZO^NA@@$=<8VrLHg!sKQt}^07?6>xQH4%wZ=U3+}s`@NshdL zPeTdXw$T?n?;aRr+V|NmSI`jqoM981ehgcDVQoV+hrfT!vq!{qKK3psQHHKHB2%HY zBL<|ls0829)3VE>Blk<3dFQ~{qkeJ4$c?a)FN=r*$i=9ZI28u4bM`16Cpap4Cc#CA zg28?bBcdr26D`sVH~~=mfKhSW6i+gDUwzr;@MpQ9SRt#>j4sWAF`XL8aXZ+vvP&eS zY1DS}Nmykqn^N&@pf-BgSy#t)8k(sIH)!++CfIsO-KrQ?cOl<;FB|&DE#5B31zj@!H;A&ov;K zNB!~C$G4ZYYSWExHsji&k0OWae!OIqgyQa>=^DwVeF+t3mw~r;0(;_6r5ldl2cFsG zka6Rn^+G6~9_fC}4rblr!lt>Q5#XRVXOYCv$$kaP#GPuO4-}!p)64l;$^b63eeWGS zo%qC&y9ja02j_;hO8}!b_cA8-Ij>?}FwfYn4~6M4Gf!aNw{&=5HtNeBvh$L=2QfyH z%|11%abh3B-$B~B7j2&stf*qM7Y^H!qT@AxbJoUE;zHpbpH~j9?!~iGdFQgzHeq&| zH1i>D2a};3b{fXzq89>W4?I@X+o*4N2o@>zI=`P82y0C@t8VHbAWKE%|J*OUcq2bq z#PR21ia0mTGP--FVu_2lhNhejZ&6}A;-gKruOvJf4h8!*A4>pp+8N-#Ox22vKqSTo z?1nhdG$5d#B@&=`yuMeEa&z^-g`}A^yQ!hOq>!MSSb5?EbAJz;9=ZLudM}i+MYs-G z+@oAf2~?O8)fBvG5j+M=g%QF7Ac5%>$f)cM{JI{4%Zb*ik(H%KiP-O zOXhl=6UgMn)OAf2o!6a_GjE;Vj}Xq*+-5aYv*;KP^DUfk2Bi}W;e{gpDr16}&#Mtk z?jD!iFxw>s>93xZe_SP67kJ&LjJFLBE(QdOOZ@eTnbV>JX1{{Gv)roGGaAY(RTWaw zDUOYUOsOFmT|k#8Zdk)=4z|YHpcSP~+#_TfxE4oTa2wwC6KeSJ?cR6c?7|nQq|i{- zhV#EC?l9MGYT~{*s7FIf)wc89jsl5c#0bmc2lhvj)$|o$Runlq8-ut;#@b7_41+<+ zJg4Enn_5k&RXuu@0l@YdpObqQ6gn*XoYq?(5AAlwib>!0d|k^MEY7$Wm)h(&#AHoW z+$w2+TNaS+=1+Fxy~D)ai>V2fUCvD9V@mW4=npFU_WX*~-Z+(~MmTZ<49rw~TwM4x zrEb0sr4Rz4W2l`&%LW!Ju(d+h*dLl~0H1`+RnB`rEPsN-5iw{HUq8U~I_Azijjwwy z%a*H>zcyTO`5u0|!~G=x%Wz{&7IK5(9eP(y&!Kr-yFS=kw~coy?L}TbI?9Ztz6tPV z8AGl=%Ajb~DC49ftD#^m(>u3a>%=bhVW<9ZWCknv#RI*v`)3pwOI;@KcB+%EsL(hR>0Ta|)8q^~03TUoXNi~C6TX(Ft?A^AMM zTd-ufuq71D_j7pX?_9C_DJUW%GiU?z*gXYINCJ+J8HW_QakQ0kmFN|u4%uVS(_Q;B zPa$Tp7~|I3ANjhS8JK-jELRF#Q(f zA|Y?Z2VOT9NI8zaE(3FWZH71J>;QH~U(vxtl}N48|20yPEh2yu0SzevCqnma@s$<`jC zk$@cX{8{rfy^IvFp+5wZ09Yl)3C4+`o{nxcF7w^K^Uv?X9um?&LjE`MQ0r^ zFwR)`K{xs6v!MS3Z0xalVB+$R>^Zqe{Bx&69SEmk2L=A|KXyp-;qf>YYf;`ZUW``eLS+jZOkoWLYF$mGdKy4DVl!TL&@>3_&%&d}mo{`K4wt!|cgNC|b_SxKGz<9I$q$lsrqV8UQ>n$ig zCbkl@HAzelPaHv0d3vaZ0_|uKr9wy_gWEZ14R$wq9VX<(2g^2{joFwxVP2mIgv!O9 zak3s9N(vAT2Q1$L{oLC7-SVP?{$3;}04M14Kr`62WhHbRMejK#MYt0CM|6Ry%D%fb zRUTd9hzo7_6G+QDJze8)KY2My`8md|63R@CV{|O2vzi-O@%SDoRjQ_kotIr29&uE{ zy%)7<_4lDe5T{PHh!%>EDxrChRm7l$iH@ZAjl%)^#3t*W{e6c>aJ+;R$?T#Obo|pC z18MYTD|EmfXg&NU!5nD(H-cbK%5Q}sS5{W%=AosKF(;L~Y9V_qEnBQ=><+3Beg!lt z7Z_bBQA-c0Hn3;~F+VIR`8QBs((~ekXn*Ec`er}MYR=h|4#P)CUOxlWHYaFKNDY4` z0@?z9oa$r$dmotAeJpzq0#N%94fRxgCAbt7Ft~$adj?D|GQ2|}?gKb;ia@V?e9EDe zq^%>+9LiaRow*Sm4vt4Yj~EH?1TV#^KBZAQ@Q){XO75ZGVrK=0`{TD^(U27Ut#w{-8NFpsH=6 zXDNX~d0`$Rih{u5aWcbx3Q(e)3ofLr3(&?E&QioJ3RMZ zd?FL6uVtK72ZBsGvH?^-He!C(a~6KGg@ugk9u{y|(OCD%l5o|WIGnf`n3x!wNDGSK z`B^wY1<=tt6uX`<FvZMF}}eFycvcpcYe2|zsV+vnZ@msjlrHMVEX=1|#K*e3ep z?9)QpC-Pw*%{PaEuVRuMjS=tCqrtjBCXRv%WQ>`akR1l*OLusN92e#PX|E5c8bode z^WvHut~QlgtmqX!d~Ac_$;Rtq0FnCJ2(}B<>lFHMi#2-4QZK-?5+?2`kOcG#?KB{& zqtc4}lV0%oz$Op))JGHyp2h7)*f%7Zy2#-*LuWK-En4@qqr!|)HUE}levd~+LRkB^ z7X%LOJN?a#7izpwdb5n1EhB300XFG4BPk*on&9tv5$xgFG=Eow80lxjqF%tx!)*+7 z9;^T##EfjOlTq)pawIGE-wxPK&JUA3yj}5`qdu%n7GOrlTxhm_X9O~b62KBUTf)!~ z{|pKpdKJ8oW(j%ENAVylusaOj(A^6I)^`9@^d_6>CL3g{c^${SEtaxvxPNQC2%-Vh zJAr@~J!2)j4oYA3#O=YvaT4Dy#-?sUETSI*77 zASVBZ`eb!e(G$=5C}=_;{n{MoEa#6hT)wjK?W6BTs>KgLd|x>e z&-+3!v_2PCZmCmO*S}acjG9+2{nrE=LQJPkFQ#-R+0xrGZCE#~o2RTh z*616KOgpA7r|>4RSj1W8ty8g0*fwM=O-)Q~$XObgil(fmE+<7uQY38HW{Ne_BlzoC zvaFu}u5FlFO|oUQW#uqtF?tz44_;!`_ef&qDZ!K2l#CXtJS<-@_-;n&aTjsP_BgCUcs11M`uR4V8S#%0 z+7y`hHMI50;rou8)WJxec{hmXnMq)%&cLqv_(^s~+Ggl@mOeV#$8O$uzz3cb9k;<> zQPGxulTRodbKNxP?&WPS9TbhFH8W z=TOx|*O+|=^Vr7TsHG#s`WcO$0v~U4{F)D^c>ywue8|B2zUee^E{i~M(VM z>|(No#+C#+kzO}slD8tZG4yunPW7w{T09Ab`8O({euS^D&UQLld7|%t*!#6N_!GzL zpO=5)1J_gd#p=!AkNU>dIh@~bT@EWxVF#cu9e4F3 zn&J8UF{h7f!@o1z(i$$efHJ;|P@?~_KLzM1&yaxcAd(|q|gxzsj@AK4pK zVrrF)W!T9CU`z#RE^6{qW;EB9Z|3CQFDit2@faq45liS{0PU3WWszBE4$57oZXC9_ zdhs*PiGm(o^YcL)E?_gtW+#1-wy^E6lz zdx$g>-j!P&xDoQ@$9uaWV&o#}6muwI_R-cb8I*%s)6;5k04px-!_|Ceeq69sk;X{> zB*)v`S^2K9v(~D8DSiTXumYa3`E)3-&-uK5_Z~seH>)>aiXSi2Hekqg={Rmq=v3!) zIv7DuNU>9F)apn!ag;H#52!zXQJY;s-tu{6sFCQ88#achmsa=tdel+gIU<;ppdjak zFWlCjG(0he>)K!-W4=v4>;i0ui(tT!c%E$QjK6fagl*p^J|@5t5C$FJ5`iKj2tAa= zy`JoZhWRq(38X%zbjKcF6OWa}g)ttaF~{Hj7+sKVzo-~&q#mH~-=Ap*jpE@O&-NLa z9|yL$TW}onLEc0eEcrNTfXmNP8lb}o1iTm$!L`PL81zz4+Pa@-@JGcSXrE+Z$bOLL zFmh#vVG#2lez8B|81HT7?RuX+cwLg#Z1h9&aYMM)!^pQ+usmxN&F<^f6FR_o3f9qm zQ{Lbl5S~v!gf1eA!R{Gps_(%-OjEa;W9`YaKQ=!#%>O!P`qyNf!}ATiK{@>r8^!fN znGrt}O_9rR6JY)o7Yx$0wZ-8!8NtEW1rsdUZ2|8J?ZM|KY_@!cUg29aX2_AjPTYN#`3G3rF`QhFLSX;dlP^o>emzlhKlY-^soJY3l zo}P_Ron^EFZ$DG}bk}i>XmD9$C*ZV3cxyMD@yM<_GIGPxQ`H`EoUbX(-gCBv!Zy3G zx~iS0N}xPs_U|-|!BmC0!Qj2W-hMTN{7 zO6@{Llhw)Xewvb|^GvtaR?<;K#^2MdVqQgGW&7KcRO^#>02G1FyA!=(*hBtWc!I(f zQ_qrRDh9aQK@Y6v+uSzWqJG~^xnLi(_JfgUxTIrXt`6_FpO7Gx7?8Wvj6l@GCOMqh*HBJ{KZow#CH6vf4GKH2S#9EJz4fhTpc^U0I z3R_n%fSFx9rQ+~FhqU3Pjyv`Y=LDK_g5{(%pEI#lL)4NPRXta0Lr0TcW0d{LIR&9! zKW{5=v`X?rSU9-V{RYj>b*4HtmPFS-^QoN@0d+UVA1Ux z6R>zM9=^JdQn!#3ri1%+H~K9qv0vxgB`$DKZcf}rTG0{rcJv5|XrKRkbWeOuE;9)+ zerHWAc#RmcS3kob;}o%khe z%R*Js0o$}57}($6f8RXOV3xOxk$MUn2^$3)d10!}gX;F44j+!k*y|yANX$k4z5(Ys zypJu%(r-FALz$z?`^i>K^r4yDF|j z&PT^b@g0Z+g|8^Kh(d^*mw051zb56H@O8LDRwK2Y3?|(V$Y{_@|K+E4S(&Zb)sQ>= z9+WAlxad0`bYB?w$6ZRW{EtH~2=J*O?kV`rq#oPH*IJfo%M3v_FSD2R^Gw4J(?<_& zC7oWKhIQTI@2(-er~!;zi~tt7HxoLVS+o`_PJ_Z&>ZJUlcM{57L6)Lw1gVIHg5xXp3_lI7?YYz zW?R|1P9P_PlaiT9oF}i@8&2`)HotZ(>z0%%(A7SwbgFnNK`Q&J3M==klGQ9LgesIO zpewS}^0dFUV6mYP-rmD}-8K+UV74{%+_$=FNg<)3$kD z=h(ojyQ`CIl&qUuRoP(OcwCKMkKXXMd`OvkVe8rW3~DabFG!oSI+J~rdnfV{GF@LGP3ygF(2tNR*$hTO=r`KcwnGInLyplkG6u+7<0>(F&2a1C%Z zbcJ@Ma?NmUa}9AFa!ql4a;0@Tay@W8xvE~@YWP^bM&vf;pV*%E&Hd7TrnzL_ zxM}{kqc-@R?i22l>=Wqo*C(}W`JZI;)OYI_=SRhJ%N2jqKgsCPZ|;x0OFt(@r}Qg> zo7=(xp|BDJ8vJdy)yvui!=9jqps=8ppdR63;XvV3;Q-+f;UwXjf%F0FFjxfjm=bdQ zsVuZeGIbM33b5Obz?P31WA+;>0(caX_j=qOqurt(-GjhxBK9Qku*e{*0lQi&`4@DYm;V-gYlhr5zN#z&PWf8HP8HZ7auwO=ZgMH!a z-}iycx=piB5^z@G(1YPFV{hZu%ly9V2WVE?YarKelQU{z~prgol?Ttl*g zPKP%5`Li&xAL<1ihokVAeaHD|*7|SEkb^uD$YpKWKh+^$L}40Et7bGLFp13ri=_8x zu&tlp=pXlYH`)DY^BoC3-3bZn2oE16H&{I9Q8W${8f|t3J=1Nl+_7fzhdw_c@e1$f z7t9?xJeOUlcjHQ)A=n`mUhGG?rFA5)bElLqMR-b9@-%#BZEUl>Ur zRPd1T2AB7METLs%l`)ElGKICl{%Sh|1kx+#oJXTr zEWVZohHgX+4k#XWe_C(3Kz#3u5=%SwFe?yD2AyP*yqn0e9ZrsAZ|@re=+PMSlKXhB zSM~1Jo_x`>)uQO3R+I2*_U1N&aSBk>3FQ^7Fux}T0(bkt>AKXAldBAIJ_*i(KW!A5P(Gh-_r3~K zC&j(Qh$4>^TCPvHsJ=@9OYK^|Qr}~y6{DA$F^zHl&7tPo#OJvPruj5nbN%$crA?kX z@Si6;XL0GZlCiL$TrZDF|mE7Kui9g`!`EFuNFTjf0Lyt1;R##L+uXov#uII&&WWv84uxvwBztZ?GB|nzf}xz zev!XrRtGaIuVCoRO2`y8rcoZKgRxca`{1=ryID}kR+Cpr6jDyo)0wPmkJ}wT$ludE zbkG3OwA-gQ4Io&uNZMaB&rx)lD(FUKXD|z4E0nTUkJsB%ufxUib_d}Y5Dh*1%y}XI z4J4gj&@BI&Gn_nI%Q9&ri+tS(#ksr1zv;Xc2Sv%`n4og+1pm8qgxQlfpM=x;&fNG` zsgRhMos`fE#y;r${Hqx*7v_?u@gaX=o``_R?uC3`NUy4pT8p`9xVQlB!wzShEh#jS z4A{Y!4w8Bgp}J^qQqq*Cc!cG)4W{H43G);51sGEQWH-T1_+}I#v#QisKcmSL{6bd) zY}s17wq@Z+CYi{~x>=Qg;CvA8mq;w&q5fuGmuii)K?8xXyq2ys3$eKTEj~LZ9fK<0 zc3N6AZs@}>)#>HihlAj~Ax&t?y>EHh76LLB~x?LV}sf46-YVZg+Ahf0i1G|8^zVAauHtVsJle6RP{r6=8m_Q6i_?=KKS(n-#hyX2;sL#b#%hSSp zht=tajCl^{j!5y-J^!a$>K;8Hv+Q#4TMV)u$`+ZBlv(fTPV;ZVB&k8eoE)5I&TX=| zc^iqufQ&f=3z3bjw=p+{e0Rkd^&*^fV{vMyGKU_I79gNLA&&5S(!1w@MI`zz>;fUV znl}4e=a-Jks{$!gx>Rn!tCFfQiayPlwWIQMpNxDP*ci_WL*5MSy8#{9=Ih-n?3uo? zbiE{?{BC?)vmkM>1GQC%wh-~8<06O}aPtzJ?bFX=$>q`&cT>Tw>scZIGG#q;m@|5s zD9s0b%F&qaK{M{sL3o3ClGsh5P67{whzNJv7i$ttl*C~l#k^Yyw8bePE{-p;UX=)@ zOX79^5U2_=PBgIO(Z=cRDkr7Kc!3QR<>ei*yXb8bTP~WnGv3Qnue_kc(I13!koCxM z*C~uH+>R!eI(R3TVj~%Jc~CQ<5(sIW1@8!wi=Peai*?a2s8n@41A> zxXoDJfG)x_A0Zw=-`d`S$Yn`3o0_imDUZ!2#jOd-J$Nm`vF>^ay+&M`6V|JjP|Ov=f`;X#xT6d6*VshbyAn)7NCSyS`(s)>}hvzEZmVDou;#^9Z#r zxa;#XBPY%R%^l~Jy8h+%wz)_+%I>0(h?{H8EeeKkqr%l+aR{GwM$|7TI{!36*O z)3TJM8bD1=Pr^aRUqlqd@G_-7$o$d&#ryp3bNg7aTmu>Un#>e&+C5 z7$mMnL=|+&=Ls80|FjhM>)rqneTG*%%|4s5csHNkzy|TLi@+xsVA6akAP$2VLP?=bal6Q zfAKG^FA*Pq4YQ&WVKt(w5t6;<4SrAj_^}9I6ZyvMh=go=yc@@D<%u07o@?Jss<=mN z&(z+!6BOh>6g)Xg{MDQdO$tgLg91|<6~7eWfR`%#u#Rz0xBG=#PP;@2W@k8{ErFog zTE<(mNk~EMknM@N7MmW6T($y!qs>g3`4AZ<-aIJqVq~SP0|!oZ#!(e$31@q2VSRl8 ze;bF=|6dE;4;qbvl=Y6?=@ghk<+9aw$!^MVfI`qm1OsLt4(bMP)D0mthJpO1w?OaSbjHj{5qd&RQXT8gD3@ zAx79Kic%U8?`dGRzOC0?r|G0y(X39N*$d7@{~v2wO5(l`G#$rWZuF!1_OPvMjOM3s zpizC8kH^g)U=)r@wI#*lS$NZhVcL0>Hi;IsYY>kk5-{@=jjWWsoPpxv6R zj{F#g2fiS^;IK<^ae7@2J->?8fAHQnxA>ft@kM7*EysGGQ{q3n)Y2%NvugS~Fu7;U zT>WDC*iG9D44kali11SI3br}RRrE7SmqFFR=Zz3)mPzxUonlJTqZly-UL48!i)2iK z)n23MZ@#413dNnd+sJc(1bCOQk45*|3q;S$=hJ6Mu=+q2O7lAwg%hV;D2fu)ilo0z zFn|*5aUpOk6bG|EOMZf>fqJ>bm<+;7O%ZOpvlRZ71Jrzd5l&4h4`xctYC{GpBfCCxmg zp5)E$zzkJnxuRt6dwc9#O+}OD(8&RmE-2hyBq7ePLAbqN*Q{`i(D{B~OLU3rj=~n8 z+Dhpa3dhvw*dX>E2oV9~$dR1>rbLFKBkxYO{kC9(Wf96dQOa5CDOcAP?KZx`NM*pG!b?=A@rfKW05G)pfk ze+LxG6SR!o&{H&%dBn_ps?}sTG&HPni)eV$XFEipuQ8;@1eNxd~t&r0sN~l0)n!QxJaCmm;jw zGB+w}{ii$K;>6dq{7}YD#L_#k!%)O%C|y>ynNx zS3Bcp=eO*0JB#bHKFWA`l?YW2>ZZu4F`8NUptB+Lq*9dhcryq{iC?NJ{g``hrC41* zi#YE*Q$Z{XdE>s{eu%0Om)v+&zwxSef4HsFJTsfe{VhasOW|2U=b~W`E$s8m%NR|= zxGe+__bIF1W0)oTU5#kV0-5 zDq`(tjK@pOv*0nEx*m}rzg1elsZ7DG4$b0pnAZ{HX`o2*yDUbcof*+r7I-B6q+Dc( zoE|k+w?PGM%Hmeo;;`Ls^Ewf1osfii7BU~~Nx-`ceZoC(@~)92BzYppi;)IkdypAq z@V*eLjk>EiHliL}ZrX+s*AQn!JDy;=Y|-<{%gO7A)Dx>$j?n73s?^HVE7AnZ;}xM9 zt90TlOi0=YgMzIli^D~xVXo&l@FGb^zIbq->m)uAOz#3c8nyBU`117R8~6^!<_s=0MdIiQ2i^x+AIR7E^D*MXa2>PRs?$ zvZC@{e2FNdRNk~8j1}fblA9X^>=MI)4mxq!ZPP*DN!SIk34yp|uyJt*N{!J}z@Y@b zpFZL!4JZdGX%YglT=flXZ}_^rhvd?(_@0n+WPho`FJYrQ;u9(ts$HZ%_oXH*T=h`>$3nwd+$b(Dlm0@dtOFZI4mZ*dUe-+d+$W^ z=kvP@bjkGzpcrR0=Vl($+&#l1=5162k3LGB!biC0@F-|=nk0& z9HQmVA8jX5S0VO$wx&yQF=R(v{$j>g9p-BVnr||TwP>X2a1rf{5*nAii z0ZJmHC6Jj3`FPZ!4GO!{p&h9iaHmi0a|@W8!`L2}eG=Gj=;HqiGGzfB-6rv^vI^85 zgn)kwy&6&k7Lrd;N4SY57+S8{M%0n?Rh!CEz|$HB3NUOn9uimF9X$L@D%8#kn9JQp zRnL|KB85z<%TSN>f(DARF$9JyVs6aT8-sdt0i7J|CN7d;27VoM53YuRW`Hpb&@1$Y zC?FlBAg40MRxhjxoNHbDi6AaJ-eP>0f3qVk;TMj2`;DZdV=`ZZai1b8TB0iBR7p(B zNX=#5A}Q)>1e50x1T+rE2dbr1YN}_e7+OsEuj25~BjX+F2V;(s|3UIXAr8NX&Mr%Q zRY4^J_FPE!0SDt7__M`J9!;&9wYXs4Z%{uVW1nT2BL_uJ?a!G3owP3_cKob%&Tt~# zdj(#SD0DBf^;pV*2e~}!(BJ9-@{jwQHAq*{oA&`*@^gmHqg}ynVTr(xh9CBJJYoph z1#-+!R)Ql1jbB@ntYjZ#LC}o-74y9TRm)!>fS9I0WQ}FS;O|Psaml-f7fyjd24p%y z)sjUaejeuemcT;CPR~N<9t(2pJo!hSn{X3Lqgj%_)QaA=BE=uh842~WKMHUaQ$LlMI{NlcHw@?@9>LtgcD`xz zH7y&6-*_dIbh?p7vs^vsL{siKd?CO(fhK>?3+*-b0mgafoZcVi@3|ZOTLzc2=MEr$ zBdbtIxTsKQfFx_3D>HK){MI@UuJ`sAs1Hv3;!uSn0-kkc)pCqxnH!cQ#g>u`H^g6hz*j zH+Vr;7=FhaqZw!=|6ZR!eMLI?8hjn?Z4SQ6>+ZZ;8=GC8>iyHkUqW2mrLYXU%Ky>- zX=M+Q3C}p>+l=YQ51S9U1hJ^TccH$Et1h~0g|N?RD!E5!s8Wj2yQBj%4P*qsJMB&+ zeML2(_HJOpgG8K&uz#~5`UN!g<1-7?|M|7v->cwZqKl4279{~xL@7-CiU=1aTWJF_ z#VsUNxaz+<*h@()i;%8V!p6(3o{}?cHY6VS#z9*Wx44@1Yh*8KWNMEeI4c`S+$t$N z=q}U=@`CtRr+^@N+q&g40NhcEMHjQFuV5i4#YI76>5C5cqKp1X^ z@l*&ndi;J#D+QrdC=?`a*L6Ww!~l-hz6N;VgIX4$k&ZAwZV>|LYEOs*@~5Rm6WIPZ zM>xyd3q~H8M~0We2dFEsxj@&P&-V(fbcpXx3uJ7zR};w1%vBV(w{cbLt+LlRsN|kz zxCPF@+VLC9-)TtJfH_qUCLH!}W(Z!ocbJ|yo(Nzc7EIyr`0$AGEjT>fTEMxN=*~48 zE|Aexrh3wwyfj7r6)U<%7U*73Fs1Viz{mrq9tN)sinfWE23*S5%?6R~ijLfCuxWx6 zuv(~~4v{`6AixT96#%@Q2ZXiGKVbu8CJSfRH|9rk1BCo<>`y8(w~HZv{;FT}+z9&rfI49R zU)SybKT!u#;>sFY5;USVHYT>thSnyIbWBYDK|5gPB%)(u;Ur??WFcZ=;$;1wvd{mE zckn-={|P+!KmGrUc?_y$h&!vAf>{@aQ(7~47jj|681TRUeH za}z^jCweEw|8$uC(_vA&s&jI;c=&&uFzVPoQCVMZ^^%KCptA}}-l z??{CIiv0gZBCvCCvi%1Up~@4|U1h1+KGfcnjS~q}@-!P-RZt*O-a^VQP>CJ@1!EDf zkQgt6qGZb4Rdq(kLPb^(j3aatOzt?8=O`))K3DPkXp5ro`jzcv+b>7bT<@v-#g9Mf zCOf0+SAwM)JEj=QmoL@jG?PlgTwA3=y zlAAyO*VP-VU{OLbH7zbSIWC2d#mUQxPma&ZNlgssZFHfsSc{CVoD6`*o~!o>4nL;>(2^PD&=yvScdL+5(3+g1!t<7*S_t%J zW=G+{PN?;%VJ#xR8nVkrVH(`?K6}M)l^fu_HGdmnKk^5^oKViK!{* z*zKA~NS%g+r{h%|wS=V9d|w4b-ref?P4UV8jNALEpys+dQMjK19L^H_`fmYSj- zRWYuVoS4#Ja^G*5DO|KTvyd!ze~lOKM)W!Ss0|Mff5x+zk~YeXZ%k>jz{FpoW+oB( zouC0As*}7#3H!H{aFFUwrWYZ|j&Q1t>_ZYN+KF_BYU|yy)T;JWwH=u~ z@A(g(71fsC7~`Dj-0{efgGiA{o)@3ZAt;{{&NEk6F2B(11wZHQ3XSjw{EO!$hv2rF;NzP=ET2M047(8X@0vwU!ik{n$ ziP=N|A0`D{2xPWfy%{8GFWID|Kp|g7=d6Kl6kN9(DR|UZ(?xNJOAwJfYEDQ*Va;bY zU_^jb3_}Srd0eSU3250(ihLG}UPY@^<*)Qm3ZIqAONOLxH1_(l`zMRyT+)g!SHSah zS(niWRn1V)a>0`&w9NoYE3I8B4W>jTSGhtC-<$Gsd4J5$;qLT0#L2F~+xx&R#w+t* zA}AAJ?^asdq$rv9;3#}`jiRccDKa)-+l5mucT{iFylHU_eXanO_9s`+Klv&#h(%kc ztcmbeZGXM>*(w2y{-S1m)+oF$j~-(g@+4=B)G_jbKKgu+WSl;#_ZJ^(1SvPcSKU{@ z{e^3gIvJVIirDR0m**5@M`om?#t_e5p|AQFwSfZQ^t8R^)a=;AX&t&f35H6A%D zri&Tu6suIqy_K-5Uib}=V!PMMSyHo@H$VUjm?|3!C6S#WU+$2nz2j;23#2jo#l48e_ZG zkg|J?^fAJqBA}KH%M#|8YPfc$PI?OVbq)B=2$)lP+P6tyzLCMp8Msa@wp*J2)tmg`?1ME#r&yOnU~`L^=#zM?2G}!Wluqk%loyg1bm7gnxN?^BQwNHFEnv_R%Z@SkOjjUF731W{k^YDm${Z}cg*cY)Ts%!6 zds>b)TQEla@qwsWIYH3b@;lpSKQht;i<%&u)`A_?@D()R&287`aq$!Lal9XoK4eNW z+sY^9e@l)nzIxv_m4~VzW~W1yfWfOtXt?w`ew|aCdu-?#$+a%7`93m+{D23S^*BF| zW}_v=GEfeXtxca?xE9;m;d7O8*+JL)GVWklv}>;GN&K!ZFAg`3IrbaXhRz~W&TjBRz>N8f zycS_VQ+@g03$pN4RP6TVNf|$Fun#NsmhoE8VWW7LRKQm-G;buLsAJ#n`&W{(NiAl& zSwnbXgC$3#A84X1gRPX-VfoB-cTDiJxoXCur~v*RAtFDcmM^S;8$t1N!Mx%-E4L9} zS^P$}+!VC>Vr*;j6drM}`PTtw5fg!nrO^c5B2i^l*Rf(~3BLYU;25C_AyDxm9l}|t zJ)lJzw&JAK8|6w)?07l7jkFe%lhi8s{y98ih${S=mh6A`MVDO zX{>gkCPs(1(RXe5zNDD@_E7g_pWVf~gPt6zj)m(TUEiCoGKPlh`%|CF6~p%3S}+9V zp9BfdV_BIB(<;HdhR~;*7LfabKPZ;HCy#BsS&g6A{Zh1AyV|!Jrnb-g`Nk*(DW+H^3A<=+Y$uKf zO=45-JXg7kpp|)7+f&9eVas>K#o$;Q-DcRlS#eT{Q?XSEdAYi$jHl$XTq+SRnU~^o zrVIt_=yu9i4AOiG95F)zU{G>8p5~DIUOKClEo!~e&Z&FqMNzkqTx2S0Ji<|QNh~Ms zo$>s}n}NCsct$C)|r|N3GxxKl`=B3%E<|^%Ky%BfIR+d}1!{&-!OkA9w zu#0vb--rA?>C{U;+C%7Va2t!y)Ivs5hVt0pBlk+*x~Jl+{Q1WDbkR^E>=I!a!Hc^S z9~bpQ>hfMg`?75{?;tk@myn%?p0}D(4)Y{Zn@}CIh2~mf6VrjV+wXJ!weVHp^X|1~ z|7Xs@VB|1%JT?Vi-u1It*}IH0XP*0v>y2C0Wqs%DW7>zCf5$`4%;;n)hrzjfuB}{J zC(T#a)LC*QLv zedhZ5>RayR=Nx&hNI9;XH}~dFDQ=%2mSBnZo3;`87$!tWVU<(|>3Wn@b5W!+*n#$% zI;vEW^_)7U)PdEOq}?$&n_=P2IS^qv%f5^o-z{fs!I?z60X<;rbgJzniyAXVw*Q(cAGLjGW%=Zf4T}a z61X0%LY-FbNf5>(6FzxP_mSGHB?$C#cg~A7Kjbk{MIs3O2;y*{eM}1ycMR}z_5jl3 z(qUSKcfUY~1ysRSvsGvGaEzZ;N|4^W-aWcr=f1p0hap@7GE>STKFwGuz!f(Qsb@~P z1L~DuFbMiO5r&xNN`p6`W`TSa7oZ^OPTtmw4$-Lk>hn>W138mBu0_^+U%1q!8N8#{ ziNu4>VNR7=9ki?0V5=+5=a8rJoE@d4h>Xz{b*+_Oll)2SNI zgY+P?kv;E^V2Ra3F(6Fc;_9CX8gAJMMKwccRbxmIrY^RZj;TljBWQ49J5035d}{#f zei>Z(c%nIew*OTc>M@b1JU_XVit~rq7rTG_yoGp@q5qgcUdjioxsmv5wjfkD457+g zquqHZvgFU+thpMJER||sLer7PT0V9qIiW_!gcPs36<>OeiAHU&&;Y7of^2w)G^FY zTpgkb^W*K8IoucLuGf>*+HK6-UM0OX48v+}x(;_-oHZc+M}8#o%2Z~~5kt&6p^wIv zJw8^v-#6*WPS{(fi0z>(XOG^}UYfu>NQ>d%;fMmg2QZ%$OUnjhT@YCh|3_=@9cE>b z?QNR_idj%Z?GY7GLhrmc3K%hgBBFpeBB8;SC=Du(0V5{HET}j}%mD!%Mnn{ILdOV3 z3>YvWIx2|p-qmMb&-A+azH|O~=eowLUDmU!o~o7Cuhui<>1o&XJpPr;)H|;p`oyts zKXu}?>z~~J;+GEDW1SDr>HB1tL(aJV=Ba&-+32mBl_&0-yWx(LPG0}o!uqY=Sbo&p zQMEg4b@hpjjv2b_ql;d7d-1YUH=6rfhYeOdv&)ZPZ(e?M+h0%Vxl#43UFSUTz$14& z(DuPy_uR8nr#=gRytmPi6L-Jk*RXX%d{ZS>>1$DK0$@@H$_m^tE-51X&~>Qm=U znf}G@ug$4x{qY{}9=gfsU$<{oH@@Q$58r(HnJ1ii_%rwQt3GdPuNPqXa1>UV6TqkA0Q`;J*_G+Fa6{g0k=(%REH zwr;=nZwtTp`-oLv>@&CTV`IJ>a`XKgPS~MK%R4u`_{?*A9bS3xx7&Yx?~@@tFW&UA zwc0&3>AF3}>^gVWMw1WewaMWvCrntKi3~VScUvF- z_f=nh-gui!e((L4X3wm*@XijG^#8s0Sr?CJzecB-hyAwEirMS_^za?)zP@Tzt9GB4 zy8mt2M|bY^+0C~OT(M(KyFYK=eC{_7kL!47%j3^mddS1mHmFQ&y|~ox>Sy{F&beZ% z^G9vcXvV#7EN1ayqxbE7v$80+Np!Q$y{%P$l9iFZA(n(bx*WTOd=Hs_$wc_Z`XANHd!bx-HeYxnx?3BybzUS#( zE?;wTiwAf5Vf%%LwZG)uCGV}+B){O^Lwjw!_`a>i58CbAVT;O_KKb*NYg~SO@7@P( zvcoGUm$!bqck@U7de`)KX8h&w8<&1t8TI@Xk9K?fwaMq)y}?$GH1BqKt4Di3xWVZ+ zZP@DE^=99&;@nB++}OWS+g3+y{X*~S@7wM64g2+-`PpXQuJzf4gMU7!_p)5m7nj|> z%XjNl-Yk}Hx%Qi0v&O!4&%;A{PI&a8B|qNu_^h8w%_@xt?>zL&9iM-wYV#xKEPr~f zs|S?IeOJ}>+T^Ai`5H{m4*xo^|Chx9t=4Kk{^wivo7#41;hJj-OIL03!ig8}{^K+A zj`{V#gPIJ#;)Xx{G=BVMKkv8EXA4{IanoTh_j$PWSO2*2qr%(Ym3RAM!MMN8pS&`E z)W(Amyu&6})y%{6PSdh@DR8(;U&nLjQ5 zXyw;$wEO6=z4AxQd*h7PM=su?&&kuzo%-SE?BJ7|ee>ld$4{Nqb>`fW&kQZ}`*7$c zgC6bOZ0Y*lcKiEVHKVq8``nK04qGwl{sYd+G}~Za>D9WAd%wQ-(=Tndf2-Ulw~ z#&%iQ^rg;!zTwBO?tEw72b=x%g?+Cd*Zue#rX2F{F&Axj)9DvX@AK`^>vz8GsSD~# zXDr=g-d9sUo!_YKC2yQ}>%w!M8T9POA8g!Yk4-+<_=qlprf;}Qu6>uoa?8hFc180| zc0c;0i%(iG`Q&|Po%QnA$!*(R|JLB279UYQwtB&Y;?+BJIr^$~r)>YiS6w@A|Lo&$ zPxJp00%9f$3G^Q_zZ?f>}s*$*x~ar80A?)1#mxnI@I=|1<2T}!vE z+vVh|4$5qN{Hy1;yXCy!uKlF%*SW>djQ(-GU#^%{>Nvlq=QiscciYz6U2$-B!lqph z`6Yk&Yooi@E&u$it6u#5(`8?LJ#*m$%Nx(?S30cslcVQudci(dEuOwX*W z^cKfXo!|P)ohH5V@F%ymZ`{3VN>$?-=gpZuVfu;pH9Pk0`Ag1yQ4UYvL=@uF!+*R zudkc`?X5rFvgP&PmY+NGmEPaHvcr%^j(KgtX3q?L=z<$x9W<)fZ2adI7j^xl z`R7xI{CY;4Yu~uI>%i`Hixxduv&s5{x47}?>$bmj?W;cju&#UE!FL?|di72>O?zbK z%U`y6f9dCsxBtuiXAF6Ja(?dNH+4Mav`-$N@kY~jvrl>`GxpWfmp$0L|KIXkop90y z4}ZDY?1^>HX6v>+?8ki$yzbtOyLR~I$hMs(m3JI8_vL4o753Tpq&3&xVj8OTMa`yx_qf?s=-iEgyZocbw8^NAH*B9jbNf4nteoF>;bYyp-_z=xF;nOD?Qz^8gSuWY zVAPr0EV}r+i~DcX?S{qYcb>n^{Hc#WcF(c1k6QEg(x(?+@WQfo+w@$}r^hvGjQz`) z%QyJPiO)QG<=iXx{(QZvQ(E<0cK+l!dv4YEoynu7uCeFym;HT*!~4u0e#`8a+idvU z&|PPb@BjL=^RBwK!{iy&6HdMKga!Mzzc^Q!P#oH!N3#is%${$vcWs-iFW7wS_2qN+o3sC`JMVMWOY1cr zzS->8F5Tg;$KUwEW`~U(eq+;bpV(r=sLw~9c*cqo{`t`M+th6EbNdfIZhhjC#gjk# zVV}XzKKS|M=LX%rEng=q?tKuo>_*=&X#3TWiCy36IA{NP>ulTNxkDD8x7pRR*6KK? zY1?xe|GeGTjZZjy$`;>Vara#xOsm=N+oLO=tpC)nkDPVchz&lyckZwMc(~7JeTr`n z-}=-&dt5#2*=6^1KmWzS6SC*N_vOj6jvapX%H7`E@XnS4Zs;|%du?I8N#lo|e8C`*)+f4nCnicoc;Uy$8_t`ddfcgRc|wD;)Xv> z`sSetpAOt~pRL!_lI8k;9>84J>&ZB|C+zyfajjtdz-PDcLub|4eQW-ahLmA zcU<$m4SSSd{9w_To*#BN?X!(P{^-j&e_Hprv#-8##+U~W>s6{NU;3vOZM&}OwAQM( z7rfMSQMX|aTyWwig>#PDre$H=aU&jDyvNH&b=&2RW;e`w@z~)@zW#mr_Iq}?;fy_Z zZ9il0lRrAS=f~sb_B^(Fn3va@dVQ16 zJJdBfbKDv4?fc&3J9etue5-x`cI3zHrk=2U=cdb7U9{bHC#*le>xYN${_5YRpRl6W znw=-_(Cdl=PF?%2XC3qH9@G0?*LXm`<#+Ep@tF}H&cFGjo!39=w|-l$^KJiScXjy7 z1O0xvrNw}I-hSfVsrQT?bMWHHS3Z4kuS4IiY&83T^E$m!es|H#37z*ne@gS+FMMy{ z^hVQfpFh0W{YUTj+7{YQKVqxa|8Q;JE&9I&NbQ(SW_@?uoobYVZ&o00E zjXmqm{AA)TO&6}QU{Qy$&DTC7v&N*WHhOOR3oh&T+#S;x306xR_zA(8EcH!ywy*p*aZ}4pA2{jZ>zmDb=aDB@zO(7p*PgY@=)>1LpzrMUuUV(%oZsdzyZO-1 zj~kcyuIahwz0)?|cHFwqZCCE{#UA-5=l<)i z>-N3j(R=0}`0JRPcW?dnX2oNled^LR-|Dq)-#K-6pLpcR*|VAypE&>ajV_wH@gY~e z^wybYR4rV8+VtV4T|2q7-zHDI-1UhC!}@&0(3`Qq~qonCK0v&+x91&gjJZ*bT)PaXGkhs(aX>)jRG{&}6hyzs~^ zJ$p8~_P6oJFQ2|(+{|`OA9%KX<&3RfdcM!F?7JgdKmW!y(_WhK{PjEb{`9HGI$ZwA z(iYtxyXL}$_dhq{jWHvy%9}$;c)>?!W7@1>5Bp zo%h_JCofsHYWaaZX4Ovo^UlN9zwF8To3?v!9vKp6qdDmzpC_xwX-UzhC(H zh0k@GcH78rrw`ry_A3j2-}Sq#zWHU-hx#|~bM`iO96jv7w(X9)bK8gCyK&OCFV8z> z&b<%3IsU%dr)Q0;9KVCDpE!H@^0n8`F23r@qX+HWZ@?C- zc%w$2PgpD4fBBRfO6`t&wAGw7-}>?7s`0-x9e&*P#r{vvsBQ7tirc=-e?IS*Po{tW zOY6?_F4_9bXP!H2`HFwO^>K?;GxmLK(BBj>q;s z>8!4sbUmUuJvUB$bE}D44>;zi+BWA;YBOoe){}3&y{hld*Yxc^`<8td?a`*qTTNEA zJ-9Nda%P9Rkw4G9xwy{O^Kb5b;%i&Bx^VFO2d)}+Wt)9pyWz48pB%pOuba*MzVG>` zo>#N~iUHU5+2Fk1E8DEM>dtj`82Q>(yZkm_t?iDOyR=E}nysF%J7VsY{L!=18soQK zvRU(^$8GTQy5;e24BPbZPnwLssCk#CYo8gk@un?0PwqV9#Cb!rz2^RXzmun5)q2R& zwd;QI`^bMhQ+vUx>&7f>e#dXqAA9QHGw;~`>3g4D(&XNkzNy-9=92l1k6i24Nw2)U z^Rug)xn!j6{ z;ivqbd2qYm_dM*B-+S)W>G+d=n)C6J(>6MH&Y&;Oy6}|mce(EM2RA%_t(Dxi?fl6p zvwwQ$%QnZI`g^8j^AA2b_m=^qci!&#&MVJ6e(7aH?p}Auj@wOII_tDWtKJ#);ieO> zICt)Sdu_4LZ|hB&(R%XYXU6t8YLgL_${7>umUTVu-Wyy0dc_;-Z}iHeTTHvTH2BT( z=;F{d_b86qWa4|@tyh}2e3KUccz66pAN`!~+vfU%k7zr%*sS$>)eG+%aq<-hcN*Db z`~i>Me#*Nyp1g6}W-kpIwaIPo?R4c6H;>%B*`b3+{rve2r{B4C>5^4lPuO+v$=iOj z)3%qkYwwV0>)R>gcD&@6$2#7)(^LKCSFPM~{#KJOZCu`V z)(87;Gj7ht&o&wNQq{^4H5-iF`{DieI%W2PAC|md_3N&O%{cL>OZ>cA65fRw2J*{V!++v;rFOiLP8)xX&PFdv(_`ekzh|6ccAjA`F>#K77? zWB2bjx~5~cI-6^g$+pQ<_sEpn=d$e!xjm{g?W?QzY8OUwT0FYns2U#o-#q#sBmd*k z5u{Q{BIutIpL7 z9Q40EjDZ9GSjWgQqlVV=-9WxDGPEX5S;u)~SjfNu?FWq*HLTxgIDh2Gp|u10C1z>| zTeR%B7yof|?dYL39qSK&pP{4w>yM$eiND$p?KgaIhnA!m z61#ICiQ#wKzkQOHOg2-UDd)2Fg3_|Ox+TL9sQ+Qazj=GN!1z4dBc1SW$L2~!{$Dy4 zcB@POsQKd`1OK2^rN8>6e+*CmsQ(Z3w4v!=>z}Bn4gTX_N2R~&f3JRA`}D8%f7R1Q zr+=*xZ0_Xk*ep>Zhz9SN}x)MD>5wk86>JWYT7h7&dZ5UCpQlFEcFt z*ZQ9ykp4R1kKe2RmHLI(|H>(UjH_S9e@|3DZ~c&CcS~dXr+?HxT>oF{f8oEEUq5iy zG^GBo`q}FTpZdq|*RNy5A4BT@wf+a{=Ny~xkA;Ts(M278q$7nnq}QpV#d zMU5-s1jT2I`9i}zU@gUBrQw76zRHvuc|YZ>Ov{5xl&!u zh?c1?n3mQ1R`}>ueBVl@REqppL7&Gns${dpD3_INp<3^eE#>3>Rd637A5?N#uO*k) z-dA#^DAR~mrKtBsM`+%BUVKo=!+_!3DtUe}(h7)@X!)$>C=~f(TSGpD4SYt$OhL~A zVvc&P0*mWBish2#C}D8pyro=GbCinGot08Kuiqi6@~| z3gO(4qD<6((DHe$FIy~|R*CvA%ML_c3@sn|9$GQ4_sF5x;vQh01K($>vB@4+%;-Gw z<*4rwz-qs5As6f~x}{L?S_;Gwaec+=Xrpiha#0^Z;|KL@xmx^Es*W}}TV2YQwO6G= z)WwV|m9##l&>Uqv;J6lya*)exb(t?XY8;XoX((R(9!kvPN*Rws6NTT)u>J8pGHgra zzf3hsN6%IygBeU?oHvs}8OLWcMe!9^IwY%TwL&}(yh5F2CTkwgWXl!3M=m3o&g61Y zw`Un8{>$X#3uH5S`2x_QAB*JYi`DUrGIS*_~nR2$IGcRL+;~X4^_77)D`&S{1 zh|gl=wJ(*D*IcPYUo?x-);^;sqi@DI9N920o^mnnX_h2n+*25);BiIi4NOY(C$m|0 zFz#PAo7X<$!iBhp7t7+s#WiDR;y!0{FuukWbspJVCE9qFl8JFoT3+;(p%p{i!}{`g z>2cmdMtTUXD;~-giaw75PJjG8%$v@mn2SC&+gI>;6bUTi@0D=UMayM1N2%a_F3GZkEPo z;^!Q0MC3{GOY&86_-64v@D4I@%{hFtD3>{;HOgBK-%Rsni_*`zY(+LDmqUw%Jw=OW zBk$&Luj0Ao2u(E(nbDf_d9N9n(fcAZ@yy|7?;kP~Wg&;m==Ta`+2kBDqx~!9yno1y z$B`$DdsV6y|FJshrd+AuGe>5`f92|;_OzUnADb%|VqBKRRrb4~*0fg@q$|Ehr64~d zS5b_OM?=IAp3PThrJL~X<=^L#naCUY>Wb`Js%=Gn&LcCLH&e>S^T=n2MC1A}H1RC+ znA*rMaJ^!yyz+2FLdcBXgK#0rM+!;e-eW!#2jr0%y&E#4_sAEe+w=LdVwZdY&o1sk z0jE&3qWC#ah@-tP+IP$sk)-&0Mf;9;{NCr1WiU^2Q)gL1fa4q`%V3_As`d(*(HWr* z^n2t0G%pD(od-Dpy)QWc{oZPF0Nmr2-YryTrFW4S#cu@yxX8N&Di)DT}3eHg!3TUS|2P~w$FIWbXIf}edC|U+d!%6QJ2w^ox$ud|VZx-de zK;BGiE)kT(_aJW;^#Q32`LRf#V$MPt;flXUI$OU-vee_)1wD(=3$!8$vB+!1YMg$J zBT*w-NwxyBFQ2eTFdp}cbf;vim|;`n94R{|ntjJ&);W|Sxn{iwxn}JF&YspsuG!;C z%C(T9mEI`kiQwXzaUCO{lA%zHQ_NSy8(1UR6r@8stXL>|E##UtZ!zP2F6OHvzZ8pD z)`ssv;Y4``(xTEscq$}^8$63#at?_^*^9X%v4CHjVGWE-QB7h?EO2Ws9==q+WIAr5uZn^X7<}L_YmncPg5)0rnr_ZGJpq%$UQ`ptJm0c$(RdAf*Syph|MMLR`=KK|D zdF3d|$V{9AYpGm)Ducy&$;halQqGX*3Tr7NGf_8D8Pl1AD$xFwaTB9oSI%NZ;~bPL z;@+pQHtq}BS+Z5mk^G9kmt&SlTaC;hsXEItG81_X?1FSo8JW?(ATxTlQ1DtPH)@|z zCHlQ$R`$1yO4OOJ#?GMHrT@yvjP`jocBWi%&bQ1KXnok3Xj95;pvO7qTgJ}lj8cKSo z*=Pgt6=@pn3t_18a21kxQI4?i%6(OGt}T+4myX~#q$4W8WVFwenDuT1woyJv$4bXm z2)H5-p z*|Pm&JV)tH*j~DmU6EW?Dz*!hg<_mX+M7aW!!<)AqbM2%T{N^#q)|-Ovzc5xOA;uB zc$U!6?(z3fIhq%>7W7a`)kk@QMxieLUP|DLrdSd@uxf$O2%h7!xUqp>m_e8hXo@AF zQ9OyiN18_Gk&-PMmydFp18Nm*H#CYJ@!2B9Hqj{didORe0X@>*1G&?)IH;NzXHR=y z5=I2?nL>M9OPNftXyS36#W{=PP}+K5L^bFVN;b;Bq%uzQeQ+Yo<9IL8_AssxV?=1e z(&5NgM|}lNwKiy~wLw$Pk}?zMl&}Y>^c44=G@)caLkTUOZw5)yvn(&32Vx%MD2x$_ z+qf1A2a#XULNO+xG?tILv%Xvw&y9dT=5hfGD8~p5A&hHbOZ9sN`6^VC6%!{UQ_QtM z6GkLeqGPO?v#1E2 z5s7NO2PIPN6`^0`Q?df$adtW8$8#jLVy+5}lkxrmL5uo@goODI7>~x4Gcle9z%0xJ zG+`#7;lxFLNkN2YIr%hT5~SBi`Nld#4o6IS4JSnWhmn{3GD>z3<0Bo5v5>w;QEH!2 zSP>5bu$DVB;eAO{d(Fs9ln)Lw#*O?QUTgfl#QCBDLKBUwn`jl;ZYqo3=R6h6_-vkR zq-YiSVQ`E16mQpSrjQfm2=u-8kGyx(O+aD2e?{FfK{+c|nge!)od?06&KH+(P@M-?ek6kl z(iYDRWUXuk*AgUyR2;m|Tn&n{O2$YrA_xrcbHVXPp0H8xRs>KO&y5t1_JuT(&bO%C zD^)dN2cbFspkm`S1N+w;Ws2`{eL(J_{m29B(|Z7e693^&nr0cqEjFK4Y_|wGW9|!X z(OsUzLGt+v$V}uHFbRq!fjdS_C90E(eApK-39_NM^77%34(av+4ouWT1!P8ZAij}@ z@Dg=KSV3C*H@JsERzR5dlqrzkgxe&49Asn617j*+wm1g|7kPuUvg|&YYsoK}KIzi} zG86eP!AdnRwbUpxxOx${1iVgpDT-yP@j(L(7T=9{M*KqGe$9(2j&=r2g5;NKfMQcH z30`wq{wX1%e411fjxvqHR(_HygzB)#oJ+^D^@=&U@+f^pYE!n15`p~0BFUw=f835z zYz6W|`4XU8iaCqafTGOcg2=apU8L_xBgOo94$oV5mCT`RR1v2-@&?%#?F(Ls{AXaz zk~iQqvX{6`ic?^5$q^i>Gb&O8in^HcrR*SHm~1y!c_jNqO1(Na)U)=U8j$wIScBYZ zum;dj?m^y2dn<2C`N;f24Jhimgf)nIBVi4qT!J-Fez!ruY|xob)qgI{CX)E3_|Q4P*!L zxa1#{r~$>jDxtL_|CN$6ANU289WewsQn~VJD2XWNL1|HX9ISzS8oW-$>|hO)X8>1KFrDmjL7Y!c~fEG3WBbNb^f(D3{4bv3-(X zGK}(RsF2D(C?hjbhe45l0M5*I{gNk(61iI?sDql~D%r*0Z~k|Lk?*-)A%8bv4dVJp6DpTMNGI7!ScAA% zsr(yb7fh(vLV`S=CD|s~nS?dayMZ-`dk@w?d8yT44RQ%r<7BpQKAq=9MEUV&-Tv%o0CFO)yRo~G+Uam~=U&C_sR zXn4z_aSqA5N?QgO~S4o@P|c#XgtL}04PK`TW222Hq9 zKrJv#oCC*L^WtP`eI<;Ko>fhbl&^A|&{TJZ2KqPt9&j4%X+^ktPEELaXu{P)Lj#66 z(Bjcnkdh|Dt8vAc4`Unx9LJG5)Uz30PvcZy;PSNYwvZH6eIZ@hi@FY)@-)!c)HrWK zFNh{wX&!4C{WWNKHSyWBTcVN96^%;>q6v>gnxA5H97pmn$`P5CSZ_?XVWsQ1~fP#biP(f2~Gz^~dj?mO^1e$W|&~#r7uSxX}Xl!fT1MY3deNMd&;`t&S zF+YUFVB+HMkw((K0OgE6Tq=&mv!qxgKPRR2wD*+Cqn^ZE%6@QTG~yEhZIbzpYX(=K z_ee?5IL=rcO3YLVG>%L|q#28or`jVPM_tM91XmDa7s}XpY;g_(R=o%KHNA(iIH_xe z?0ItV<9j4$Ir0YHmCh}>C2?N3MgBcbgZ3F*f#eMXCwarD$X1|G#gizpm~*4#Pao~D ze@Wot{=ww3wH&(i9|s~|k4a>=z!k_JN@de1A9O_&KT~N_>_?hf{t!q5ojJGy`9pbJ z%*eZBw7eE%MrUsP8n0oQ0Wu-q3|v9X`=a_GT{Zw~OP#5ud0Y@zCDdX&$rR!x{GsafyqedEa9j6g%1Z2`;FJNvZ zxJH^pM)4p}1KAm{3-S-}xTLSh9_Y+dN+HS?w^wBs;5DzsHBv-ceVLM&OJ9LqP;3gS zMRoz~g4awQF3Jq(XT=bpI~7Av&6C{+yCC}uu0S~y#3<(15)4Yd3b+Ev4AxRP6vS74 zc1qnue}m9evIVX{vPE@Y_7_}%>@Vt6ypeDPQO*;tKx;u}B9A9rfybfjf}C>;T=6ux z0`W7r0?8J*0-XmYKs-c-Q8_Je1+g}U`EYy$u0ZxErO2Y}6V50$C<28Dx|mDM>X!jc zxpipjwFXW1Kj@mGdK9#5tf4^@W)Yftt);f-v2F@YcVAPxAoCj6_oM#9)GAg3)+aqq zWME#yEr~KsW=_6diR5t9*@TVqo4^&wZz_=hjJl4>v1A&kuIwPV0{Kl`x00{IwME4r zyqWfyET&|dP**v!Qo2DCsBpm?MsN|ZmM&Y{d1YgG9X*Yotgl%B*Jxc8CAxm_#S zrwS&$#tlWqjYzb4k|WnzXmYN#&GCCh$F}8`+~W>NM^P=p4x#ZIop9%b=8^Yyk(> zd0=@W78cMoh9<5rX;jgKYbH?3$UmSXn|$=t8cln(nw$b%9K2?73VJv9g9qv?tQAtu z?ZfyUUjo@%M;b zw9m93()$v-Xf3o7(3*)|^n3Jv*EnJq%|Y*X@fs>Dj-%aS^trgABMdk+E^&nS#e&9o zniQGt)6lm&^0!VUE|DksUV?ZH zFEQFTJV(_|pee_gu5U?~knxSV0#c>w$qTIzeK=_92TGP-JzSs(Z=0$CvK5*1;h?ZD z^d^Y8G{6yD>eo1aQZ(UhNo>-vG>(IzgI#5u`hh|db`YBGbr3}1h{ZXWLTje%tTnqA zCMjvlG|^Ga0i@J*^pT+H+f&JF5)aXuKIUW?r|$+q!&8j!mN-x}^(Np7pfDZKlA zh3SB%x&bs{I-m*D0XkdQL1@D7LQ`)7Xu@=)TSc;=c;1o^TItEo(3?PZh7^xvpK4>Q zLDH_740POQVwY&A!4OCngTRP25gJJ9&L}kHH}Dv+@?l<@$3*|0aq8Cxtt1-?JICBV zC@mbUI0q?($ZKS2V(h}#Y=lLG1{cNOBfN}qPIV^6oIJ~gzxZrI=!mAACD&4wk4RTl zV+=tFNO)jq!ULyLk!)T{g==59XRUYRo^{kYm{rL>YD4y%Y+a0h$>1pm3Qc!Vp;@M> z(#f8azK^j62`$xYp($qx@LYFLp{Z{aH1&;w=2!#mBHNh2zfs;O$ti}QF8?YOkE0CosQjjM)h*iE6dw6mAQu zQZBuVlO(;H9Fw^B81$Ggra+)xX3!9%_#Xad3FCx)!yxN>3DA7zFurschaU4+WD3-) z7#b7D^-&Jhn$rgf;yA*n$m8izJxh_)P zEyPEDmLzw4U*Loqmp)*taq9mE+v|IG34$Tn0#T&>qwFmIk1Ut+LxijHZPWJ`;<@3X z>)mL>C;iN+$$vrZ#(W}1OW&!6CX5I)_3VdTxCj*Xj|`*y7uYK1?|D}DL^w$G;Dn|- z#?UB=#CcJk@yxlc;XObIqizCyDt%8{Kz?TtV1~{kHBX3Z0Wc743K5ucWTdm@E2nF6 zdX_Z5_L-J2(MO`2mh>9keI!R@y_Nf-^kkmo0%nvMTvp{QG0Bqs)OI1t7B>}Q9V~g- z(WXEH;2!pr^ow{1l_NiqkW>CkYAYG{FS*(A-0(K!zi@?DIYzEqN%l#f>dfgwAU~0E zw`^WgliDldqsXVQhU@~8Dw{{CL;Or10`V!;9>sK|e3cWV>vXK4p~f8_q1q)|lqJ0u zWG2cl)o$@A?wa&H?FGDlY>f6QH9LqrL@GS;Gq8eK&m)1SUeC~UR|}f@-~w*a{YGe( znbbx^dw|SDK7~tVFVm-1qkMoNP%a1*lxzx?QF@Xske(zPA-|Y1nB+ILafrN2MMQov zP$t8r_r<;25`0p5TQCIXjr38U_N!|LnY&}#A{h#GZnUD*|8KrL?ME*;C2%`Q&4S5gT?*@*vubiN@k^hLC zWiP=Hs0Ng-V8wF-L*P9~py+VEUN;;2Mzj05hYtaHm?o2PjZ{g?fp) zi9Q69UoZr+GhCmEm}Vg?gxd4`8X*=XxlLSl_pYrg|te-G_z7*PY_}i1)P?$NLnD$kgar z;k^mZak|1dFa$ae;ASzO&9k6fm7fO!i#>7cwh6nS35{a)mNcmRN@@|-eS7BDLFy{qYp>AM0Y}<=}t(h zd`26PZn{M*d%CU>eF10`N#mLcsP!Jka+10cK0AF*Gy3h&^nEb05xT>XG8EoF-*rn@ zOrrmiBDEN6KvORjXzDcyP2XZlDJbzh;X<@WjI&I`88P?36&PVTp#h)_drt%#<0!@{ zc0q9p%L$E2ar`~v{b*BCh@*Mah4J_-u}hS5pl{xTRM3mhQXGqNMC1~4892p?g`g41 z>-W^hI^7x*?}8SUZfDkrQ{Y+oC0swUjzFtLU!HNQ*Fsa>0I!ZykMBWkM|(v|HR@Qj zbBqV+yrce2&@j|-4#3#@J!(7R4M4&=bFQ{{97-b2;o1pBQ`KvsDZc@Y8&cu-z!1c^ zkphtJm_Xw)N_-Z@7I_Ux)w?A)fAj?~3o$21Y8ZDV&f)&HTsc$k&(z*l=YbZIypcK7 zd2k)aYsO8~yKw~}o)M|*n0vrOQor2fHTm7B?r2ZR{KPY--AIfFxd5ndokIfx6Za3- zR=<~;yT);;QL1PJVWN>06iu;9Y9lNkiHg7M8x=^O2mPQlFTHIvFR4TEGbIAOFUyPj zjGBybHwM`X~>;x-~ya6rpeY#y6c_`JaV{Ip8O6BVjs!7+S3!>sb%12SZP@;-?d90xDvCxE* zN!HS5nJ#)oJ|N*4Ws5AP`IJPQ_>Vlb)|?VVaSoC#9;ZGD7$enoQfqMe7GzK&?uK!~ z(vi9GnG+IACj$PBd7t#zb)6ffz?hFk!|F~yG|K8>56DVMcVYtMKLe(VSOWkZsue-g zx3&NQ=(|DC>^qW`m+nl!7OfeONIW;vYvNtr*6#+6C-MQJDxZPWgY-};&PMql+>(x^ z9)O01j`O-4%qUyMoX#!C{V+^oX)gibrc9Xpi}E%GiXH@%IRwqdp~QQcTQ|%P!!!i-(d^ z9nTF-Cp(>-J*@>b9(gx8AsUyuVaE5sIBH&ey=Z4ZJ1S>{B2vBth2S*f(K#%W>ucy5tB^_k}*HJz-ZQrDeL7pqegVz-65DCpHSp@HvrWGC|7vR>0@BQ z25@CXJ`#|8*>1X=$gfMPSp0(V7QdwCJ8@o&iuM3f^!X{V$QQ+fGjEVS(6gvr(}Z!r zqBO=K4d27PP|je3^cMH4Sx?B-jrw{Jt7SlPuNr3KW?{ZoccHp{bc#y!B!1U`W4g)@RCoDnqjWaFbZs?kHE0utv<094TcrbSu;Mv5k159ESy zLFsn4&m23Wc~P=DbL@=xg|xTF$<6>_w#;yoBKoI{Q@jOD@mBgYTlA@+sa^(6{g$Aq zZ#)%i;di0wE)+D}*0A@h=|hm<*)gA!ZV7wMuzlpURJPI_B&pp(D zimSIPG~Gvp21FwM9;wZ!*HRyX$j>CD#m~6PG0%^ZR9+NkLwAf5x--V&(9|CZn)0Fv z1~0w`LlAux#;MmSG}X7bSg&s_q}F`VhC&k_7@BZ0K=Rce37WpA4ow((XsUrjQ$Hg@ zcHx1csRj;B-~EE7-XbZB8L>Uks3rt{Nv)`(jzAkkpOEx{>RZqhzd_UY)S;>8S!#h8 z{VQk$IdOfyo5nbO?<8fQVtp`Ok%&GuG?HI&4%&i6`M@iQeiP{uZe+)2Nw8_nK$ycm zqi}@dPW7IsuW$(I6zDmm+sVquImjsJ-ZnJA8u9nQSnJHWG!S(O5)*SbL}2R008My8 zXf!*B^8#4b9E26x)6|Sh&nhNPR$9EvErM7JWSnARXzC5g0dc`6%uAV5{DMamcSuQ)({y+0g7;q_&}&7j}vKOjoPu^V4$F zz6I6QSPvlCr0-2YQ;#EPnD4k(^g0M~M2gY=IN@N74HyUbE&d)dqj$q1X%6lsL>tQX z`7Ck&^ek);`I&ZKG0tQ22=wCmxXUAcK`eDf$c)~DaNpy~F*YUfU|)a;JJv}Ur+y>Q zbVrt*QjHXv>f+GUZv+~dxUdHx9Au-wDaaRqP2~fVd61suW~B5aP#o!;luC0RIgF&CabQypuN&;V!$KHwH^#Dg%- zGMI1$(H#9X zXS`;v07kwd?D3k}EX~0cXq_9C-6)q7+GTqZJX>dk1<@Q}aWn^)o1=e4%3OK_T!Hik zR#5&fMH}h5)J`GFAh-haDJe3o1>AL9GYMnmrBXsR?mOK(gp8$MzExKh>OSKq0|UGKFjBO#Lr-H;y$Ogzw!4dTZuPFF-mVF zT!DTMT!DU%R#)-N6RyDH^nE|JPd$^M310_IJ(Hl>4klbd+-IzjY|3hI1*xTkVuOS$ zh`f<-1ySA-u0VTWTmi`|^{9oWUJ>bY81kivR%Ls@6-Xy0T!GGwc68e9R8 z7z%5F|58h6?Eyj)c@kWK^mxJ*M19XmDuzw1J2Wrl1@RRZ^L0kJgqnj^Dt^|!D7XUU zTF7LGpTQN#N5a#S?FPmjF~>L{>PZ9*iyrnFE2vxxQYk*As5_DR7pFWb&aIX^d&HTb*!ux{Y(>+pXsyjmy#xSA( zq7Q-=Qr#Jv?uJ9-vSq_HLnBcWX?(sSoEyqi_dcNsyAKT~UB5>&$4DbQ3Oq^mTzU-} z-lLuc5E5ympn}Zc3aI7<4HT}PRjxUG5-HYAp>f+NK1-Q1@CM@)FW`bGUVtWF1sYx1 z;v7t&Gs1D#xa0)KXK^UC_pp$DkKA?8S2)G$=?P7_YiPPJgo4ob0-*^52~8MCXp(6p z8l*yaH`qSv7sde`(Kw89q`7w+uu|pCpy@siG<~m}+fiJSjdO69TWd}io;6OoGquEu zehxJC!G%UbEdCx1n2mBOg&-~uwT@%M1~wPq5inuFeL+Itdv+I!Tb)=a`l z&muFLcQrDT3anZ)GNa!EH4){53XtR+l^APsv>wxUY0H!_VqOKBz8wrr7(-~n7?Rb4 z1LNLvCr*2iK4PSC`X(kB4gS6M3GibVV295H2d|%2! z@ho|@7>APN*SDvjk-dq(M?yC07cvU60bJ{q4FE3XJ;2%1nn|xkS;fT1_=w9w26pkc7lUCGq!YdZvBh(-$5moB~NC;f7QDznEu$rhbeGp`<;)%Z_m^ zA-nKg(5O&{_W+g_bA60c?ixTA7A`&uL&rUZ?PJW&v${7+`an2jX#6kELGC*GKB@nK z=H-&R)<+&L$}|YH7&GCa(}XF`OJPlOa3{{=)JKVn-0B5KY6Jxs=OFZpatZt|)<1Yw zcj5>ZbSDm)bxvvxsd-c8F|LIuLVJ%ki87e_ABcyz-{WzWnERr-YG0X8U79Be%(NDQ zoVaF6d^$J!AH?_)(2aVnL8IGW*aJdry<37=#xqJVvd9PYKaf5pG>CCoYQd?vjO)%Z zmSmi89;C?h9bRZT**Bom@=vMK#rPMYRDBB?1}W|fxNGe_Y!GED^*@L*NdE($2O_9B zNCD_PD9Y*g=zkFXKj3=O?R-i|u?r!X_ZilWyhgQ0@5pP2ZR& zOlWlZQpqied`09UTZ?BVU5t7b-?Q`b<;k+jKLF}1SxuiAh`JMPA9I$pyj6`kwZWBN z2Uo?MWkMInIu11Z@?0Yo-;V4E9PTq@vE9O zG~u{Ow*yWNyqm6EM17hd<>CYPJ0z1v6SeqzKnrd$ ze&(8pV$%eJ*Brp>q7DN|Cp|>gQSo$wB1WDBxvF@E$WJ){+zs(PT%{Nm6)WADp!-_0 zzg)A;U}T#J`*(E zrGO^sU~b8zdV>$of}{eme|t^kBh#9+|iNO|S-X>Hk6x_-#d0ev9-g4ZPd z0%St^1y@wO!9_30AlIIxU%1vG{lWzw$zV#=M>$UyR3Z;i5f%@nixbgaf}m2ofQ3?y z7Ku?FgwkEa+~EQ1JFU=kmjW8pps(mq5o>%jptG+Gf<BsF zUPQcmoo*s450^?J+EWBw?}nfU-!XXtQ6Ho_PSk5u#o>gwSCrqx2Vi@mZbB1Fw}XyQ z?mvY>+7|{!8-@EP-JS~V;y;Sk;eEOAxSBKKlD5WCn2fp|3nksoQoLr&Lfm_bEb+bz z7F5_CXzEvxEUo011hm#nDpGkGJa5TwN<7BBV*9kuB-6a+bTKCI0dBVBlH`cwlB9xi zss!lb4brFL4K$}@fq+XnL5^IqfF~v1#i^DL!tGVB84zjYgXBy_zNcCt`6cBWF@a=! zg|AClVDU3-Dt^YpRPH)K2y~Ww1}4fXmQlJLoPvBuOq60PJZ0|zrdvEt&yXlbh*qrO zg4h;T2%7MEWcq|tfaY4lYH$jS(>L`<-dpb`oI=zuRN5rdNgZe$u36**Bwy#2!p3+W z^gmF(1e}6o8k~Y`1tm1u+JsYxdKa96*Nh7DINcpd8d174g}TuvO#Kh^ZsZiSR|FQ3 zH^3<<29q~GzW?!$^zd|@IKT3 zAj&GHLNQZnFRC+g{{uuuwFv-d$^k&r-4SSbVh!~;v`X~lS+Ty!08KdnXsRVZ6ILjR zbhJ^>bdMC8d>?4~t|d2p)jJEC?s7v@`~gkz2kJ%f2Q0QZS{h@_eNGo)L%(T7VnredrM4aYsMg`9%cLc%@hKg_D` zqCivcV`%DqOf^s6ih`!z$IvMH$MtcMMe74zqV>_8S?@+)-6-d1mzdk+ri|)?&{Q9U zrurZ>>j*FeF&C6R*%ae_Xu`=rBhe7|AaSo~st@AQ$WB9}G!&jq7=pNel3#vjmR_o1m*7&Q6b(DXg))OjrCuA%9!3^dz)GN6(VFa%yR7=p-GtHBW9 z?ovpM`^<$B?LB1;kF(uR7y|8mYMv1107DRUCz&eq5Ya)j$+ROg4}l?wHUM=R@lxrF zKG7#6>n-dXG{w`<)WZduYW{=<`uGwirxhBL4ae*PABM0+ygKK z@8AXbOnI*iUQL`AmpaN8E0zz3X41QXArP;DA&3}0u9&F5eEqeP z$V0Ta)VqNpkdCG7C;OYq8gWn4r<~*7lP&R@6F@QS3s)`WGk_tGj!kWAHHW`D!sbyT zjB}tQ;u)nG21LeH|F~vcGw}gltH-IgLqbH$zXt&s zbM>Shb*~(n^3>3jr-mlHEw>lcR~MSFZ|RnU{2@wrQ76)fL3cA!{VnnkL8JXTe$Veq z%{9sgI0fk@Zh^=ABrnTq!WnUuOtO$Zg%i(>=+|qZ<{Hm3ea?z2DBp)@LH+@qpW+$e(KttuuDa>n04iwjeJ_C|jj&9qYk+(o%%JQR zI0f%>x+xORjhsTXGxR!;ZU?6zJ;{=#C&4Mix*vsEVUD2*bBuMr~-+H$Rv3jNtw8R$V_|>+)3#*3V+%^ifuY`3S2sKvKEqGiY)SrIZ4?C z(k1eXX@?^DO^NErSIGsAd&UW+am2Go))&@!UXmEB8PGLH;Dr z5BZanw*79tXU$8Hxd`XWld2u#Zb$}kv?IO=bcya+L(`p6Xe5H;yf_U}H&Kq4Z-EzU z9^%fZ&dqm5iBjyF5xIDuSL2(dRGQX@Z>D+KEWI1ruAyvEA;mXqD1(U>WiZhqE+f%E zmo?ZwX!vHD1K+G6kEd}_1`{pb8HJ{M)`=EnFwvq6CR*&_0*!i))(5mqdw_4IcjHo5 z!+lP_7cr^OxNOu=Hziu^b&zNg7oKP|c8PmRVnzE$lEUL+uLFLMvY*BQX4m?-Hqnq@ zcovz_9`J#+hO){ydJ{C%8;KV2j)?}iC%zkwlg@*y$eI^}t}_Dl?r|X9;v6V#J&OX= zyeVqevmgKiE%hdd`ICD@2MAcqT%(&HLFI-IMqlKE!x3Ei~R%>E!Ie(sYaS; zJRH|T$f-R@jW6Rk?pZhV;rKn$m{Asx1oMXOS@SG4QvIH4q-k8N&m>yRQ9z?cs^3H1 z>)aA3HNG2&OzjJZ5b-^gA?+2Rq@RuT6i_#CZycA}0mSu@T?_tNdKT|HteJ{+@Yfhe zx;UO2DQT@aeacGXsFB8Z1BxSkklZzoBMOV_;~Iw6Ov*#gQX|z`sF6m#!lFn9!4*hv zkeLhl6841}sn*9OMXiq-spiE@>x^*6BX1D-iHE=yNI#SM5Ag@Z^-INm?xI0jfA) zyVJOc`%g66!PJ`|z6aF>`5UP>L0mIv0qJq~CSYD_q;c<4>MQOGxPnkeAkCmNPw6_XuMqpoGcQ*jJ&uMM@hmAFM}0sk zOa2CM)({Ue2X}AuZX{oIZom&Tj!;tTOP6%w`qG7$NK2PTM2lD<)<<7my)R})dk?Bj zyrI6j{2m!{j|0LM)`DvxznIMz!ijiBEQG9@!b-xK+me5 z2hWn`iu;T^BwGQlKt4L`9Ab8U51G+gfG%h)q=Mp}f;9*_JGmRe4cEJg4WGpgX6qp;eCnm;U2zrL$4@(timi zqdhccVBKZ4d40CHv{hp7ubw>lB?6u*UZwcdc_{dE!5E*Ls%Z zYt#{>U*yLECk*$QQlmAN6V?KDLGqD4S03j8yCC`C>PX0=r^HgD9$bOW9JMQ74^*z=fW&H1$EFf}JWFr|(up7pr4zvw z$ku`@kgWw*AX^KrK)y0kLc5t4JzLw+|@XVe==rF2v3 ze-P!|{SQc3QUZ#y<^BgLu^GQ5nP~A2V4}ru*Cbl(CkRbgoJ6w>CR~Bm2Lnc#Ayp7? zE&Lw!o~TQxBuMXqE0BEPu1P+~<|tmkL`hZ?v?t0c-kSFr)g&HLy@&T$O}z(Pfz}MJ zKx@Xu)S6KTifb{e$~&?J;vsMak`Jm!lKs@2G}>-vRh}j_&Ga}Tm0&}0i{+1}#)whJ zrZTpE52$H84|)^$-2j2>_sCsGzX>R@^cuYhRR2gB3 zWOD*;iOK{9B+5Qvym%7$vuqh{1C;Mf*P^4&A^ebD16SZRb5}uU$pWKH6R`&z1@8e# z`-e-d-=k2h-y_tBdq9OsF$5u#cmwk$|2aV%B3~iW%8jH;nURNh4X=gVb>u5>1v*Qr zbn-F573eIfBHk#k;xP-;_Ik}A~Qj6P~cKd z23owk&a5i|&yonrfseNR0XqTKw)I&st`Z7ZGD|92m(A zw0PH?-(&l<2gr=hgBq#M1DOdto(y4>^JG+_?+8c6J8Z1QGLtwr>UL<#HS=)nNtuC$ zLJcxgztN&8*UZC=jN>TM2mQyeh~rL=Vu0hbl)$5$r#7S>$4wDE%SDk$BX=Fo2$7EU zcrx$=)Or^5YNY8qio6C;u%O4G#Xe7IoNy1Rjf3PP!DIDqsEDZ7@Iaz}z`YLnj?iez z7T<$|uOga0StJXNZ!c9fW2(NLyX6 znZ^N8U(ps(dI+yQ+HS-RRTS6Em9w~4WXENrsK&>F-6M!>4~1yrv0pz)j5Qnno=qZR!y0usJX9^Zpu6~x#cgl4?=iP}wu zI?Rzi9}wRIT!H)&a0Sx4WCdhXz!gLvJ+-Wn-i0G|=2XyP?w2-eknWrrKw$3qeasRm~f}yPoC%^d86I%17P+VJKZfH`*9S5lOD*j7VAP zeQBelInw3QxE6BP(e{v4md>G7UCh&@FJH(n=BrSWb2MVi(NblPcNN$x(yZDml4*Jm zRFl?Bk|){^a0>FDY1uFPMz>ArgLG_B-ax6!KVVnni;|7dnt^!5Tn2mwv16Z2u8`&J`4a|)62Ddb0zLTzv zWRc^$v4N4F5sw&m)0&FQZhBUEGsdYNg_}s?F+NLtq_w1CrN-Hp0?r^_qg}n%j7aPE zaIYedvl97IsfCco#k&v5-6eR5Yfed8(MaBiMs-Xy3|FLay+Y5X?jjl&`+KuK;-k3F zM7okAT=ZBI#(}dh1)5*J6tEHT8W|{^M}hcAYvI~S+$*lONcK~R9c6}S!E458i2RJ( zB;8JX0r48uE$R03DTAn+Q1?1tN-^R|JTvJC*j~8|prO*)ghH};^goasrB4D!-rz07 zlT;0SZp25C*YHkbO$26+-_}Wv3XVkJKho&tHI^4`H}0wYA^IQ0oEsS^`+D?0hrC$tt?wn|p%2rXZwh*gd$skB`5H%tU?xaqN8tDx^IiK8kt`c(!~(9+h3-D-yEP zU3u2uc zH?X8%fMvvbV`?iYJDsjk#rGfus$52b!D}3Wyxs#gi0?}Wl30@?fnqyNq$}A6S0Gyk z2gwGcQbOQyyh-J%D6o*MYRJ2Qm37C2LWi*I^i5F>l9q~eVEgai8$GID?a-Q0jrQ8B zs@;*br`1&959h1ebssTebQNq>)xE0S!NUiQsA36K-5a$#c>k(Cd+on(W`92D4iaf! zFvI(m_DOxAQ%~T|l>_$8?33^KKacoxqmCUL)s5~qYV@h2Y6dl8PB2lqM!W2Cz~P-6 F{U2|-P2vCm literal 0 HcmV?d00001 diff --git a/open_stage_control/modules/custom_module.js b/open_stage_control/modules/custom_module.js index 138b55f..f9a7ece 100644 --- a/open_stage_control/modules/custom_module.js +++ b/open_stage_control/modules/custom_module.js @@ -118,8 +118,8 @@ loadModel = function(model){ receive("/step_probs_env_canvas", ...envVals) } - if(typeof model.hd_exp !== 'undefined') {receive("/hd_exp_val_slider", envSize)} - if(typeof model.hd_invert !== 'undefined') {receive("/hd_invert", envSize)} + if(typeof model.hd_exp !== 'undefined') {receive("/hd_exp_val_slider", model.hd_exp)} + if(typeof model.hd_invert !== 'undefined') {receive("/hd_invert", model.hd_invert)} if(typeof model.order_size !== 'undefined') {receive("/order_size_rslider", ...model.order_size)} if(typeof model.passages_size !== 'undefined') {receive("/passages_size_rslider", ...model.passages_size)} diff --git a/open_stage_control/seeds_and_ledgers_gui-backup000.json b/open_stage_control/seeds_and_ledgers_gui-backup000.json new file mode 100644 index 0000000..ca9b562 --- /dev/null +++ b/open_stage_control/seeds_and_ledgers_gui-backup000.json @@ -0,0 +1,2859 @@ +{ + "createdWith": "Open Stage Control", + "version": "1.24.0", + "type": "session", + "content": { + "type": "root", + "lock": false, + "id": "root", + "visible": true, + "interaction": true, + "comments": "", + "width": "auto", + "height": "auto", + "colorText": "auto", + "colorWidget": "auto", + "alphaFillOn": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "hideMenu": false, + "variables": "@{parent.variables}", + "traversing": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "", + "widgets": [ + { + "type": "panel", + "top": 10, + "left": 780, + "lock": false, + "id": "motif_panel", + "visible": true, + "interaction": true, + "comments": "", + "width": 560, + "height": 640, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "variables": "@{parent.variables}", + "traversing": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "", + "widgets": [ + { + "type": "textarea", + "top": 50, + "left": 10, + "lock": false, + "id": "mus_seq", + "visible": true, + "comments": "", + "width": 530, + "height": 570, + "expand": false, + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": 0, + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "textarea {\n white-space: pre;\n line-height: 20rem;\n font-size: 12rem;\n}", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 2, + "target": "", + "onCreate": "", + "onValue": "//set(\"this\", JSON.stringify(JSON.parse(value), null, 5))", + "interaction": true, + "typeTags": "s", + "ignoreDefaults": false, + "bypass": false + }, + { + "type": "text", + "top": 10, + "left": 10, + "lock": false, + "id": "motif_label", + "visible": true, + "comments": "", + "width": 90, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "align": "center", + "value": "motif", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 2, + "target": "", + "onCreate": "", + "onValue": "", + "vertical": false, + "wrap": false + }, + { + "type": "button", + "top": 10, + "left": 110, + "lock": false, + "id": "commit", + "visible": true, + "interaction": true, + "comments": "", + "width": 90, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "value": "", + "default": 0, + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "if(value === 0){\n console.log(get(\"start_play_index_switch\"))\n send('/commit', get(\"mus_seq\"), get(\"commit_type\"), get(\"start_play_index\"))\n}\n ", + "colorTextOn": "auto", + "label": "auto", + "vertical": false, + "wrap": false, + "on": 1, + "off": 0, + "mode": "push", + "doubleTap": false, + "decoupled": false + }, + { + "type": "button", + "top": 10, + "left": 230, + "lock": false, + "id": "one_shot", + "visible": true, + "interaction": true, + "comments": "", + "width": 90, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "value": "", + "default": 0, + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "if(value === 1){\n send(false, \"/transport\", 2, get(\"mus_seq\"));\n} else {\n send(false, \"/transport\", 0);\n}\n ", + "colorTextOn": "auto", + "label": "#{@{this} == 0 ? \"play\" : \"stop\"}", + "vertical": false, + "wrap": false, + "on": 1, + "off": 0, + "mode": "toggle", + "doubleTap": false, + "decoupled": false + }, + { + "type": "button", + "top": 10, + "left": 330, + "lock": false, + "id": "transcribe_motif", + "visible": true, + "interaction": true, + "comments": "", + "width": 90, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "value": "", + "default": 0, + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "if(value === 1){\n send(false, \"/transcribe_motif\", get(\"mus_seq\"), get(\"ref_uid\"));\n}\n ", + "colorTextOn": "auto", + "label": "transcribe", + "vertical": false, + "wrap": false, + "on": 1, + "off": 0, + "mode": "toggle", + "doubleTap": false, + "decoupled": false + }, + { + "type": "switch", + "top": 0, + "left": 200, + "lock": false, + "id": "commit_type", + "visible": true, + "interaction": true, + "comments": "", + "width": 20, + "height": 50, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorTextOn": "auto", + "layout": "vertical", + "gridTemplate": "", + "wrap": false, + "values": { + "a": "add", + "i": "insert", + "r": "replace" + }, + "mode": "tap", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "" + } + ], + "tabs": [], + "tabsPosition": "top" + }, + { + "type": "panel", + "top": 10, + "left": 580, + "lock": false, + "id": "ledger_panel", + "visible": true, + "interaction": true, + "comments": "", + "width": 200, + "height": 640, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": false, + "innerPadding": true, + "variables": "@{parent.variables}", + "traversing": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "", + "widgets": [ + { + "type": "text", + "top": 10, + "left": 10, + "lock": false, + "id": "ledger_label", + "visible": true, + "comments": "", + "width": 70, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "value": "ledger", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 2, + "target": "", + "onCreate": "", + "onValue": "", + "vertical": false, + "wrap": false, + "align": "center" + }, + { + "type": "file", + "top": 50, + "left": 10, + "lock": false, + "id": "load_ledger", + "visible": true, + "interaction": true, + "comments": "", + "width": 80, + "height": 30.000000000000004, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "value": "open", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "set(\"ledger_name\", value)\nconsole.log(value)\nconsole.log(\"ASDFASDFASDF\")\nset(\"this\", \"open\", {send:false})\n\n", + "align": "center", + "hidePath": true, + "mode": "open", + "directory": "resources", + "extension": "*", + "allowDir": false + }, + { + "type": "panel", + "top": 130, + "left": 10, + "lock": false, + "id": "sequencer_panel", + "visible": true, + "interaction": true, + "comments": "", + "width": 180, + "height": 600, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": 0, + "borderRadius": 0, + "padding": 0, + "html": "", + "css": "", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": false, + "innerPadding": true, + "variables": "@{parent.variables}", + "traversing": false, + "widgets": [ + { + "type": "panel", + "top": 21, + "left": 0, + "lock": false, + "id": "ledger_panel", + "visible": true, + "interaction": true, + "comments": "", + "width": 170, + "height": 460, + "expand": true, + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": 0, + "borderRadius": 0, + "padding": 0, + "html": "", + "css": "", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": "auto", + "innerPadding": true, + "variables": "@{parent.variables}", + "traversing": false, + "widgets": [ + { + "type": "variable", + "lock": false, + "id": "ledger_switch_vals", + "comments": "", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "" + }, + { + "type": "variable", + "lock": false, + "id": "ledger_switch_indices", + "comments": "", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "" + }, + { + "type": "variable", + "lock": false, + "id": "ledger_size", + "comments": "", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "/*\nvar array = Array(value + 2).fill().map((element, index) => index)\nconsole.log(array)\nset('ledger_switch_vals', array)\n*/" + }, + { + "type": "textarea", + "top": 0, + "left": 40, + "lock": false, + "id": "ledger", + "visible": true, + "interaction": true, + "comments": "", + "width": 90, + "height": "VAR{\"height\", 100}", + "expand": true, + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "textarea {\n overflow-y: hidden;\n line-height: VAR{\"line-height\", 20}rem;\n font-size: VAR{\"font-size\", 12}rem;\n padding-left: 10rem\n}", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "setVar(\"this\", \"height\", 0)\nvar lines = get(\"this\").split(/\\r|\\r\\n|\\n/);\nsetVar(\"this\", \"height\", (lines.length * getVar(\"this\", \"line-height\") + 6));\n\nsetVar(\"cur_play_index\", \"height\", getVar(\"this\", \"height\"))\nsetVar(\"cur_play_index_switch\", \"height\", getVar(\"this\", \"height\"))\n\nsetVar(\"start_play_index\", \"height\", getVar(\"this\", \"height\"))\nsetVar(\"start_play_index_switch\", \"height\", getVar(\"this\", \"height\"))\n\nsetVar(\"ledger_panel\", \"height\", getVar(\"this\", \"height\"))\n\nsetVar(\"ref_uid\", \"height\", getVar(\"this\", \"height\"))\nsetVar(\"ref_uid_switch\", \"height\", getVar(\"this\", \"height\"))\n\nvar switchVals = lines.map((element, index) => element.trim().replace(',',''))\nvar switchIndices = lines.map((element, index) => index - 1)\nset(\"ledger_switch_vals\", switchVals)\nset(\"ledger_switch_indices\", switchIndices)" + }, + { + "type": "switch", + "top": 0, + "left": 0, + "lock": false, + "id": "ref_uid", + "visible": true, + "interaction": true, + "comments": "", + "width": 20, + "height": "VAR{\"height\", 100}", + "expand": "false", + "colorText": "rgba(216,222,233,1)", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": 2, + "html": "", + "css": ":host {\n font-size: 0\n}", + "colorTextOn": "auto", + "layout": "vertical", + "gridTemplate": "", + "wrap": false, + "values": "@{ledger_switch_vals}", + "mode": "tap", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "" + }, + { + "type": "switch", + "top": 0, + "left": 0, + "lock": false, + "id": "ref_uid_switch", + "visible": true, + "interaction": true, + "comments": "", + "width": 20, + "height": "VAR{\"height\", 100}", + "expand": "false", + "colorText": "rgba(216,222,233,1)", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": 2, + "html": "", + "css": ":host {\n font-size: 0\n}", + "colorTextOn": "auto", + "layout": "vertical", + "gridTemplate": "", + "wrap": false, + "values": "@{ledger_switch_vals}", + "mode": "tap", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "if(value === get(\"ref_uid\")){\n set(\"ref_uid\", \"\")\n} else {\n set(\"ref_uid\", value)\n}\nset(\"this\", \"\")" + }, + { + "type": "switch", + "top": 0, + "left": 20, + "lock": false, + "id": "start_play_index", + "visible": true, + "interaction": true, + "comments": "", + "width": 20, + "height": "VAR{\"height\", 100}", + "expand": "false", + "colorText": "rgba(216,222,233,1)", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": 2, + "html": "", + "css": ":host {\n font-size: 0\n}", + "colorTextOn": "auto", + "layout": "vertical", + "gridTemplate": "", + "wrap": false, + "values": "@{ledger_switch_indices}", + "mode": "click", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "" + }, + { + "type": "switch", + "top": 0, + "left": 20, + "lock": false, + "id": "start_play_index_switch", + "visible": true, + "interaction": true, + "comments": "", + "width": 20, + "height": "VAR{\"height\", 100}", + "expand": "false", + "colorText": "rgba(216,222,233,1)", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": 2, + "html": "", + "css": ":host {\n font-size: 0\n}", + "colorTextOn": "auto", + "layout": "vertical", + "gridTemplate": "", + "wrap": false, + "values": "@{ledger_switch_indices}", + "mode": "click", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "if(value === get(\"start_play_index\")){\n set(\"start_play_index\", \"\")\n} else {\n set(\"start_play_index\", value)\n send(false, \"/load_model_state\", get(\"ledger_switch_vals\")[get(\"start_play_index\") + 1]);\n}\nset(\"this\", \"\")" + }, + { + "type": "switch", + "top": 0, + "left": 130, + "lock": false, + "id": "cur_play_index", + "visible": true, + "comments": "", + "width": 20, + "height": "VAR{\"height\", 100}", + "expand": "false", + "colorText": "rgba(216,222,233,1)", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": 2, + "html": "", + "css": ":host {\n font-size: 0\n}", + "wrap": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 2, + "target": "", + "onCreate": "", + "onValue": "", + "interaction": true, + "colorTextOn": "auto", + "mode": "click", + "typeTags": "", + "ignoreDefaults": false, + "bypass": false, + "layout": "vertical", + "gridTemplate": "", + "values": "@{ledger_switch_indices}" + }, + { + "type": "switch", + "top": 0, + "left": 130, + "lock": false, + "id": "cur_play_index_switch", + "visible": true, + "comments": "", + "width": 20, + "height": "VAR{\"height\", 100}", + "expand": "false", + "colorText": "rgba(216,222,233,1)", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": 2, + "html": "", + "css": ":host {\n font-size: 0\n}", + "wrap": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 2, + "target": "", + "onCreate": "locals.last = \"\"", + "onValue": "if(value === get(\"cur_play_index\")){\n set(\"cur_play_index\", \"\")\n} else {\n set(\"cur_play_index\", value)\n}\nset(\"this\", \"\")", + "interaction": true, + "colorTextOn": "auto", + "mode": "tap", + "typeTags": "", + "ignoreDefaults": false, + "bypass": false, + "layout": "vertical", + "gridTemplate": "", + "values": "@{ledger_switch_indices}" + } + ], + "tabs": [], + "tabsPosition": "top" + }, + { + "type": "button", + "top": 0, + "left": 0, + "lock": false, + "id": "ref_uid_lock", + "visible": true, + "comments": "", + "width": 20, + "height": 20.727272727272727, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "vertical": false, + "wrap": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 2, + "target": "", + "onCreate": "", + "onValue": "", + "interaction": true, + "colorTextOn": "auto", + "label": "L", + "on": 1, + "off": 0, + "mode": "toggle", + "doubleTap": false, + "decoupled": false, + "typeTags": "", + "ignoreDefaults": false, + "bypass": true + }, + { + "type": "variable", + "lock": false, + "id": "current_uid", + "comments": "", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 0, + "target": "", + "onCreate": "", + "onValue": "", + "typeTags": "s", + "ignoreDefaults": false, + "bypass": true + } + ], + "tabs": [], + "tabsPosition": "top" + }, + { + "type": "file", + "top": 50, + "left": 100, + "lock": false, + "id": "save_ledger", + "visible": true, + "interaction": true, + "comments": "", + "width": 80, + "height": 30.000000000000004, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "value": "save", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "send(false, \"/save_ledger\", get(\"ledger\"), get(\"this\"));\nset(\"this\", \"save\", {send:false})\nset(\"ledger_name\", value)", + "align": "center", + "hidePath": true, + "mode": "save", + "directory": "resources", + "extension": "*", + "allowDir": false + }, + { + "type": "button", + "top": 90, + "left": 10, + "lock": false, + "id": "transport", + "visible": true, + "interaction": true, + "comments": "", + "width": 80, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorTextOn": "auto", + "label": "#{@{this} == 0 ? \"play\" : \"stop\"}", + "vertical": false, + "wrap": false, + "on": 1, + "off": 0, + "mode": "toggle", + "doubleTap": false, + "decoupled": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 0, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "if(value === 1){\n send(false, \"/transport\", 1, get(\"start_play_index\"));\n} else {\n send(false, \"/transport\", 0, get(\"start_play_index\"));\n}" + }, + { + "type": "button", + "top": 90, + "left": 100, + "lock": false, + "id": "transcribe_all", + "visible": true, + "interaction": true, + "comments": "", + "width": 80, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "value": "", + "default": 0, + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "if(value === 1){\n //send(false, \"/transcribe\", get(\"ref_uid\"), get(\"mus_seq\"));\n send(false, \"/transcribe_all\", get(\"start_play_index\"));\n}\n ", + "colorTextOn": "auto", + "label": "transcribe", + "vertical": false, + "wrap": false, + "on": 1, + "off": 0, + "mode": "toggle", + "doubleTap": false, + "decoupled": false + }, + { + "type": "variable", + "lock": false, + "id": "ledger_name", + "comments": "", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "" + } + ], + "tabs": [], + "tabsPosition": "top" + }, + { + "type": "panel", + "top": 10, + "left": 30, + "lock": false, + "id": "seeds_panel", + "visible": true, + "interaction": true, + "comments": "", + "width": 550, + "height": 640, + "expand": false, + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "variables": "@{parent.variables}", + "traversing": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "", + "widgets": [ + { + "type": "text", + "top": 10, + "left": 10, + "lock": false, + "id": "seeds_label", + "visible": true, + "comments": "", + "width": 80, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "value": "seeds", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 2, + "target": "", + "onCreate": "", + "onValue": "", + "vertical": false, + "wrap": false, + "align": "center" + }, + { + "type": "panel", + "top": 90, + "left": 10, + "lock": false, + "id": "seeds_tab_panel", + "visible": true, + "comments": "", + "width": "96.3%", + "height": "84.13%", + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": 0, + "html": "", + "css": "> inner > .navigation {\n height: 35rem;\n}", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 2, + "target": "", + "onCreate": "", + "onValue": "", + "interaction": true, + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "variables": "@{parent.variables}", + "traversing": false, + "typeTags": "", + "ignoreDefaults": false, + "bypass": true, + "widgets": [], + "tabs": [ + { + "type": "tab", + "lock": false, + "id": "instrumentation", + "visible": true, + "interaction": true, + "comments": "", + "colorText": "auto", + "colorWidget": "auto", + "colorFill": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "label": "auto", + "variables": "@{parent.variables}", + "traversing": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "", + "widgets": [ + { + "type": "textarea", + "top": 270, + "left": 290, + "lock": false, + "id": "order", + "visible": true, + "interaction": true, + "comments": "", + "width": "41.18%", + "height": "41.24%", + "expand": false, + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "textarea {\n white-space: pre;\n line-height: 20rem;\n font-size: 12rem;\n}", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "s", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "" + }, + { + "type": "button", + "top": 270, + "left": 480, + "lock": false, + "id": "order_lock", + "visible": true, + "comments": "", + "width": 20, + "height": 20, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "vertical": false, + "wrap": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 2, + "target": "", + "onCreate": "", + "onValue": "", + "interaction": true, + "colorTextOn": "auto", + "label": "L", + "on": 1, + "off": 0, + "mode": "toggle", + "doubleTap": false, + "decoupled": false, + "typeTags": "", + "ignoreDefaults": false, + "bypass": false + }, + { + "type": "panel", + "top": 190, + "left": 290, + "lock": false, + "id": "order_size", + "visible": true, + "comments": "", + "width": 210, + "height": 40, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 2, + "target": "", + "onCreate": "", + "onValue": "", + "interaction": true, + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "variables": "@{parent.variables}", + "traversing": false, + "typeTags": "", + "ignoreDefaults": false, + "bypass": false, + "widgets": [ + { + "type": "range", + "top": 0, + "left": 40, + "lock": false, + "id": "@{parent.id}_rslider", + "visible": true, + "interaction": true, + "comments": "", + "width": 120, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "design": "default", + "knobSize": "auto", + "horizontal": true, + "pips": false, + "dashed": false, + "gradient": [], + "snap": true, + "spring": false, + "doubleTap": false, + "range": { + "min": 1, + "max": 10 + }, + "logScale": false, + "sensitivity": 1, + "steps": 10, + "value": "[@{@{parent.id}_input_min.value}, @{@{parent.id}_input_max.value}]", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "i", + "decimals": 0, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "", + "onTouch": "" + }, + { + "type": "input", + "top": 0, + "left": 160, + "lock": false, + "id": "@{parent.id}_input_max", + "visible": true, + "comments": "", + "width": 40, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "align": "center", + "value": "@{@{parent.id}_rslider.value.1}", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 0, + "target": "", + "onCreate": "", + "onValue": "", + "interaction": true, + "unit": "", + "asYouType": false, + "numeric": false, + "validation": "", + "maxLength": "", + "typeTags": "i", + "ignoreDefaults": false, + "bypass": false + }, + { + "type": "input", + "top": 0, + "left": 0, + "lock": false, + "id": "@{parent.id}_input_min", + "visible": true, + "comments": "", + "width": 40, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "align": "center", + "value": "@{@{parent.id}_rslider.value.0}", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 0, + "target": "", + "onCreate": "", + "onValue": "", + "interaction": true, + "unit": "", + "asYouType": false, + "numeric": false, + "validation": "", + "maxLength": "", + "typeTags": "i", + "ignoreDefaults": false, + "bypass": false + } + ], + "tabs": [], + "tabsPosition": "top" + }, + { + "type": "panel", + "top": 230, + "left": 290, + "lock": false, + "id": "passages_size", + "visible": true, + "interaction": true, + "comments": "", + "width": 210, + "height": 40, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "variables": "@{parent.variables}", + "traversing": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "", + "widgets": [ + { + "type": "range", + "top": 0, + "left": 40, + "lock": false, + "id": "@{parent.id}_rslider", + "visible": true, + "interaction": true, + "comments": "", + "width": 120, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "design": "default", + "knobSize": "auto", + "horizontal": true, + "pips": false, + "dashed": false, + "gradient": [], + "snap": true, + "spring": false, + "doubleTap": false, + "range": { + "min": 0, + "max": 10 + }, + "logScale": false, + "sensitivity": 1, + "steps": 11, + "value": "[@{@{parent.id}_input_min.value}, @{@{parent.id}_input_max.value}]", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "i", + "decimals": 0, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "", + "onTouch": "" + }, + { + "type": "input", + "top": 0, + "left": 160, + "lock": false, + "id": "@{parent.id}_input_max", + "visible": true, + "comments": "", + "width": 40, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "align": "center", + "value": "@{@{parent.id}_rslider.value.1}", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 0, + "target": "", + "onCreate": "", + "onValue": "", + "interaction": true, + "unit": "", + "asYouType": false, + "numeric": false, + "validation": "", + "maxLength": "", + "typeTags": "", + "ignoreDefaults": false, + "bypass": false + }, + { + "type": "input", + "top": 0, + "left": 0, + "lock": false, + "id": "@{parent.id}_input_min", + "visible": true, + "comments": "", + "width": 40, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "align": "center", + "value": "@{@{parent.id}_rslider.value.0}", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 0, + "target": "", + "onCreate": "", + "onValue": "set(\"passages_size\", [value, get(\"passages_size_v2\")])", + "interaction": true, + "unit": "", + "asYouType": false, + "numeric": false, + "validation": "", + "maxLength": "", + "typeTags": "", + "ignoreDefaults": false, + "bypass": false + } + ], + "tabs": [], + "tabsPosition": "top" + }, + { + "type": "matrix", + "top": 10, + "left": 290, + "lock": false, + "id": "sus_weights", + "visible": true, + "interaction": true, + "comments": "", + "width": "41.18%", + "height": "37.11%", + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "horizontal", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "variables": "@{parent.variables}", + "traversing": false, + "widgetType": "fragment", + "quantity": 3, + "props": "{\n \"file\": \"fragments/slider.json\",\n \"props\": {\n \"variables\": #{{\n \"n\": $, \"slider_label\": [\"1\", \"2\", \"3\"][$], \n \"min\": 0.0, \"max\": 1.0\n }}\n }\n}", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "", + "widgets": [], + "tabs": [], + "tabsPosition": "top" + }, + { + "type": "matrix", + "top": 10, + "left": 10, + "lock": false, + "id": "range_matrix", + "visible": true, + "interaction": true, + "comments": "", + "width": "52%", + "height": "95.24%", + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "horizontal", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "variables": "@{parent.variables}", + "traversing": false, + "widgetType": "fragment", + "quantity": 4, + "props": "{\n \"file\": \"fragments/range_slider.json\",\n \"props\": {\n \"variables\": #{{\n \"n\": $, \"min\": -3600, \"max\": 2400, \n \"rslider_label\": [\"IV\", \"III\", \"II\", \"I\"][$], \n \"decimals\": 0\n }\n }}\n}", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "", + "widgets": [], + "tabs": [], + "tabsPosition": "top" + } + ], + "tabs": [], + "tabsPosition": "top" + }, + { + "type": "tab", + "lock": false, + "id": "durations", + "visible": true, + "interaction": true, + "comments": "", + "colorText": "auto", + "colorWidget": "auto", + "colorFill": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": false, + "label": "auto", + "variables": "@{parent.variables}", + "traversing": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "", + "widgets": [ + { + "type": "panel", + "top": 0, + "left": 0, + "lock": false, + "id": "dur_panel", + "visible": true, + "interaction": true, + "comments": "", + "width": "100%", + "height": "100%", + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "> inner > .navigation {\n height: 30rem;\n}", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "variables": "@{parent.variables}", + "traversing": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "", + "widgets": [], + "tabs": [ + { + "type": "tab", + "lock": false, + "id": "entrances", + "visible": true, + "interaction": true, + "comments": "", + "colorText": "auto", + "colorWidget": "auto", + "colorFill": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "label": "auto", + "variables": "@{parent.variables}", + "traversing": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "", + "widgets": [ + { + "type": "fragment", + "top": 50, + "left": 10, + "lock": false, + "id": "entrances", + "visible": true, + "interaction": true, + "comments": "", + "width": "96.46%", + "height": "86.09%", + "expand": "false", + "css": "", + "file": "fragments/dur_probs_panel.json", + "fallback": "", + "props": {}, + "address": "auto", + "variables": "@{parent.variables}" + }, + { + "type": "switch", + "top": 10, + "left": 10, + "lock": false, + "id": "entrances_probs_sync", + "visible": true, + "interaction": true, + "comments": "", + "width": 490, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorTextOn": "auto", + "layout": "horizontal", + "gridTemplate": "", + "wrap": false, + "values": { + "sync to entrances": "entrances", + "sync to passages": "passages", + "sync to exits": "exits" + }, + "mode": "tap", + "value": "", + "default": "entrances", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "set(\"@{parent.id}_probs_vals\", get(value + \"_probs_vals\"))" + } + ], + "tabs": [], + "tabsPosition": "top" + }, + { + "type": "tab", + "lock": false, + "id": "passages", + "visible": true, + "interaction": true, + "comments": "", + "colorText": "auto", + "colorWidget": "auto", + "colorFill": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "label": "auto", + "variables": "@{parent.variables}", + "traversing": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "", + "widgets": [ + { + "type": "fragment", + "top": 50, + "left": 10, + "lock": false, + "id": "passages", + "visible": true, + "interaction": true, + "comments": "", + "width": "96.46%", + "height": "86.09%", + "expand": "false", + "css": "", + "file": "fragments/dur_probs_panel.json", + "fallback": "", + "props": {}, + "address": "auto", + "variables": "@{parent.variables}" + }, + { + "type": "switch", + "top": 10, + "left": 10, + "lock": false, + "id": "passages_probs_sync", + "visible": true, + "interaction": true, + "comments": "", + "width": 490, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorTextOn": "auto", + "layout": "horizontal", + "gridTemplate": "", + "wrap": false, + "values": { + "sync to entrances": "entrances", + "sync to passages": "passages", + "sync to exits": "exits" + }, + "mode": "tap", + "value": "", + "default": "passages", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "set(\"@{parent.id}_probs_vals\", get(value + \"_probs_vals\"))" + } + ], + "tabs": [], + "tabsPosition": "top" + }, + { + "type": "tab", + "lock": false, + "id": "exits", + "visible": true, + "interaction": true, + "comments": "", + "colorText": "auto", + "colorWidget": "auto", + "colorFill": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "label": "auto", + "variables": "@{parent.variables}", + "traversing": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "", + "widgets": [ + { + "type": "fragment", + "top": 50, + "left": 10, + "lock": false, + "id": "exits", + "visible": true, + "interaction": true, + "comments": "", + "width": "96.46%", + "height": "86.09%", + "expand": "false", + "css": "", + "file": "fragments/dur_probs_panel.json", + "fallback": "", + "props": {}, + "address": "auto", + "variables": "@{parent.variables}" + }, + { + "type": "switch", + "top": 10, + "left": 10, + "lock": false, + "id": "exits_probs_sync", + "visible": true, + "interaction": true, + "comments": "", + "width": 490, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorTextOn": "auto", + "layout": "horizontal", + "gridTemplate": "", + "wrap": false, + "values": { + "sync to entrances": "entrances", + "sync to passages": "passages", + "sync to exits": "exits" + }, + "mode": "tap", + "value": "", + "default": "exits", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "set(\"@{parent.id}_probs_vals\", get(value + \"_probs_vals\"))" + } + ], + "tabs": [], + "tabsPosition": "top" + } + ], + "tabsPosition": "top" + } + ], + "tabs": [], + "tabsPosition": "top" + }, + { + "type": "tab", + "lock": false, + "id": "weights", + "visible": true, + "interaction": true, + "comments": "", + "colorText": "auto", + "colorWidget": "auto", + "colorFill": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": false, + "label": "melody weights", + "variables": "@{parent.variables}", + "traversing": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "", + "widgets": [ + { + "type": "fragment", + "top": 10, + "left": 10, + "lock": false, + "id": "step_probs", + "visible": true, + "interaction": true, + "comments": "", + "width": "96.53%", + "height": 260, + "expand": "false", + "css": "", + "props": { + "variables": { + "min": 0, + "max": 1200, + "decimals": 0 + } + }, + "file": "fragments/env.json", + "fallback": "", + "address": "auto", + "variables": "@{parent.variables}" + }, + { + "type": "matrix", + "top": 280, + "left": 10, + "lock": false, + "id": "passages_weights", + "visible": true, + "interaction": true, + "comments": "", + "width": "79.15%", + "height": "40.57%", + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "horizontal", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "variables": "@{parent.variables}", + "traversing": false, + "widgetType": "fragment", + "quantity": 5, + "props": "{\n \"file\": \"fragments/slider.json\",\n \"props\": {\n \"variables\": #{{\n \"n\": $, \"slider_label\": [\"step\", \"dc\", \"range\", \"reg\", \"hd\"][$], \n \"min\": 0, \"max\": 1\n }}\n }\n}", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "", + "widgets": [], + "tabs": [], + "tabsPosition": "top" + }, + { + "type": "panel", + "top": 280, + "left": 420, + "lock": false, + "id": "panel_1", + "visible": true, + "interaction": true, + "comments": "", + "width": 90, + "height": "auto", + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorBg": "auto", + "layout": "default", + "justify": "start", + "gridTemplate": "", + "contain": true, + "scroll": true, + "innerPadding": true, + "tabsPosition": "top", + "variables": "@{parent.variables}", + "traversing": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "", + "widgets": [ + { + "type": "fragment", + "top": 0, + "left": 0, + "lock": false, + "id": "hd_exp", + "visible": true, + "interaction": true, + "comments": "", + "width": 80, + "height": 190, + "expand": "false", + "css": "", + "file": "fragments/slider.json", + "fallback": "", + "props": { + "variables": { + "min": -2, + "max": 2, + "slider_label": "hd exp" + } + }, + "address": "auto", + "variables": "@{parent.variables}" + }, + { + "type": "button", + "top": 130, + "left": 50, + "lock": false, + "id": "hd_invert", + "visible": true, + "interaction": true, + "comments": "", + "width": 20, + "height": 20, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorTextOn": "auto", + "label": "i", + "vertical": false, + "wrap": false, + "on": 1, + "off": 0, + "mode": "toggle", + "doubleTap": false, + "decoupled": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "" + } + ], + "tabs": [] + } + ], + "tabs": [], + "tabsPosition": "top" + } + ], + "tabsPosition": "top" + }, + { + "type": "button", + "top": 50, + "left": 130, + "lock": false, + "id": "order_seed_lock", + "visible": true, + "comments": "", + "width": 30.000000000000004, + "height": 30.000000000000004, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "vertical": false, + "wrap": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 2, + "target": "", + "onCreate": "", + "onValue": "", + "interaction": true, + "colorTextOn": "auto", + "label": "L", + "on": 1, + "off": 0, + "mode": "toggle", + "doubleTap": false, + "decoupled": false, + "typeTags": "", + "ignoreDefaults": false, + "bypass": true + }, + { + "type": "input", + "top": 50, + "left": 50, + "lock": false, + "id": "order_seed", + "visible": true, + "interaction": true, + "comments": "", + "width": 80, + "height": 30.000000000000004, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "align": "center", + "unit": "", + "asYouType": false, + "numeric": false, + "validation": "", + "maxLength": "", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "" + }, + { + "type": "button", + "top": 50, + "left": 300, + "lock": false, + "id": "dur_seed_lock", + "visible": true, + "comments": "", + "width": 29.999999999999996, + "height": 29.999999999999996, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "vertical": false, + "wrap": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 2, + "target": "", + "onCreate": "", + "onValue": "", + "interaction": true, + "colorTextOn": "auto", + "label": "L", + "on": 1, + "off": 0, + "mode": "toggle", + "doubleTap": false, + "decoupled": false, + "typeTags": "", + "ignoreDefaults": false, + "bypass": true + }, + { + "type": "input", + "top": 50, + "left": 220, + "lock": false, + "id": "dur_seed", + "visible": true, + "interaction": true, + "comments": "", + "width": 80, + "height": 30.000000000000004, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "align": "center", + "unit": "", + "asYouType": false, + "numeric": false, + "validation": "", + "maxLength": "", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "" + }, + { + "type": "button", + "top": 50, + "left": 460, + "lock": false, + "id": "weights_seed_lock", + "visible": true, + "comments": "", + "width": 29.999999999999996, + "height": 29.999999999999996, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "vertical": false, + "wrap": false, + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "decimals": 2, + "target": "", + "onCreate": "", + "onValue": "", + "interaction": true, + "colorTextOn": "auto", + "label": "L", + "on": 1, + "off": 0, + "mode": "toggle", + "doubleTap": false, + "decoupled": false, + "typeTags": "", + "ignoreDefaults": false, + "bypass": true + }, + { + "type": "input", + "top": 50, + "left": 380, + "lock": false, + "id": "weights_seed", + "visible": true, + "interaction": true, + "comments": "", + "width": 80, + "height": 30.000000000000004, + "expand": false, + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "align": "center", + "unit": "", + "asYouType": false, + "numeric": false, + "validation": "", + "maxLength": "", + "value": "", + "default": "", + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": false, + "onCreate": "", + "onValue": "" + }, + { + "type": "button", + "top": 10, + "left": 100, + "lock": false, + "id": "generate", + "visible": true, + "interaction": true, + "comments": "", + "width": 90, + "height": 30, + "expand": "false", + "colorText": "auto", + "colorWidget": "auto", + "colorStroke": "auto", + "colorFill": "auto", + "alphaStroke": "auto", + "alphaFillOff": "auto", + "alphaFillOn": "auto", + "lineWidth": "auto", + "borderRadius": "auto", + "padding": "auto", + "html": "", + "css": "", + "colorTextOn": "auto", + "label": "generate", + "vertical": false, + "wrap": false, + "on": 1, + "off": 0, + "mode": "push", + "doubleTap": false, + "decoupled": false, + "value": "", + "default": 0, + "linkId": "", + "address": "auto", + "preArgs": "", + "typeTags": "", + "decimals": 2, + "target": "", + "ignoreDefaults": false, + "bypass": true, + "onCreate": "", + "onValue": "if(value === 0){\n var minSeed = 100000\n var maxSeed = 999999\n \n if(get(\"dur_seed_lock\") === 0){\n set(\"dur_seed\", Math.floor(Math.random() * (maxSeed - minSeed) + minSeed))\n }\n \n if(get(\"order_seed_lock\") === 0){\n set(\"order_seed\", Math.floor(Math.random() * (maxSeed - minSeed) + minSeed))\n }\n \n if(get(\"weights_seed_lock\") === 0){\n set(\"weights_seed\", Math.floor(Math.random() * (maxSeed - minSeed) + minSeed))\n }\n \n set(\"entrances_probs_vals\", \"\")\n set(\"passages_probs_vals\", \"\")\n set(\"exits_probs_vals\", \"\")\n \n send('/generate', stateGet('root'), stateGet('ledger_panel'))\n\n}" + } + ], + "tabs": [], + "tabsPosition": "top" + } + ], + "tabs": [], + "tabsPosition": "top" + } +} \ No newline at end of file diff --git a/open_stage_control/seeds_and_ledgers_gui.json b/open_stage_control/seeds_and_ledgers_gui.json index ca9b562..adadc32 100644 --- a/open_stage_control/seeds_and_ledgers_gui.json +++ b/open_stage_control/seeds_and_ledgers_gui.json @@ -1,6 +1,6 @@ { "createdWith": "Open Stage Control", - "version": "1.24.0", + "version": "1.25.5", "type": "session", "content": { "type": "root", @@ -1474,11 +1474,11 @@ "doubleTap": false, "range": { "min": 1, - "max": 10 + "max": 100 }, "logScale": false, "sensitivity": 1, - "steps": 10, + "steps": 99, "value": "[@{@{parent.id}_input_min.value}, @{@{parent.id}_input_max.value}]", "default": "", "linkId": "", @@ -2345,7 +2345,7 @@ "css": "", "props": { "variables": { - "min": 0, + "min": -1200, "max": 1200, "decimals": 0 } @@ -2472,7 +2472,7 @@ "props": { "variables": { "min": -2, - "max": 2, + "max": 10, "slider_label": "hd exp" } }, diff --git a/records.json b/records.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/records.json @@ -0,0 +1 @@ +[] diff --git a/resources/piece_ledger/314s49e1/lilypond/part_I.ly b/resources/piece_ledger/314s49e1/lilypond/part_I.ly new file mode 100644 index 0000000..5fe9b6e --- /dev/null +++ b/resources/piece_ledger/314s49e1/lilypond/part_I.ly @@ -0,0 +1,54 @@ +{ + { r4 r8.[ c'16^\markup { \pad-markup #0.2 "+0"}] ~ c'2 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 } + \bar "|" + { b1^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b4 ~ b8[ ais8^\markup { \pad-markup #0.2 "+35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ ais8.[ a16^\markup { \pad-markup #0.2 "-20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}] ~ a4 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a16[ a8.^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}] ~ a2. } + \bar "|" + { gis1^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }} ~ } + \bar "|" + { gis8.[ gis16^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}] ~ gis2. ~ } + \bar "|" + { gis4 ~ gis8[ f8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ f4 fis4^\markup { \pad-markup #0.2 "-5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }} ~ } + \bar "|" + { fis2. ~ fis8[ gis8^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ } + \bar "|" + { gis1 ~ } + \bar "|" + { gis1 ~ } + \bar "|" + { gis1} +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger/314s49e1/lilypond/part_II.ly b/resources/piece_ledger/314s49e1/lilypond/part_II.ly new file mode 100644 index 0000000..ac6a85b --- /dev/null +++ b/resources/piece_ledger/314s49e1/lilypond/part_II.ly @@ -0,0 +1,54 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r16[ ais8.^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}] ~ ais2. ~ } + \bar "|" + { ais2. gis4^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }} ~ } + \bar "|" + { gis16[ fis8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }}] ~ fis2. ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis2 ~ fis8.[ r16] r4 } + \bar "|" + { r1 } + \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/314s49e1/lilypond/part_III.ly b/resources/piece_ledger/314s49e1/lilypond/part_III.ly new file mode 100644 index 0000000..8288ca0 --- /dev/null +++ b/resources/piece_ledger/314s49e1/lilypond/part_III.ly @@ -0,0 +1,54 @@ +{ + { r2. e'4^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }} ~ } + \bar "|" + { e'2 e'4^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ e'8.[ f'16^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { f'4 ~ f'8[ g'8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ g'4 ~ g'8[ ais'8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { ais'4 ~ ais'8[ c''8^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ c''2 } + \bar "|" + { gis'4^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }} ~ gis'8.[ gis'16^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ gis'4 ~ gis'8[ f'8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { f'2 e'4^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ e'8[ r8] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 r16[ dis'8.^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }}] ~ dis'4 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'8[ dis'8^\markup { \pad-markup #0.2 "-11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }}] ~ dis'2 d'4^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }} ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'4 ~ d'8[ ais8^\markup { \pad-markup #0.2 "+35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ ais2 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais8.[ b16^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }}] ~ b2. ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2 ~ b16[ r8.] r4 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger/314s49e1/lilypond/part_IV.ly b/resources/piece_ledger/314s49e1/lilypond/part_IV.ly new file mode 100644 index 0000000..43e8385 --- /dev/null +++ b/resources/piece_ledger/314s49e1/lilypond/part_IV.ly @@ -0,0 +1,54 @@ +{ + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'4 ~ c'16[ e'8.^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↓" }}] ~ e'2 ~ } + \bar "|" + { e'2 ~ e'8.[ g'16^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ g'4 ~ } + \bar "|" + { g'4 ~ g'8[ ais'8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ ais'2 ~ } + \bar "|" + { ais'4 ~ ais'8.[ gis'16^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ~ gis'2 } + \bar "|" + { gis'2^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }} ~ gis'8[ g'8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ g'4 ~ } + \bar "|" + { g'8[ f'8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }}] ~ f'2. ~ } + \bar "|" + { f'4 ~ f'8[ e'8^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }}] ~ e'2 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'16[ fis'8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ fis'2. ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'16[ a'8.^\markup { \pad-markup #0.2 "-20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ~ a'2 ~ a'16[ ais'8.^\markup { \pad-markup #0.2 "+8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'2 ~ ais'16[ b'8.^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ b'4 ~ } + \bar "|" + { b'2 ~ b'8[ a'8^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}] ~ a'4 ~ } + \bar "|" + { a'2 ~ a'8.[ fis'16^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}] ~ fis'4 ~ } + \bar "|" + { fis'16[ f'8.^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↑" }}] ~ f'2. ~ } + \bar "|" + { f'4 ~ f'8.[ e'16^\markup { \pad-markup #0.2 "+45"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }}] ~ e'2 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'4 ~ e'16[ r8.] r2 } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger/4c01589b/lilypond/part_I.ly b/resources/piece_ledger/4c01589b/lilypond/part_I.ly new file mode 100644 index 0000000..a806b97 --- /dev/null +++ b/resources/piece_ledger/4c01589b/lilypond/part_I.ly @@ -0,0 +1,38 @@ +{ + { r2. r8[ e'8^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'4 ~ e'8[ f'8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }}] ~ f'4 ~ f'16[ e'8.^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'2 d'2^\markup { \pad-markup #0.2 "+31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }} ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'2 r2 } + \bar "|" + { r8.[ d'16^\markup { \pad-markup #0.2 "+31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }}] ~ d'2. ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger/4c01589b/lilypond/part_II.ly b/resources/piece_ledger/4c01589b/lilypond/part_II.ly new file mode 100644 index 0000000..26c0d70 --- /dev/null +++ b/resources/piece_ledger/4c01589b/lilypond/part_II.ly @@ -0,0 +1,38 @@ +{ + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'2. ~ c'8.[ b16^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { b4 b2.^\markup { \pad-markup #0.2 "-39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }} ~ } + \bar "|" + { b16[ cis'8.^\markup { \pad-markup #0.2 "-47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ cis'4 ~ cis'8[ ais8^\markup { \pad-markup #0.2 "+45"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}] ~ ais4 ~ } + \bar "|" + { ais8[ a8^\markup { \pad-markup #0.2 "+33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}] ~ a8[ b8^\markup { \pad-markup #0.2 "-28"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ~ b2 ~ } + \bar "|" + { b8.[ c'16^\markup { \pad-markup #0.2 "+0"}] ~ c'2 ~ c'8.[ b16^\markup { \pad-markup #0.2 "-39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2 c'2^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'8.[ r16] r2. } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger/4c01589b/lilypond/part_III.ly b/resources/piece_ledger/4c01589b/lilypond/part_III.ly new file mode 100644 index 0000000..69e87d9 --- /dev/null +++ b/resources/piece_ledger/4c01589b/lilypond/part_III.ly @@ -0,0 +1,38 @@ +{ + { r2. r8[ ais8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { ais2 gis2^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }} ~ } + \bar "|" + { gis4 ~ gis8[ g8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }}] ~ g2 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g8.[ g16^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }}] ~ g8.[ a16^\markup { \pad-markup #0.2 "+33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}] ~ a2 ~ } + \bar "|" + { a8.[ gis16^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }}] ~ gis2 ~ gis8.[ a16^\markup { \pad-markup #0.2 "-20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }}] ~ } + \bar "|" + { a2 r2 } + \bar "|" + { r1 } + \bar "|" + { r16[ gis8.^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}] ~ gis2. } + \bar "|" + { gis4^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }} ~ gis8[ gis8^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }}] ~ gis2 } + \bar "|" + { g2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} g2^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} ~ } + \bar "|" + { g8.[ a16^\markup { \pad-markup #0.2 "-20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }}] ~ a2. ~ } + \bar "|" + { a1} +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger/4c01589b/lilypond/part_IV.ly b/resources/piece_ledger/4c01589b/lilypond/part_IV.ly new file mode 100644 index 0000000..2ab46c8 --- /dev/null +++ b/resources/piece_ledger/4c01589b/lilypond/part_IV.ly @@ -0,0 +1,38 @@ +{ + { r2. r8[ gis8^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }}] ~ } + \bar "|" + { gis1 ~ } + \bar "|" + { gis4 ~ gis8[ ais8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }}] ~ ais4 ~ ais16[ d'8.^\markup { \pad-markup #0.2 "+31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }}] ~ } + \bar "|" + { d'2 ~ d'8[ f'8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }}] ~ f'4 ~ } + \bar "|" + { f'2 g'2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 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'8.[ r16] r2. } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger/7e170ef8/lilypond/part_I.ly b/resources/piece_ledger/7e170ef8/lilypond/part_I.ly new file mode 100644 index 0000000..c8ca6a7 --- /dev/null +++ b/resources/piece_ledger/7e170ef8/lilypond/part_I.ly @@ -0,0 +1,34 @@ +{ + { r2. r8[ e'8^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { e'8.[ f'16^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }}] ~ f'4 ~ f'8[ e'8^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↓" }}] ~ e'4 ~ } + \bar "|" + { e'4 ~ e'16[ fis'8.^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↑" }}] ~ fis'16[ g'8.^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }}] ~ g'4 ~ } + \bar "|" + { g'8.[ gis'16^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }}] ~ gis'2. ~ } + \bar "|" + { gis'2. ~ gis'8.[ fis'16^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ } + \bar "|" + { fis'2. ~ fis'16[ fis'8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}] ~ } + \bar "|" + { fis'2 ~ fis'8.[ gis'16^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ gis'4 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'2 gis'2^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'2. ~ gis'16[ ais'8.^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { ais'4 ~ ais'8.[ c''16^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ c''2 ~ } + \bar "|" + { c''16[ cis''8.^\markup { \pad-markup #0.2 "+39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ cis''2 ~ cis''16[ d''8.^\markup { \pad-markup #0.2 "+31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}]} +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger/7e170ef8/lilypond/part_II.ly b/resources/piece_ledger/7e170ef8/lilypond/part_II.ly new file mode 100644 index 0000000..7683e88 --- /dev/null +++ b/resources/piece_ledger/7e170ef8/lilypond/part_II.ly @@ -0,0 +1,34 @@ +{ + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'2. ~ c'8.[ d'16^\markup { \pad-markup #0.2 "+31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}] ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'8.[ r16] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. r16[ d'8.^\markup { \pad-markup #0.2 "+31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}] ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger/7e170ef8/lilypond/part_III.ly b/resources/piece_ledger/7e170ef8/lilypond/part_III.ly new file mode 100644 index 0000000..c3f7109 --- /dev/null +++ b/resources/piece_ledger/7e170ef8/lilypond/part_III.ly @@ -0,0 +1,34 @@ +{ + { r2. r8[ ais8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais4 ~ ais16[ g8.^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }}] ~ g16[ gis8.^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }}] ~ gis4 ~ } + \bar "|" + { gis8.[ gis16^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ~ gis2. ~ } + \bar "|" + { gis2. ~ gis8.[ ais16^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais2 ~ ais8.[ gis16^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}] ~ gis4 ~ } + \bar "|" + { gis2 ~ gis16[ gis8.^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ gis4 ~ } + \bar "|" + { gis1 ~ } + \bar "|" + { gis2. ~ gis8[ g8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { g4 ~ g8[ ais8^\markup { \pad-markup #0.2 "+45"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↓" }}] ~ ais4 ~ ais8.[ d'16^\markup { \pad-markup #0.2 "-35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}] ~ } + \bar "|" + { d'2. cis'4^\markup { \pad-markup #0.2 "+12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} ~ } + \bar "|" + { cis'2 ~ cis'8[ e'8^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ e'4 ~ } + \bar "|" + { e'8.[ f'16^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ f'2. ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger/7e170ef8/lilypond/part_IV.ly b/resources/piece_ledger/7e170ef8/lilypond/part_IV.ly new file mode 100644 index 0000000..1bf0348 --- /dev/null +++ b/resources/piece_ledger/7e170ef8/lilypond/part_IV.ly @@ -0,0 +1,34 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r8[ c'8^\markup { \pad-markup #0.2 "+0"}] ~ c'2. ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch.json b/resources/piece_ledger_sq1_candidates_stitch.json index 43e9593..8e41d98 100644 --- a/resources/piece_ledger_sq1_candidates_stitch.json +++ b/resources/piece_ledger_sq1_candidates_stitch.json @@ -18,6 +18,8 @@ "784130cc", "443ec222", "52c9a980", - "4200a90d" + "4200a90d", + "61ce9067", + "774ed940" ] } \ 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 a8fca11..fd22a58 100644 --- a/resources/piece_ledger_sq1_candidates_stitch.json_bak +++ b/resources/piece_ledger_sq1_candidates_stitch.json_bak @@ -8,7 +8,6 @@ "761e4585", "6fb60ab6", "79e0a4a7", - "62820081", "43b009ff", "7d3c9a80", "4b7745df", @@ -18,6 +17,8 @@ "7edbdceb", "784130cc", "443ec222", - "52c9a980" + "52c9a980", + "4200a90d", + "61ce9067" ] } \ No newline at end of file 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 index ace6b67..de04cf3 100644 --- a/resources/piece_ledger_sq1_candidates_stitch/4200a90d/4200a90d_mus_model.json +++ b/resources/piece_ledger_sq1_candidates_stitch/4200a90d/4200a90d_mus_model.json @@ -19,9 +19,8 @@ [ [ [ 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 ] + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ 1, -1, 1, -2, 3, 0 ] ], 0.875 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ "Rest" ] ], 1.125 ] ] ] ], diff --git a/resources/piece_ledger_sq1_candidates_stitch/4a8a6e53/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/4a8a6e53/lilypond/part_I.ly index e9e6601..ce99136 100644 --- a/resources/piece_ledger_sq1_candidates_stitch/4a8a6e53/lilypond/part_I.ly +++ b/resources/piece_ledger_sq1_candidates_stitch/4a8a6e53/lilypond/part_I.ly @@ -1,4 +1,4 @@ -{ +{ \numericTimeSignature { r2. e'4^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }} ~ } \bar "|" { e'2 e'4^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ e'8.[ f'16^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ } diff --git a/resources/piece_ledger_sq1_candidates_stitch/61ce9067/61ce9067_code.scd b/resources/piece_ledger_sq1_candidates_stitch/61ce9067/61ce9067_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/61ce9067/61ce9067_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/61ce9067/61ce9067_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/61ce9067/61ce9067_mus_model.json new file mode 100644 index 0000000..bdc8f6f --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/61ce9067/61ce9067_mus_model.json @@ -0,0 +1,55 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, -1, 1, -2, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0.125 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 2, -1, 1, -3, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 2, -1, 1, -3, 1, 0 ], [ 3, -2, 1, -2, 1, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 2, -1, 1, -3, 1, 0 ], [ 3, -2, 1, -2, 1, 0 ], [ 3, -1, 1, -3, 1, 0 ] ], 0.375 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 3, -2, 1, -2, 1, 0 ], [ 3, -1, 1, -3, 1, 0 ] ], 1 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 3, -1, 1, -3, 1, 0 ] ], 0.5 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 2, -1, 1, -2, 1, 0 ] ], 0.75 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 2, -2, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 2, -1, 1, -2, 1, 0 ] ], 1.125 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 2, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 1, -2, 1, 0 ] ], 0.75 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 2, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 2, -2, 1, 0 ] ], 0.25 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 2, -2, 1, 0 ] ], 0.875 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ 2, -1, 2, -2, 1, 0 ] ], 0.875 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ 2, -1, 1, -2, 1, 0 ] ], 0.375 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ 2, -1, 1, -2, 1, 0 ] ], 0.375 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ "Rest" ] ], 0.375 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0.125 ] + ] + ] +], +"last_changes": +[ + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 2, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 1, -2, 1, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 2, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 2, -2, 1, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 2, -2, 1, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ 2, -1, 2, -2, 1, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ 2, -1, 1, -2, 1, 0 ] ] +], +"cur_uid": "61ce9067", +"ref_uid": "4200a90d", +"order_seed": 278192, +"dur_seed": 660041, +"motifs_seed": 150685, +"entrances_probs_vals": [ 0, 0, 0, 0.19, 0.93406593406593, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0.2, 2.3015873015873, 0.08, 1.2912087912088, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0.16, 1.0714285714286, 0.16, 1.510989010989, 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.08641975308642, 0.9375, 0.50617283950617, 0.89772727272727, 0.69135802469136, 0, 1, 0 ], +"passages_weights": [ 0.27, 0, 0.6, 0, 1 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3 ], [ ] ] +], +"sus_weights": [ 0.62, 0.25, 0 ], +"order_size": [ 7, 10 ], +"passages_size": [ 0, 6 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_I.ly new file mode 100644 index 0000000..7f37d66 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_I.ly @@ -0,0 +1,12 @@ +{ + { r1 } + \bar "|" + { r8.[ a'16^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ a'2. ~ } + \bar "|" + { a'8[ g'8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ g'2. ~ } + \bar "|" + { g'4 ~ g'8.[ b'16^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ b'2 ~ } + \bar "|" + { b'4 ~ b'8.[ g'16^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ g'4 ~ g'16[ r8.]} +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_II.ly new file mode 100644 index 0000000..0179fb5 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_II.ly @@ -0,0 +1,12 @@ +{ + { r2. r16[ c''8.^\markup { \pad-markup #0.2 "-4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { c''2. ~ c''8[ f'8^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'16[ c'8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ c'2. } + \bar "|" + { dis'1^\markup { \pad-markup #0.2 "+39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_III.ly new file mode 100644 index 0000000..b62bec1 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_III.ly @@ -0,0 +1,12 @@ +{ + { r4 r8.[ a16^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}] ~ a2 ~ } + \bar "|" + { a4 ~ a8[ g8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ g2 ~ } + \bar "|" + { g2 b2^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { b2 ~ b16[ g8.^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ g4 ~ } + \bar "|" + { g1} +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_IV.ly new file mode 100644 index 0000000..962bfbd --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/61ce9067/lilypond/part_IV.ly @@ -0,0 +1,12 @@ +{ + { g,1^\markup { \pad-markup #0.2 "-2"} ~ } + \bar "|" + { g,1 ~ } + \bar "|" + { g,1 ~ } + \bar "|" + { g,1 ~ } + \bar "|" + { g,2 ~ g,8[ r8] r4} +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/774ed940/774ed940_code.scd b/resources/piece_ledger_sq1_candidates_stitch/774ed940/774ed940_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/774ed940/774ed940_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/774ed940/774ed940_mus_model.json b/resources/piece_ledger_sq1_candidates_stitch/774ed940/774ed940_mus_model.json new file mode 100644 index 0000000..beff01e --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/774ed940/774ed940_mus_model.json @@ -0,0 +1,70 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 0.25 ], + [ [ [ 0, -1, 1, -2, 1, 1 ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 0.5 ], + [ [ [ 0, 0, 1, -2, 1, 0 ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 1.25 ], + [ [ [ 0, -1, 1, -2, 1, 1 ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 0.75 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 2.875 ] + ], + [ + [ [ [ 0, -1, 1, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ 0, 0, 1, -1, 1, 0 ] ], 0 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ "Rest" ], [ 0, -1, 1, 0, 1, 0 ], [ 0, 0, 1, -1, 1, 0 ] ], 0 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ 0, -1, 2, -1, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, 0, 1, -1, 1, 0 ] ], 1.5 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ 1, -1, 1, -1, 1, -1 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, 0, 1, -1, 1, 0 ] ], 1.5 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ -1, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, 0, 1, -1, 1, 0 ] ], 0 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ -1, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 1.5 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ 0, -1, 1, -1, 2, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 0.625 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ 1, -2, 1, -1, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 1.25 ] + ], + [ + [ [ [ 0, -1, 1, -1, 1, 0 ], [ -1, -1, 1, 0, 1, 1 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 0 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 1, 0, 1, 1 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 1 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 1, 0, 1, 1 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 1, 1, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], 1.25 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 0, 2, 0 ] ], 1.125 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], 0 ], + [ [ [ -1, -1, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], 0.875 ], + [ [ [ -1, -1, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], 2 ], + [ [ [ -1, -1, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], 0], + [ [ [ -1, -1, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 2.875 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 1, 1, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], + [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], + [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 0, 2, 0 ] ], + [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], + [ [ -1, -1, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ] +], +"cur_uid": "774ed940", +"ref_uid": "61ce9067", +"order_seed": 473248, +"dur_seed": 979780, +"motifs_seed": 262605, +"entrances_probs_vals": [ 1, 0, 0, 0.19, 1.7582417582418, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0.85, 0, 1.2301587301587, 0.54945054945055, 1.79, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0.16, 1.0714285714286, 0.16, 1.510989010989, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2560, 59.442724458204 ], [ -1074, 393.8080495356 ], [ 59.442724458205, 1638.3900928793 ], [ -52.012383900929, 1582.6625386997 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.092592592592593, 0.78977272727273, 0.16460905349794, 0, 0.24279835390947, 0, 0.60699588477366, 0.63636363636364, 0.73662551440329, 0, 1, 0 ], +"passages_weights": [ 1, 0, 0.6, 0, 0.54 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 0, 0, 0, 0 ], [ 1, 2 ] ], + [ [ 0 ], [ 3, 2, 1, 1, 1, 3, 1, 1 ], [ ] ], + [ [ 2 ], [ 1, 0, 3, 1, 1, 3, 3, 0 ], [ ] ] +], +"sus_weights": [ 0.69, 0, 0 ], +"order_size": [ 1, 7 ], +"passages_size": [ 3, 6 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_I.ly b/resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_I.ly new file mode 100644 index 0000000..2b16ea6 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_I.ly @@ -0,0 +1,22 @@ +{ + { g'1^\markup { \pad-markup #0.2 "-2"} ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'2. ~ g'16[ c'8.^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'4 ~ c'16[ gis'8.^\markup { \pad-markup #0.2 "+26"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ gis'2 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'2 c''2^\markup { \pad-markup #0.2 "+5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }} ~ } + \bar "|" + { c''8[ gis'8^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↑" }}] ~ gis'4 ~ gis'8.[ d''16^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ d''4 ~ } + \bar "|" + { d''1 ~ } + \bar "|" + { d''8[ r8] r2. } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_II.ly b/resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_II.ly new file mode 100644 index 0000000..86115b1 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_II.ly @@ -0,0 +1,22 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. r16[ d'8.^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'8[ r8] r2. } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_III.ly b/resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_III.ly new file mode 100644 index 0000000..9d65346 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_III.ly @@ -0,0 +1,22 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. r16[ a8.^\markup { \pad-markup #0.2 "-47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { a2 ~ a16[ gis8.^\markup { \pad-markup #0.2 "+26"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ gis4 ~ } + \bar "|" + { gis4 ~ gis16[ d8.^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ d2 ~ } + \bar "|" + { d16[ ais8.^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ ais8[ ais8^\markup { \pad-markup #0.2 "-35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ ais2 } + \bar "|" + { b2^\markup { \pad-markup #0.2 "-24"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }} c'2^\markup { \pad-markup #0.2 "+5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c'8[ fis8^\markup { \pad-markup #0.2 "+22"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}] ~ fis2. ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis8[ r8] r2. } +\bar "||" +} \ No newline at end of file diff --git a/resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_IV.ly b/resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_IV.ly new file mode 100644 index 0000000..c7d9483 --- /dev/null +++ b/resources/piece_ledger_sq1_candidates_stitch/774ed940/lilypond/part_IV.ly @@ -0,0 +1,22 @@ +{ + { r8[ dis8^\markup { \pad-markup #0.2 "+39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ~ dis8[ d8^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ d2 } + \bar "|" + { dis4^\markup { \pad-markup #0.2 "+39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }} ~ dis8[ f8^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}] ~ f2 ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f1 } + \bar "|" + { a,1^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }} ~ } + \bar "|" + { a,2 ~ a,8.[ d16^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ d4 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d8[ r8] r2. } +\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 ebb3746..a1f5f5e 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,57 +3,68 @@ [ [ [ - [ [ [ "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" ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 1.125 ], + [ [ [ 0, -1, 1, -2, 1, 1 ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 0.5 ], + [ [ [ 0, 0, 1, -2, 1, 0 ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 1.25 ], + [ [ [ 0, -1, 1, -2, 1, 1 ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 0.75 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 2.875 ] ], [ - [ [ [ "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 ] + [ [ [ 0, -1, 1, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ 0, 0, 1, -1, 1, 0 ] ], 0 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ "Rest" ], [ 0, -1, 1, 0, 1, 0 ], [ 0, 0, 1, -1, 1, 0 ] ], 0 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ 0, -1, 2, -1, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, 0, 1, -1, 1, 0 ] ], 1.5 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ 1, -1, 1, -1, 1, -1 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, 0, 1, -1, 1, 0 ] ], 1.5 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ -1, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, 0, 1, -1, 1, 0 ] ], 0 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ -1, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 1.5 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ 0, -1, 1, -1, 2, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 0.625 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ 1, -2, 1, -1, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 1.25 ] + ], + [ + [ [ [ 0, -1, 1, -1, 1, 0 ], [ -1, -1, 1, 0, 1, 1 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 0 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 1, 0, 1, 1 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 1 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 1, 0, 1, 1 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 1, 1, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], 1.25 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 0, 2, 0 ] ], 1.125 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], 0 ], + [ [ [ -1, -1, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], 0.875 ], + [ [ [ "Rest" ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, -1, 1, 0, 1, 0 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 2.875 ] ] ] ], "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 ] ] + [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 1, 1, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], + [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], + [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 0, 2, 0 ] ], + [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], + [ [ -1, -1, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ] ], "cur_uid": "tmp", -"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 ], +"ref_uid": "61ce9067", +"order_seed": 473248, +"dur_seed": 979780, +"motifs_seed": 262605, +"entrances_probs_vals": [ 1, 0, 0, 0.19, 1.7582417582418, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0.85, 0, 1.2301587301587, 0.54945054945055, 1.79, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0.16, 1.0714285714286, 0.16, 1.510989010989, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2560, 59.442724458204 ], [ -1074, 393.8080495356 ], [ 59.442724458205, 1638.3900928793 ], [ -52.012383900929, 1582.6625386997 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.092592592592593, 0.78977272727273, 0.16460905349794, 0, 0.24279835390947, 0, 0.60699588477366, 0.63636363636364, 0.73662551440329, 0, 1, 0 ], +"passages_weights": [ 1, 0, 0.6, 0, 0.54 ], "hd_exp": 2, "hd_invert": 0, "order": [ - [ [ 3 ], [ 2 ], [ 0, 1 ] ], - [ [ 1 ], [ 2, 0, 2, 0, 0, 2, 3, 0, 2, 3 ], [ ] ] + [ [ 3 ], [ 0, 0, 0, 0 ], [ 1, 2 ] ], + [ [ 0 ], [ 3, 2, 1, 1, 1, 3, 1, 1 ], [ ] ], + [ [ 2 ], [ 1, 0, 3, 1, 1, 3, 3, 0 ], [ ] ] ], -"sus_weights": [ 0.41, 0, 0 ], -"order_size": [ 2, 6 ], -"passages_size": [ 0, 5 ], +"sus_weights": [ 0.69, 0, 0 ], +"order_size": [ 1, 7 ], +"passages_size": [ 3, 6 ], "motif_edited": "false", "order_edited": "false" } \ No newline at end of file diff --git a/resources/string_quartet_1.json b/resources/string_quartet_1.json new file mode 100644 index 0000000..8e41d98 --- /dev/null +++ b/resources/string_quartet_1.json @@ -0,0 +1,25 @@ +{ +"ledger": +[ + "4a8a6e53", + "66f6a618", + "490b1e6e", + "46985d14", + "761e4585", + "6fb60ab6", + "79e0a4a7", + "43b009ff", + "7d3c9a80", + "4b7745df", + "6ed95c4c", + "6d635e88", + "4e7d35e5", + "7edbdceb", + "784130cc", + "443ec222", + "52c9a980", + "4200a90d", + "61ce9067", + "774ed940" +] +} \ No newline at end of file diff --git a/resources/string_quartet_1/4200a90d/4200a90d_code.scd b/resources/string_quartet_1/4200a90d/4200a90d_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/4200a90d/4200a90d_mus_model.json b/resources/string_quartet_1/4200a90d/4200a90d_mus_model.json new file mode 100644 index 0000000..de04cf3 --- /dev/null +++ b/resources/string_quartet_1/4200a90d/4200a90d_mus_model.json @@ -0,0 +1,58 @@ +{ +"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 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ 1, -1, 1, -2, 3, 0 ] ], 0.875 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ "Rest" ], [ "Rest" ] ], 1.125 ] + ] + ] +], +"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/string_quartet_1/4200a90d/lilypond/part_I.ly b/resources/string_quartet_1/4200a90d/lilypond/part_I.ly new file mode 100644 index 0000000..bcf7780 --- /dev/null +++ b/resources/string_quartet_1/4200a90d/lilypond/part_I.ly @@ -0,0 +1,18 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4200a90d/lilypond/part_II.ly b/resources/string_quartet_1/4200a90d/lilypond/part_II.ly new file mode 100644 index 0000000..b74e7bb --- /dev/null +++ b/resources/string_quartet_1/4200a90d/lilypond/part_II.ly @@ -0,0 +1,18 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4200a90d/lilypond/part_III.ly b/resources/string_quartet_1/4200a90d/lilypond/part_III.ly new file mode 100644 index 0000000..11cd320 --- /dev/null +++ b/resources/string_quartet_1/4200a90d/lilypond/part_III.ly @@ -0,0 +1,18 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4200a90d/lilypond/part_IV.ly b/resources/string_quartet_1/4200a90d/lilypond/part_IV.ly new file mode 100644 index 0000000..5e43176 --- /dev/null +++ b/resources/string_quartet_1/4200a90d/lilypond/part_IV.ly @@ -0,0 +1,18 @@ +{ + { 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,1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/43b009ff/43b009ff_code.scd b/resources/string_quartet_1/43b009ff/43b009ff_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/43b009ff/43b009ff_mus_model.json b/resources/string_quartet_1/43b009ff/43b009ff_mus_model.json new file mode 100644 index 0000000..3c475af --- /dev/null +++ b/resources/string_quartet_1/43b009ff/43b009ff_mus_model.json @@ -0,0 +1,48 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 5.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.375 ] + ] + ] +], +"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/string_quartet_1/43b009ff/lilypond/part_I.ly b/resources/string_quartet_1/43b009ff/lilypond/part_I.ly new file mode 100644 index 0000000..af37609 --- /dev/null +++ b/resources/string_quartet_1/43b009ff/lilypond/part_I.ly @@ -0,0 +1,28 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/43b009ff/lilypond/part_II.ly b/resources/string_quartet_1/43b009ff/lilypond/part_II.ly new file mode 100644 index 0000000..5c63449 --- /dev/null +++ b/resources/string_quartet_1/43b009ff/lilypond/part_II.ly @@ -0,0 +1,28 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/43b009ff/lilypond/part_III.ly b/resources/string_quartet_1/43b009ff/lilypond/part_III.ly new file mode 100644 index 0000000..0795a41 --- /dev/null +++ b/resources/string_quartet_1/43b009ff/lilypond/part_III.ly @@ -0,0 +1,28 @@ +{ + { 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'1 ~ } + \bar "|" + { fis'4 ~ fis'8[ r8] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/43b009ff/lilypond/part_IV.ly b/resources/string_quartet_1/43b009ff/lilypond/part_IV.ly new file mode 100644 index 0000000..ce1dd80 --- /dev/null +++ b/resources/string_quartet_1/43b009ff/lilypond/part_IV.ly @@ -0,0 +1,28 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/443ec222/443ec222_code.scd b/resources/string_quartet_1/443ec222/443ec222_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/443ec222/443ec222_mus_model.json b/resources/string_quartet_1/443ec222/443ec222_mus_model.json new file mode 100644 index 0000000..26d2794 --- /dev/null +++ b/resources/string_quartet_1/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" ] ], 5.25 ] + ] + ] +], +"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/string_quartet_1/443ec222/lilypond/part_I.ly b/resources/string_quartet_1/443ec222/lilypond/part_I.ly new file mode 100644 index 0000000..da8950a --- /dev/null +++ b/resources/string_quartet_1/443ec222/lilypond/part_I.ly @@ -0,0 +1,18 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/443ec222/lilypond/part_II.ly b/resources/string_quartet_1/443ec222/lilypond/part_II.ly new file mode 100644 index 0000000..3245478 --- /dev/null +++ b/resources/string_quartet_1/443ec222/lilypond/part_II.ly @@ -0,0 +1,18 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/443ec222/lilypond/part_III.ly b/resources/string_quartet_1/443ec222/lilypond/part_III.ly new file mode 100644 index 0000000..0f68e02 --- /dev/null +++ b/resources/string_quartet_1/443ec222/lilypond/part_III.ly @@ -0,0 +1,18 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/443ec222/lilypond/part_IV.ly b/resources/string_quartet_1/443ec222/lilypond/part_IV.ly new file mode 100644 index 0000000..a524c1b --- /dev/null +++ b/resources/string_quartet_1/443ec222/lilypond/part_IV.ly @@ -0,0 +1,18 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4526b76b/4526b76b_code.scd b/resources/string_quartet_1/4526b76b/4526b76b_code.scd new file mode 100644 index 0000000..8e9fe42 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/4526b76b/4526b76b_mus_model.json b/resources/string_quartet_1/4526b76b/4526b76b_mus_model.json new file mode 100644 index 0000000..7489563 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/46985d14/46985d14_code.scd b/resources/string_quartet_1/46985d14/46985d14_code.scd new file mode 100644 index 0000000..3eb3cbd --- /dev/null +++ b/resources/string_quartet_1/46985d14/46985d14_code.scd @@ -0,0 +1,1058 @@ +( +// 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, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark +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; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file; + file = File(path, "w"); + 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"); +//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((dir +/+ ".." +/+ "resources" +/+ 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((dir +/+ ".." +/+ "resources" +/+ curUID).standardizePath); + File.copy(exPath, (dir +/+ ".." +/+ "resources" +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (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; + }); + 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((dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \".." +/+ ".." +/+ "resources" +/+ 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); + +) + +~transcribe.value(~seq, dir); + +( +//synthdefs +~stringModelBusArray = 4.collect({Bus.audio(s, 1)}); +~sineBusArray = 4.collect({Bus.audio(s, 1)}); +~bassBusArray = 1.collect({Bus.audio(s, 1)}); +~hdustBusArray = 1.collect({Bus.audio(s, 1)}); +~samplerBusArray = 2.collect({Bus.audio(s, 1)}); +~sBuf = Buffer.alloc(s, 10, 2); +SynthDef(\string_model, {arg freq, gate = 1, sustain, amp, dur, attack, release = 1, busIndex = 0; + var trig, exc, sig1, sig2, noHarms; + noHarms = rrand(20, 40); + exc = Saw.ar(freq, TRand.ar(0.5, 1, Impulse.ar(freq))) * 0.001 + Dust.ar(10000, 0.01); + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(1, 2)}) ], exc) * 0.8).softclip; + //sig1 = HPF.ar(sig1, 300); + Out.ar(Select.kr(busIndex, ~stringModelBusArray), sig1 * amp * EnvGen.kr(Env.adsr(attack, 0.3, 0.9, release, 0.9, -3), gate, doneAction: 2)); + //Out.ar([0, 1], sig1 * EnvGen.kr(Env.asr(dur, 0.3, 1), gate, doneAction: 2)); +}).add; + +SynthDef(\sine, {arg freq, gate = 1, sustain, amp, dur, busIndex = 0; + var sig; + sig = SinOsc.ar(freq); + Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.asr(0.3, 0.4, 0.3), gate, timeScale: dur, doneAction: 2)); + //Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.sine(dur), gate, doneAction: 2)); +}).add; + +SynthDef(\mixer, {arg freq, gate = 1, sustain, amp, dur, out; + var nameSpaces, sigs; + + sigs = [~stringModelBusArray, ~sineBusArray/*, ~bassBusArray, ~hdustBusArray, ~samplerBusArray*/].collect({arg busArray, i; + var nameSpace, sig; + nameSpace = ['string', 'sine', 'bass', 'hdust', 'sampler'][i]; + sig = busArray.collect({arg bus, c; In.ar(bus, 1) * NamedControl.kr(\ ++ nameSpace ++ '_volume_' ++ c, 1, 0.1)}); + sig = sig.collect({arg channel, c; Pan2.ar(channel, NamedControl.kr(\ ++ nameSpace ++ '_pan_' ++ c, i / (busArray.size - 1), 0.1) * 2 - 1)}); + sig = sig.collect({arg channel, c; channel * NamedControl.kr(\ ++ nameSpace ++ '_mute_' ++ c, 1, 0.1)}); + sig = Mix.ar(sig) * pow(NamedControl.kr(\ ++ nameSpace ++ '_volume_master', 1, 0.1), 2); + }); + + sigs = Mix.ar(sigs / 4); + Out.ar(0, sigs) +}).add; + +SynthDef(\bass, { + var switches, drone; + switches = {|i| Dust.kr(0.1)} ! 9; + drone = {|i| var harm = pow(2, 2 - (i / 3).trunc), amp = (1 / pow(harm, 2)); + SinOsc.ar(60 * harm + TRand.kr(-3, 3, switches[i]), 0, amp)} ! 9; + Out.ar(~bassBusArray[0], Mix.new(drone) * 0.2); +}).add; + +SynthDef(\sampler, { + Out.ar(~samplerBusArray, PlayBuf.ar(2, ~sBuf, BufRateScale.kr(~sBuf), doneAction: 2)) +}).add; + +// main routine +SynthDef(\hdust, { + arg gate = 0; + var hierarchical_dust, low_sine, high_sine, brown_noise, white_noise; + // this triggers the combinations of sources + // it is similar to the Supercollider UGen called dust but with a hierarchical structure + hierarchical_dust = ( + TIRand.kr(0, 1, Impulse.kr(100)) * + TIRand.kr(0, 1, Impulse.kr(10)) * + TIRand.kr(0, 1, Impulse.kr(1)) * + TIRand.kr(0, 1, Impulse.kr(0.1)) + ); + // adjust the multiplier at the end of each line for adjusting levels + // note with each trigger, each source has a 1 in 3 chance of sounding + low_sine = SinOsc.ar(76.midicps / 16) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.1; + high_sine = SinOsc.ar(76.midicps * 8) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.01; + brown_noise = BrownNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.025; + white_noise = WhiteNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.02; + Out.ar(~hdustBusArray[0], + ((low_sine + high_sine + brown_noise + white_noise) ) + ); +}).add; + +) + +( +var bass, hdust, sampler, mixer; +/* +bass = Synth.tail(~group, \bass); +hdust = Synth.tail(~group, \hdust); +sampler = Synth.head(~group, \sampler); +*/ +mixer = Synth.tail(~group, \mixer); + +OSCdef(\mixer, {arg msg, time, addr, port; + mixer.set((msg[1] ++ '_' ++ msg[2] ++ '_' ++ msg[3]), msg[4]) +}, \mixer); + +/* +OSCdef(\sampler, {arg msg, time, addr, port; + msg.postln; + sampler.free; + ~sBuf.free; + ~sBuf = Buffer.read(s, msg[1].asString.postln, action: {sampler = Synth.head(~group, \sampler)}); +}, \sampler); +*/ +) + +/* old something +( +SynthDef(\test, {arg freq, gate = 1, sustain, amp, dur; + var trig, exc, sig1, sig2, noHarms, freqFinal, start, end; + noHarms = 30; + freq = WhiteNoise.ar * 3 + freq; + freqFinal = Duty.ar((1/freq), 0, freq); + trig = Changed.ar(freqFinal); + start = Demand.ar(trig, 0, Dwhite(-1, -0.75)); + end = Demand.ar(trig, 0, Dwhite(0.75, 1)); + exc = Phasor.ar(trig, (end - start) * freqFinal / SampleRate.ir, start, end, 0) * 0.001 + Dust.ar(10000, 0.01); + + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(2, 3)}) ], exc) * 0.5).softclip; + sig1 = HPF.ar(sig1, 300); + Out.ar([0, 1], sig1 * EnvGen.kr(Env.adsr(0.3, 0.3, 0.9, 0.5, 0.9), gate, doneAction: 2)); +}).add; +) +*/ + diff --git a/resources/string_quartet_1/46985d14/46985d14_mus_model.json b/resources/string_quartet_1/46985d14/46985d14_mus_model.json new file mode 100644 index 0000000..de147a2 --- /dev/null +++ b/resources/string_quartet_1/46985d14/46985d14_mus_model.json @@ -0,0 +1,85 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ 0, -1, 0, 0, 1, 0 ], [ "Rest" ] ], 1.75 ], + [ [ [ -1, -1, 0, 1, 1, 0 ], [ "Rest" ], [ 0, -1, 0, 0, 1, 0 ], [ "Rest" ] ], 0 ], + [ [ [ -1, -1, 0, 1, 1, 0 ], [ "Rest" ], [ 0, -1, 0, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ] ], 0 ], + [ [ [ -1, -1, 0, 1, 1, 0 ], [ -1, -1, 0, 0, 1, 1 ], [ 0, -1, 0, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ] ], 1.25 ], + [ [ [ 0, -1, -1, 0, 1, 0 ], [ -1, -1, 0, 0, 1, 1 ], [ 0, -1, 0, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ] ], 1.75 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ -1, -1, 0, 0, 1, 1 ], [ 0, -1, 0, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ] ], 0 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ -1, -1, 0, 0, 1, 1 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -2, 0, 0, 1, 0 ] ], 0 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ -1, -1, 0, 1, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -2, 0, 0, 1, 0 ] ], 0.875 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -2, 0, 0, 1, 0 ] ], 0 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, 0, 1, -1 ] ], 1.625 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 1, -2, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, 0, 1, -1 ] ], 1.75 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, 0, 1, -1 ] ], 0 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 0.875 ] + ], + [ + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ -1, 0, 1, 0, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 0.625 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, -1 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.625 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ -1, 0, 0, 0, 2, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.125 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, -1, -1, -1, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 1 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, 0, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 0.5 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, -1, 1, 1 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.625 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.5 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, -1 ], [ 1, -1, 0, -1, 1, 0 ] ], 0.5 ] + ], + [ + [ [ [ 1, -2, 0, -1, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, -1 ], [ 1, -1, 0, -1, 1, 0 ] ], 0.5 ], + [ [ [ 0, 0, 0, -1, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, -1 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.5 ], + [ [ [ -1, 0, 0, 1, 1, -1 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, -1 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.5 ], + [ [ [ 1, -1, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, -1 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.125 ] + ], + [ + [ [ [ 1, -1, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, -1 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, -1 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ "Rest" ] ], 1.375 ] + ], + [ + [ [ [ "Rest" ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.75 ], + [ [ [ 0, -1, 0, -1, 2, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.875 ], + [ [ [ -1, -1, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 0.75 ], + [ [ [ 0, -1, -1, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.25 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 1 ], + [ [ [ 1, -2, 0, -1, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.375 ], + [ [ [ 1, -1, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 2 ], + [ [ [ 1, -1, 0, -1, 0, 0 ], [ "Rest" ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 0 ], + [ [ [ 1, -1, 0, -1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, -1, 0, -1, 1, 0 ] ], 4.75 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 1, -1, 0, -1, 1, 0 ] ], 8.875 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ 0, 0, -1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ 0, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ] +], +"cur_uid": "46985d14", +"ref_uid": "nil", +"order_seed": 209164, +"dur_seed": 417909, +"motifs_seed": 885208, +"entrances_probs_vals": [ 0.34, 0, 10, 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, 0, 10, 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, 10, 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 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 2 ], [ 0, 0, 0, 0 ], [ 1, 3 ] ] +], +"sus_weights": [ 0.75, 0.75, 0.75 ], +"order_size": [ 1, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_1/46985d14/lilypond/part_I.ly b/resources/string_quartet_1/46985d14/lilypond/part_I.ly new file mode 100644 index 0000000..f38f2bd --- /dev/null +++ b/resources/string_quartet_1/46985d14/lilypond/part_I.ly @@ -0,0 +1,50 @@ +{ + { r2. r8[ d'8^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'4 ~ d'8[ dis'8^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }}] ~ dis'4 ~ dis'16[ d'8.^\markup { \pad-markup #0.2 "+9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'2 cis'2^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }} ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'2 r2 } + \bar "|" + { r8.[ cis'16^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }}] ~ cis'2. ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/46985d14/lilypond/part_II.ly b/resources/string_quartet_1/46985d14/lilypond/part_II.ly new file mode 100644 index 0000000..2bae7a3 --- /dev/null +++ b/resources/string_quartet_1/46985d14/lilypond/part_II.ly @@ -0,0 +1,50 @@ +{ + { ais1^\markup { \pad-markup #0.2 "+49"} ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais2. ~ ais8.[ a16^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { a4 a2.^\markup { \pad-markup #0.2 "+11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }} ~ } + \bar "|" + { a16[ b8.^\markup { \pad-markup #0.2 "+3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ b4 ~ b8[ a8^\markup { \pad-markup #0.2 "-6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}] ~ a4 ~ } + \bar "|" + { a8[ gis8^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}] ~ gis8[ a8^\markup { \pad-markup #0.2 "+21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ~ a2 ~ } + \bar "|" + { a8.[ ais16^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ ais2 ~ ais8.[ a16^\markup { \pad-markup #0.2 "+11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a2 ais2^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais8.[ r16] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/46985d14/lilypond/part_III.ly b/resources/string_quartet_1/46985d14/lilypond/part_III.ly new file mode 100644 index 0000000..3ee54a1 --- /dev/null +++ b/resources/string_quartet_1/46985d14/lilypond/part_III.ly @@ -0,0 +1,50 @@ +{ + { r2. r8[ g8^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }}] ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g4 ~ g8[ gis8^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }}] ~ gis4 ~ gis16[ cis'8.^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }}] ~ } + \bar "|" + { cis'2 ~ cis'8[ dis'8^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }}] ~ dis'4 ~ } + \bar "|" + { dis'2 fis'2^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'8.[ r16] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/46985d14/lilypond/part_IV.ly b/resources/string_quartet_1/46985d14/lilypond/part_IV.ly new file mode 100644 index 0000000..f0e3f84 --- /dev/null +++ b/resources/string_quartet_1/46985d14/lilypond/part_IV.ly @@ -0,0 +1,50 @@ +{ + { r2. r8[ gis8^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { gis2 g2^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }} ~ } + \bar "|" + { g4 ~ g8[ fis8^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }}] ~ fis2 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis8.[ fis16^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }}] ~ fis8.[ gis16^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }}] ~ gis2 ~ } + \bar "|" + { gis8.[ g16^\markup { \pad-markup #0.2 "-20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }}] ~ g2 ~ g8.[ g16^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }}] ~ } + \bar "|" + { g2 r2 } + \bar "|" + { r1 } + \bar "|" + { r16[ fis8.^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}] ~ fis2. } + \bar "|" + { g4^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }} ~ g8[ g8^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }}] ~ g2 } + \bar "|" + { fis2^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} fis2^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} ~ } + \bar "|" + { fis8.[ g16^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }}] ~ g2. ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g2 ~ g16[ r8.] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/490b1e6e/490b1e6e_code.scd b/resources/string_quartet_1/490b1e6e/490b1e6e_code.scd new file mode 100644 index 0000000..3eb3cbd --- /dev/null +++ b/resources/string_quartet_1/490b1e6e/490b1e6e_code.scd @@ -0,0 +1,1058 @@ +( +// 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, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark +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; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file; + file = File(path, "w"); + 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"); +//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((dir +/+ ".." +/+ "resources" +/+ 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((dir +/+ ".." +/+ "resources" +/+ curUID).standardizePath); + File.copy(exPath, (dir +/+ ".." +/+ "resources" +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (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; + }); + 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((dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \".." +/+ ".." +/+ "resources" +/+ 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); + +) + +~transcribe.value(~seq, dir); + +( +//synthdefs +~stringModelBusArray = 4.collect({Bus.audio(s, 1)}); +~sineBusArray = 4.collect({Bus.audio(s, 1)}); +~bassBusArray = 1.collect({Bus.audio(s, 1)}); +~hdustBusArray = 1.collect({Bus.audio(s, 1)}); +~samplerBusArray = 2.collect({Bus.audio(s, 1)}); +~sBuf = Buffer.alloc(s, 10, 2); +SynthDef(\string_model, {arg freq, gate = 1, sustain, amp, dur, attack, release = 1, busIndex = 0; + var trig, exc, sig1, sig2, noHarms; + noHarms = rrand(20, 40); + exc = Saw.ar(freq, TRand.ar(0.5, 1, Impulse.ar(freq))) * 0.001 + Dust.ar(10000, 0.01); + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(1, 2)}) ], exc) * 0.8).softclip; + //sig1 = HPF.ar(sig1, 300); + Out.ar(Select.kr(busIndex, ~stringModelBusArray), sig1 * amp * EnvGen.kr(Env.adsr(attack, 0.3, 0.9, release, 0.9, -3), gate, doneAction: 2)); + //Out.ar([0, 1], sig1 * EnvGen.kr(Env.asr(dur, 0.3, 1), gate, doneAction: 2)); +}).add; + +SynthDef(\sine, {arg freq, gate = 1, sustain, amp, dur, busIndex = 0; + var sig; + sig = SinOsc.ar(freq); + Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.asr(0.3, 0.4, 0.3), gate, timeScale: dur, doneAction: 2)); + //Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.sine(dur), gate, doneAction: 2)); +}).add; + +SynthDef(\mixer, {arg freq, gate = 1, sustain, amp, dur, out; + var nameSpaces, sigs; + + sigs = [~stringModelBusArray, ~sineBusArray/*, ~bassBusArray, ~hdustBusArray, ~samplerBusArray*/].collect({arg busArray, i; + var nameSpace, sig; + nameSpace = ['string', 'sine', 'bass', 'hdust', 'sampler'][i]; + sig = busArray.collect({arg bus, c; In.ar(bus, 1) * NamedControl.kr(\ ++ nameSpace ++ '_volume_' ++ c, 1, 0.1)}); + sig = sig.collect({arg channel, c; Pan2.ar(channel, NamedControl.kr(\ ++ nameSpace ++ '_pan_' ++ c, i / (busArray.size - 1), 0.1) * 2 - 1)}); + sig = sig.collect({arg channel, c; channel * NamedControl.kr(\ ++ nameSpace ++ '_mute_' ++ c, 1, 0.1)}); + sig = Mix.ar(sig) * pow(NamedControl.kr(\ ++ nameSpace ++ '_volume_master', 1, 0.1), 2); + }); + + sigs = Mix.ar(sigs / 4); + Out.ar(0, sigs) +}).add; + +SynthDef(\bass, { + var switches, drone; + switches = {|i| Dust.kr(0.1)} ! 9; + drone = {|i| var harm = pow(2, 2 - (i / 3).trunc), amp = (1 / pow(harm, 2)); + SinOsc.ar(60 * harm + TRand.kr(-3, 3, switches[i]), 0, amp)} ! 9; + Out.ar(~bassBusArray[0], Mix.new(drone) * 0.2); +}).add; + +SynthDef(\sampler, { + Out.ar(~samplerBusArray, PlayBuf.ar(2, ~sBuf, BufRateScale.kr(~sBuf), doneAction: 2)) +}).add; + +// main routine +SynthDef(\hdust, { + arg gate = 0; + var hierarchical_dust, low_sine, high_sine, brown_noise, white_noise; + // this triggers the combinations of sources + // it is similar to the Supercollider UGen called dust but with a hierarchical structure + hierarchical_dust = ( + TIRand.kr(0, 1, Impulse.kr(100)) * + TIRand.kr(0, 1, Impulse.kr(10)) * + TIRand.kr(0, 1, Impulse.kr(1)) * + TIRand.kr(0, 1, Impulse.kr(0.1)) + ); + // adjust the multiplier at the end of each line for adjusting levels + // note with each trigger, each source has a 1 in 3 chance of sounding + low_sine = SinOsc.ar(76.midicps / 16) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.1; + high_sine = SinOsc.ar(76.midicps * 8) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.01; + brown_noise = BrownNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.025; + white_noise = WhiteNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.02; + Out.ar(~hdustBusArray[0], + ((low_sine + high_sine + brown_noise + white_noise) ) + ); +}).add; + +) + +( +var bass, hdust, sampler, mixer; +/* +bass = Synth.tail(~group, \bass); +hdust = Synth.tail(~group, \hdust); +sampler = Synth.head(~group, \sampler); +*/ +mixer = Synth.tail(~group, \mixer); + +OSCdef(\mixer, {arg msg, time, addr, port; + mixer.set((msg[1] ++ '_' ++ msg[2] ++ '_' ++ msg[3]), msg[4]) +}, \mixer); + +/* +OSCdef(\sampler, {arg msg, time, addr, port; + msg.postln; + sampler.free; + ~sBuf.free; + ~sBuf = Buffer.read(s, msg[1].asString.postln, action: {sampler = Synth.head(~group, \sampler)}); +}, \sampler); +*/ +) + +/* old something +( +SynthDef(\test, {arg freq, gate = 1, sustain, amp, dur; + var trig, exc, sig1, sig2, noHarms, freqFinal, start, end; + noHarms = 30; + freq = WhiteNoise.ar * 3 + freq; + freqFinal = Duty.ar((1/freq), 0, freq); + trig = Changed.ar(freqFinal); + start = Demand.ar(trig, 0, Dwhite(-1, -0.75)); + end = Demand.ar(trig, 0, Dwhite(0.75, 1)); + exc = Phasor.ar(trig, (end - start) * freqFinal / SampleRate.ir, start, end, 0) * 0.001 + Dust.ar(10000, 0.01); + + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(2, 3)}) ], exc) * 0.5).softclip; + sig1 = HPF.ar(sig1, 300); + Out.ar([0, 1], sig1 * EnvGen.kr(Env.adsr(0.3, 0.3, 0.9, 0.5, 0.9), gate, doneAction: 2)); +}).add; +) +*/ + diff --git a/resources/string_quartet_1/490b1e6e/490b1e6e_mus_model.json b/resources/string_quartet_1/490b1e6e/490b1e6e_mus_model.json new file mode 100644 index 0000000..64ab1de --- /dev/null +++ b/resources/string_quartet_1/490b1e6e/490b1e6e_mus_model.json @@ -0,0 +1,63 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ -2, 0, 0, 1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.375 ], + [ [ [ -2, 0, 0, 1, 1, 1 ], [ -2, 0, 0, 1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0.875 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 0, 0, 1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0.875 ], + [ [ [ -2, 0, 0, 1, 2, 0 ], [ -2, 0, 0, 1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0.75 ], + [ [ [ -1, 0, 0, 1, 0, 0 ], [ -2, 0, 0, 1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.5 ], + [ [ [ -1, -1, 0, 1, 1, 0 ], [ -2, 0, 0, 1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.125 ], + [ [ [ -2, 0, 1, 1, 1, 0 ], [ -2, 0, 0, 1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.625 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ -2, 0, 0, 1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.625 ] + ], + [ + [ [ [ -1, 0, 0, 0, 1, 0 ], [ -2, 0, 0, 1, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ "Rest" ] ], 0 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ -2, 0, 0, 1, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 1.25 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ -1, -1, 1, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 1.125 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, -2, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 1 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ -2, 1, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 0.5 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ -1, 0, -1, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 1.625 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, -1 ], [ 0, -1, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 2 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, -1, 0, -1, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 1.75 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ -1, -1, 1, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 1.5 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, -2, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 1.25 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 0, -2, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ "Rest" ] ], 0.875 ], + [ [ [ "Rest" ], [ 0, -2, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 0, -2, 0, 0, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1.375 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ 0, 0, -1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ 0, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ] +], +"cur_uid": "490b1e6e", +"ref_uid": "nil", +"order_seed": 209164, +"dur_seed": 417909, +"motifs_seed": 885208, +"entrances_probs_vals": [ 0.34, 0, 10, 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, 0, 10, 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, 10, 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 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 2 ], [ 0, 0, 0, 0 ], [ 1, 3 ] ] +], +"sus_weights": [ 0.75, 0.75, 0.75 ], +"order_size": [ 1, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_1/490b1e6e/lilypond/part_I.ly b/resources/string_quartet_1/490b1e6e/lilypond/part_I.ly new file mode 100644 index 0000000..57e7dcf --- /dev/null +++ b/resources/string_quartet_1/490b1e6e/lilypond/part_I.ly @@ -0,0 +1,26 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. r8[ fis'8^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'2. ~ fis'8[ r8] } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/490b1e6e/lilypond/part_II.ly b/resources/string_quartet_1/490b1e6e/lilypond/part_II.ly new file mode 100644 index 0000000..72acd78 --- /dev/null +++ b/resources/string_quartet_1/490b1e6e/lilypond/part_II.ly @@ -0,0 +1,26 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. r8[ ais8^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais4 ~ ais16[ r8.] r2 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/490b1e6e/lilypond/part_III.ly b/resources/string_quartet_1/490b1e6e/lilypond/part_III.ly new file mode 100644 index 0000000..c698c38 --- /dev/null +++ b/resources/string_quartet_1/490b1e6e/lilypond/part_III.ly @@ -0,0 +1,26 @@ +{ + { dis1^\markup { \pad-markup #0.2 "+20"} ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis2 d2^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }} ~ } + \bar "|" + { d16[ dis8.^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }}] ~ dis4 ~ dis16[ cis8.^\markup { \pad-markup #0.2 "-47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ cis16[ d8.^\markup { \pad-markup #0.2 "-35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ } + \bar "|" + { d2 ~ d8[ d8^\markup { \pad-markup #0.2 "+9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↓" }}] ~ d4 ~ } + \bar "|" + { d2 ~ d8[ cis8^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }}] ~ cis4 ~ } + \bar "|" + { cis2 d2^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }} ~ } + \bar "|" + { d4 dis2.^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }} ~ } + \bar "|" + { dis4 ~ dis16[ r8.] r2 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/490b1e6e/lilypond/part_IV.ly b/resources/string_quartet_1/490b1e6e/lilypond/part_IV.ly new file mode 100644 index 0000000..72aea57 --- /dev/null +++ b/resources/string_quartet_1/490b1e6e/lilypond/part_IV.ly @@ -0,0 +1,26 @@ +{ + { r2 r8.[ c'16^\markup { \pad-markup #0.2 "-39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ c'4 ~ } + \bar "|" + { c'8[ ais8^\markup { \pad-markup #0.2 "+22"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}] ~ ais4 ~ ais16[ a8.^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↑" }}] ~ a8.[ ais16^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }}] ~ } + \bar "|" + { ais2 ~ ais8.[ gis16^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }}] ~ gis4 ~ } + \bar "|" + { gis4 g2.^\markup { \pad-markup #0.2 "+6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }} ~ } + \bar "|" + { g16[ fis8.^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }}] ~ fis2. ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis4 ~ fis16[ r8.] r2 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4a8a6e53/4a8a6e53_code.scd b/resources/string_quartet_1/4a8a6e53/4a8a6e53_code.scd new file mode 100644 index 0000000..3eb3cbd --- /dev/null +++ b/resources/string_quartet_1/4a8a6e53/4a8a6e53_code.scd @@ -0,0 +1,1058 @@ +( +// 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, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark +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; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file; + file = File(path, "w"); + 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"); +//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((dir +/+ ".." +/+ "resources" +/+ 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((dir +/+ ".." +/+ "resources" +/+ curUID).standardizePath); + File.copy(exPath, (dir +/+ ".." +/+ "resources" +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (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; + }); + 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((dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \".." +/+ ".." +/+ "resources" +/+ 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); + +) + +~transcribe.value(~seq, dir); + +( +//synthdefs +~stringModelBusArray = 4.collect({Bus.audio(s, 1)}); +~sineBusArray = 4.collect({Bus.audio(s, 1)}); +~bassBusArray = 1.collect({Bus.audio(s, 1)}); +~hdustBusArray = 1.collect({Bus.audio(s, 1)}); +~samplerBusArray = 2.collect({Bus.audio(s, 1)}); +~sBuf = Buffer.alloc(s, 10, 2); +SynthDef(\string_model, {arg freq, gate = 1, sustain, amp, dur, attack, release = 1, busIndex = 0; + var trig, exc, sig1, sig2, noHarms; + noHarms = rrand(20, 40); + exc = Saw.ar(freq, TRand.ar(0.5, 1, Impulse.ar(freq))) * 0.001 + Dust.ar(10000, 0.01); + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(1, 2)}) ], exc) * 0.8).softclip; + //sig1 = HPF.ar(sig1, 300); + Out.ar(Select.kr(busIndex, ~stringModelBusArray), sig1 * amp * EnvGen.kr(Env.adsr(attack, 0.3, 0.9, release, 0.9, -3), gate, doneAction: 2)); + //Out.ar([0, 1], sig1 * EnvGen.kr(Env.asr(dur, 0.3, 1), gate, doneAction: 2)); +}).add; + +SynthDef(\sine, {arg freq, gate = 1, sustain, amp, dur, busIndex = 0; + var sig; + sig = SinOsc.ar(freq); + Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.asr(0.3, 0.4, 0.3), gate, timeScale: dur, doneAction: 2)); + //Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.sine(dur), gate, doneAction: 2)); +}).add; + +SynthDef(\mixer, {arg freq, gate = 1, sustain, amp, dur, out; + var nameSpaces, sigs; + + sigs = [~stringModelBusArray, ~sineBusArray/*, ~bassBusArray, ~hdustBusArray, ~samplerBusArray*/].collect({arg busArray, i; + var nameSpace, sig; + nameSpace = ['string', 'sine', 'bass', 'hdust', 'sampler'][i]; + sig = busArray.collect({arg bus, c; In.ar(bus, 1) * NamedControl.kr(\ ++ nameSpace ++ '_volume_' ++ c, 1, 0.1)}); + sig = sig.collect({arg channel, c; Pan2.ar(channel, NamedControl.kr(\ ++ nameSpace ++ '_pan_' ++ c, i / (busArray.size - 1), 0.1) * 2 - 1)}); + sig = sig.collect({arg channel, c; channel * NamedControl.kr(\ ++ nameSpace ++ '_mute_' ++ c, 1, 0.1)}); + sig = Mix.ar(sig) * pow(NamedControl.kr(\ ++ nameSpace ++ '_volume_master', 1, 0.1), 2); + }); + + sigs = Mix.ar(sigs / 4); + Out.ar(0, sigs) +}).add; + +SynthDef(\bass, { + var switches, drone; + switches = {|i| Dust.kr(0.1)} ! 9; + drone = {|i| var harm = pow(2, 2 - (i / 3).trunc), amp = (1 / pow(harm, 2)); + SinOsc.ar(60 * harm + TRand.kr(-3, 3, switches[i]), 0, amp)} ! 9; + Out.ar(~bassBusArray[0], Mix.new(drone) * 0.2); +}).add; + +SynthDef(\sampler, { + Out.ar(~samplerBusArray, PlayBuf.ar(2, ~sBuf, BufRateScale.kr(~sBuf), doneAction: 2)) +}).add; + +// main routine +SynthDef(\hdust, { + arg gate = 0; + var hierarchical_dust, low_sine, high_sine, brown_noise, white_noise; + // this triggers the combinations of sources + // it is similar to the Supercollider UGen called dust but with a hierarchical structure + hierarchical_dust = ( + TIRand.kr(0, 1, Impulse.kr(100)) * + TIRand.kr(0, 1, Impulse.kr(10)) * + TIRand.kr(0, 1, Impulse.kr(1)) * + TIRand.kr(0, 1, Impulse.kr(0.1)) + ); + // adjust the multiplier at the end of each line for adjusting levels + // note with each trigger, each source has a 1 in 3 chance of sounding + low_sine = SinOsc.ar(76.midicps / 16) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.1; + high_sine = SinOsc.ar(76.midicps * 8) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.01; + brown_noise = BrownNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.025; + white_noise = WhiteNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.02; + Out.ar(~hdustBusArray[0], + ((low_sine + high_sine + brown_noise + white_noise) ) + ); +}).add; + +) + +( +var bass, hdust, sampler, mixer; +/* +bass = Synth.tail(~group, \bass); +hdust = Synth.tail(~group, \hdust); +sampler = Synth.head(~group, \sampler); +*/ +mixer = Synth.tail(~group, \mixer); + +OSCdef(\mixer, {arg msg, time, addr, port; + mixer.set((msg[1] ++ '_' ++ msg[2] ++ '_' ++ msg[3]), msg[4]) +}, \mixer); + +/* +OSCdef(\sampler, {arg msg, time, addr, port; + msg.postln; + sampler.free; + ~sBuf.free; + ~sBuf = Buffer.read(s, msg[1].asString.postln, action: {sampler = Synth.head(~group, \sampler)}); +}, \sampler); +*/ +) + +/* old something +( +SynthDef(\test, {arg freq, gate = 1, sustain, amp, dur; + var trig, exc, sig1, sig2, noHarms, freqFinal, start, end; + noHarms = 30; + freq = WhiteNoise.ar * 3 + freq; + freqFinal = Duty.ar((1/freq), 0, freq); + trig = Changed.ar(freqFinal); + start = Demand.ar(trig, 0, Dwhite(-1, -0.75)); + end = Demand.ar(trig, 0, Dwhite(0.75, 1)); + exc = Phasor.ar(trig, (end - start) * freqFinal / SampleRate.ir, start, end, 0) * 0.001 + Dust.ar(10000, 0.01); + + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(2, 3)}) ], exc) * 0.5).softclip; + sig1 = HPF.ar(sig1, 300); + Out.ar([0, 1], sig1 * EnvGen.kr(Env.adsr(0.3, 0.3, 0.9, 0.5, 0.9), gate, doneAction: 2)); +}).add; +) +*/ + diff --git a/resources/string_quartet_1/4a8a6e53/4a8a6e53_mus_model.json b/resources/string_quartet_1/4a8a6e53/4a8a6e53_mus_model.json new file mode 100644 index 0000000..150baf5 --- /dev/null +++ b/resources/string_quartet_1/4a8a6e53/4a8a6e53_mus_model.json @@ -0,0 +1,97 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 0.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 0.625 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 1.5 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 0.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 1.25 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ] ], 0.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ] ], 0.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1.25 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0.75 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 0.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ], [ "Rest" ] ], 1.5 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 1, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ "Rest" ] ], 1.25 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ "Rest" ] ], 1.375 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ "Rest" ] ], 0.625 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 0, 1 ], [ "Rest" ] ], 1.125 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ] ], 1.25 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ "Rest" ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ "Rest" ] ], 0.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, -1, 0, -1, 0 ] ], 0.875 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, -1, 0, -1, 0 ] ], 0.75 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 1, -1, 0 ], [ 1, 0, -1, 0, -1, 0 ] ], 1.5 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 1, -1, 0 ], [ 0, 0, 0, 0, -1, 1 ] ], 1.25 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 1, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 0.625 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 0.625 ], + [ [ [ 0, 0, 1, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 0.625 ], + [ [ [ 1, 0, 0, -1, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 1.375 ], + [ [ [ 1, 0, 0, -1, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 1, 0, -1, 0 ] ], 1.375 ], + [ [ [ 1, 0, 0, -1, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 2, 0, 0, -1, -1, 0 ], [ 0, 0, 1, 0, -1, 0 ] ], 1.5 ], + [ [ [ 1, 0, 0, -1, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 2, 0, 0, 0, -1, -1 ], [ 0, 0, 1, 0, -1, 0 ] ], 0.75 ], + [ [ [ 1, 0, 0, -1, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 2, 0, 0, 0, -1, -1 ], [ 1, -1, 0, 0, -1, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, 0, -1, -1, 0 ], [ "Rest" ], [ 2, 0, 0, 0, -1, -1 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.75 ], + [ [ [ 0, -1, 0, 1, -1, 0 ], [ "Rest" ], [ 2, 0, 0, 0, -1, -1 ], [ 1, -1, 0, 0, -1, 0 ] ], 1 ], + [ [ [ 0, -1, 0, 1, -1, 0 ], [ "Rest" ], [ 2, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.875 ], + [ [ [ 0, -1, 0, 0, -1, 1 ], [ "Rest" ], [ 2, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 1.25 ], + [ [ [ 0, -1, 0, 0, -1, 1 ], [ "Rest" ], [ 1, -1, 0, 1, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 1.125 ], + [ [ [ 1, -1, -1, 0, -1, 0 ], [ "Rest" ], [ 1, -1, 0, 1, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 1 ], + [ [ [ 1, -1, -1, 0, -1, 0 ], [ "Rest" ], [ 1, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.75 ], + [ [ [ 1, -1, -1, 0, -1, 0 ], [ "Rest" ], [ 1, -1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.625 ], + [ [ [ 0, -1, 0, 0, 0, 0 ], [ "Rest" ], [ 1, -1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.75 ], + [ [ [ 1, -1, 0, 0, -2, 0 ], [ "Rest" ], [ 1, -1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 1.375 ], + [ [ [ 1, -1, 0, 0, -2, 0 ], [ "Rest" ], [ 2, -2, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.875 ], + [ [ [ 0, -1, 0, 0, -1, 1 ], [ "Rest" ], [ 2, -2, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 3.375 ], + [ [ [ 0, -1, 0, 0, -1, 1 ], [ "Rest" ], [ 2, -2, 0, 0, -1, 0 ], [ "Rest" ] ], 1.5 ], + [ [ [ 0, -1, 0, 0, -1, 1 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1.375 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 2 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 1, 0, -1 ], [ 0, 0, 0, 0, 0, 0 ], [ 2, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, -2 ] ], + [ [ 0, 0, 0, 1, 0, -1 ], [ 0, 0, 0, 0, 0, 0 ], [ 2, 0, 0, 0, 0, -1 ], [ -1, 0, 0, 1, 0, -1 ] ], + [ [ 0, 0, 0, 1, 0, -1 ], [ 0, 0, 0, 0, 0, 0 ], [ 2, 0, 0, 0, 0, -1 ], [ 0, 0, 0, 0, -1, 0 ] ], + [ [ 0, 0, 0, 1, 0, -1 ], [ 0, 0, 0, 0, 0, 0 ], [ 2, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ] ], + [ [ 1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 2, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ] ] +], +"cur_uid": "4a8a6e53", +"ref_uid": "nil", +"order_seed": 320463, +"dur_seed": 903977, +"motifs_seed": 895384, +"entrances_probs_vals": [ 0.75, 0, 10, 0, 0.5, 0.26424870466321, 0.75675675675676, 0.5, 0.5, 0.58549222797927, 0.72635135135135, 1, 0.5 ], +"passages_probs_vals": [ 0.75, 0, 10, 0, 0.5, 0.20725388601036, 0.68581081081081, 0.24093264248705, 0.34121621621622, 0.5, 0.5, 0.67616580310881, 0.81081081081081, 1, 0.5 ], +"exits_probs_vals": [ 0.75, 0, 10, 0, 0.5, 0.20725388601036, 0.68581081081081, 0.24093264248705, 0.34121621621622, 0.5, 0.5, 0.67616580310881, 0.81081081081081, 1, 0.5 ], +"ranges": [ [ -384, 2400 ], [ -507, 2400 ], [ -282, 2237 ], [ -1200, 2053 ] ], +"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 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 1 ], [ 0, 3, 2, 3, 3, 2 ], [ ] ], + [ [ 2, 1 ], [ 3, 0, 3 ], [ ] ], + [ [ 1 ], [ 3, 2, 0 ], [ ] ] +], +"sus_weights": [ 0.75, 0.69, 0.75 ], +"order_size": [ 2, 6 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4a8a6e53/lilypond/part_I.ly b/resources/string_quartet_1/4a8a6e53/lilypond/part_I.ly new file mode 100644 index 0000000..e9e6601 --- /dev/null +++ b/resources/string_quartet_1/4a8a6e53/lilypond/part_I.ly @@ -0,0 +1,56 @@ +{ + { r2. e'4^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }} ~ } + \bar "|" + { e'2 e'4^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ e'8.[ f'16^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { f'4 ~ f'8[ g'8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ g'4 ~ g'8[ ais'8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { ais'4 ~ ais'8[ c''8^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ c''2 } + \bar "|" + { gis'4^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }} ~ gis'8.[ gis'16^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ gis'4 ~ gis'8[ f'8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { f'2 e'4^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ e'8[ r8] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 r16[ dis'8.^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}] ~ dis'4 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'8[ dis'8^\markup { \pad-markup #0.2 "-11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ dis'2 d'4^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'4 ~ d'8[ ais8^\markup { \pad-markup #0.2 "+35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] ~ ais2 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais8.[ b16^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }}] ~ b2. ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2 ~ b16[ r8.] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4a8a6e53/lilypond/part_II.ly b/resources/string_quartet_1/4a8a6e53/lilypond/part_II.ly new file mode 100644 index 0000000..3c00ff8 --- /dev/null +++ b/resources/string_quartet_1/4a8a6e53/lilypond/part_II.ly @@ -0,0 +1,56 @@ +{ + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'4 ~ c'16[ e'8.^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ e'2 ~ } + \bar "|" + { e'2 ~ e'8.[ g'16^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ g'4 ~ } + \bar "|" + { g'4 ~ g'8[ ais'8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ ais'2 ~ } + \bar "|" + { ais'4 ~ ais'8.[ gis'16^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}] ~ gis'2 } + \bar "|" + { gis'2^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ gis'8[ g'8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ g'4 ~ } + \bar "|" + { g'8[ f'8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ f'2. ~ } + \bar "|" + { f'4 ~ f'8[ e'8^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}] ~ e'2 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'16[ fis'8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ fis'2. ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'16[ a'8.^\markup { \pad-markup #0.2 "-20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ a'2 ~ a'16[ ais'8.^\markup { \pad-markup #0.2 "+8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'2 ~ ais'16[ b'8.^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ~ b'4 ~ } + \bar "|" + { b'2 ~ b'8[ a'8^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}] ~ a'4 ~ } + \bar "|" + { a'2 ~ a'8.[ fis'16^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ fis'4 ~ } + \bar "|" + { fis'16[ f'8.^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}] ~ f'2. ~ } + \bar "|" + { f'4 ~ f'8.[ e'16^\markup { \pad-markup #0.2 "+45"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }}] ~ e'2 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'4 ~ e'16[ r8.] r2 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4a8a6e53/lilypond/part_III.ly b/resources/string_quartet_1/4a8a6e53/lilypond/part_III.ly new file mode 100644 index 0000000..75dcb1c --- /dev/null +++ b/resources/string_quartet_1/4a8a6e53/lilypond/part_III.ly @@ -0,0 +1,56 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r16[ ais8.^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ ais2. ~ } + \bar "|" + { ais2. gis4^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { gis16[ fis8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}] ~ fis2. ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis2 ~ fis8.[ r16] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \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/string_quartet_1/4a8a6e53/lilypond/part_IV.ly b/resources/string_quartet_1/4a8a6e53/lilypond/part_IV.ly new file mode 100644 index 0000000..41471e8 --- /dev/null +++ b/resources/string_quartet_1/4a8a6e53/lilypond/part_IV.ly @@ -0,0 +1,56 @@ +{ + { r4 r8.[ c'16^\markup { \pad-markup #0.2 "+0"}] ~ c'2 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 } + \bar "|" + { b1^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b4 ~ b8[ ais8^\markup { \pad-markup #0.2 "+35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] ~ ais8.[ a16^\markup { \pad-markup #0.2 "-20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }}] ~ a4 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a16[ a8.^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}] ~ a2. } + \bar "|" + { gis1^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }} ~ } + \bar "|" + { gis8.[ gis16^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}] ~ gis2. ~ } + \bar "|" + { gis4 ~ gis8[ f8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ f4 fis4^\markup { \pad-markup #0.2 "-5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }} ~ } + \bar "|" + { fis2. ~ fis8[ gis8^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ~ } + \bar "|" + { gis1 ~ } + \bar "|" + { gis1 ~ } + \bar "|" + { gis1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4b7745df/4b7745df_code.scd b/resources/string_quartet_1/4b7745df/4b7745df_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/4b7745df/4b7745df_mus_model.json b/resources/string_quartet_1/4b7745df/4b7745df_mus_model.json new file mode 100644 index 0000000..f30b098 --- /dev/null +++ b/resources/string_quartet_1/4b7745df/4b7745df_mus_model.json @@ -0,0 +1,48 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 7.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/string_quartet_1/4b7745df/lilypond/part_I.ly b/resources/string_quartet_1/4b7745df/lilypond/part_I.ly new file mode 100644 index 0000000..d9ec1f9 --- /dev/null +++ b/resources/string_quartet_1/4b7745df/lilypond/part_I.ly @@ -0,0 +1,28 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4b7745df/lilypond/part_II.ly b/resources/string_quartet_1/4b7745df/lilypond/part_II.ly new file mode 100644 index 0000000..6118a4a --- /dev/null +++ b/resources/string_quartet_1/4b7745df/lilypond/part_II.ly @@ -0,0 +1,28 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4b7745df/lilypond/part_III.ly b/resources/string_quartet_1/4b7745df/lilypond/part_III.ly new file mode 100644 index 0000000..80764d9 --- /dev/null +++ b/resources/string_quartet_1/4b7745df/lilypond/part_III.ly @@ -0,0 +1,28 @@ +{ + { 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'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'16[ r8.] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4b7745df/lilypond/part_IV.ly b/resources/string_quartet_1/4b7745df/lilypond/part_IV.ly new file mode 100644 index 0000000..8cc8f1d --- /dev/null +++ b/resources/string_quartet_1/4b7745df/lilypond/part_IV.ly @@ -0,0 +1,28 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4e7d35e5/4e7d35e5_code.scd b/resources/string_quartet_1/4e7d35e5/4e7d35e5_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/4e7d35e5/4e7d35e5_mus_model.json b/resources/string_quartet_1/4e7d35e5/4e7d35e5_mus_model.json new file mode 100644 index 0000000..0eebea8 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/4e7d35e5/lilypond/part_I.ly b/resources/string_quartet_1/4e7d35e5/lilypond/part_I.ly new file mode 100644 index 0000000..18a5337 --- /dev/null +++ b/resources/string_quartet_1/4e7d35e5/lilypond/part_I.ly @@ -0,0 +1,14 @@ +{ + { ais'1^\markup { \pad-markup #0.2 "+41"} ~ } + \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/string_quartet_1/4e7d35e5/lilypond/part_II.ly b/resources/string_quartet_1/4e7d35e5/lilypond/part_II.ly new file mode 100644 index 0000000..0674392 --- /dev/null +++ b/resources/string_quartet_1/4e7d35e5/lilypond/part_II.ly @@ -0,0 +1,14 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4e7d35e5/lilypond/part_III.ly b/resources/string_quartet_1/4e7d35e5/lilypond/part_III.ly new file mode 100644 index 0000000..7778860 --- /dev/null +++ b/resources/string_quartet_1/4e7d35e5/lilypond/part_III.ly @@ -0,0 +1,14 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/4e7d35e5/lilypond/part_IV.ly b/resources/string_quartet_1/4e7d35e5/lilypond/part_IV.ly new file mode 100644 index 0000000..0f106c9 --- /dev/null +++ b/resources/string_quartet_1/4e7d35e5/lilypond/part_IV.ly @@ -0,0 +1,14 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/52c9a980/52c9a980_code.scd b/resources/string_quartet_1/52c9a980/52c9a980_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/52c9a980/52c9a980_mus_model.json b/resources/string_quartet_1/52c9a980/52c9a980_mus_model.json new file mode 100644 index 0000000..f3cde46 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/52c9a980/lilypond/part_I.ly b/resources/string_quartet_1/52c9a980/lilypond/part_I.ly new file mode 100644 index 0000000..ba446df --- /dev/null +++ b/resources/string_quartet_1/52c9a980/lilypond/part_I.ly @@ -0,0 +1,18 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/52c9a980/lilypond/part_II.ly b/resources/string_quartet_1/52c9a980/lilypond/part_II.ly new file mode 100644 index 0000000..75eeb2b --- /dev/null +++ b/resources/string_quartet_1/52c9a980/lilypond/part_II.ly @@ -0,0 +1,18 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/52c9a980/lilypond/part_III.ly b/resources/string_quartet_1/52c9a980/lilypond/part_III.ly new file mode 100644 index 0000000..9e681f0 --- /dev/null +++ b/resources/string_quartet_1/52c9a980/lilypond/part_III.ly @@ -0,0 +1,18 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/52c9a980/lilypond/part_IV.ly b/resources/string_quartet_1/52c9a980/lilypond/part_IV.ly new file mode 100644 index 0000000..bb5c589 --- /dev/null +++ b/resources/string_quartet_1/52c9a980/lilypond/part_IV.ly @@ -0,0 +1,18 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/5e54c468/5e54c468_code.scd b/resources/string_quartet_1/5e54c468/5e54c468_code.scd new file mode 100644 index 0000000..3eb3cbd --- /dev/null +++ b/resources/string_quartet_1/5e54c468/5e54c468_code.scd @@ -0,0 +1,1058 @@ +( +// 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, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark +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; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file; + file = File(path, "w"); + 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"); +//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((dir +/+ ".." +/+ "resources" +/+ 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((dir +/+ ".." +/+ "resources" +/+ curUID).standardizePath); + File.copy(exPath, (dir +/+ ".." +/+ "resources" +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (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; + }); + 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((dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \".." +/+ ".." +/+ "resources" +/+ 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); + +) + +~transcribe.value(~seq, dir); + +( +//synthdefs +~stringModelBusArray = 4.collect({Bus.audio(s, 1)}); +~sineBusArray = 4.collect({Bus.audio(s, 1)}); +~bassBusArray = 1.collect({Bus.audio(s, 1)}); +~hdustBusArray = 1.collect({Bus.audio(s, 1)}); +~samplerBusArray = 2.collect({Bus.audio(s, 1)}); +~sBuf = Buffer.alloc(s, 10, 2); +SynthDef(\string_model, {arg freq, gate = 1, sustain, amp, dur, attack, release = 1, busIndex = 0; + var trig, exc, sig1, sig2, noHarms; + noHarms = rrand(20, 40); + exc = Saw.ar(freq, TRand.ar(0.5, 1, Impulse.ar(freq))) * 0.001 + Dust.ar(10000, 0.01); + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(1, 2)}) ], exc) * 0.8).softclip; + //sig1 = HPF.ar(sig1, 300); + Out.ar(Select.kr(busIndex, ~stringModelBusArray), sig1 * amp * EnvGen.kr(Env.adsr(attack, 0.3, 0.9, release, 0.9, -3), gate, doneAction: 2)); + //Out.ar([0, 1], sig1 * EnvGen.kr(Env.asr(dur, 0.3, 1), gate, doneAction: 2)); +}).add; + +SynthDef(\sine, {arg freq, gate = 1, sustain, amp, dur, busIndex = 0; + var sig; + sig = SinOsc.ar(freq); + Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.asr(0.3, 0.4, 0.3), gate, timeScale: dur, doneAction: 2)); + //Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.sine(dur), gate, doneAction: 2)); +}).add; + +SynthDef(\mixer, {arg freq, gate = 1, sustain, amp, dur, out; + var nameSpaces, sigs; + + sigs = [~stringModelBusArray, ~sineBusArray/*, ~bassBusArray, ~hdustBusArray, ~samplerBusArray*/].collect({arg busArray, i; + var nameSpace, sig; + nameSpace = ['string', 'sine', 'bass', 'hdust', 'sampler'][i]; + sig = busArray.collect({arg bus, c; In.ar(bus, 1) * NamedControl.kr(\ ++ nameSpace ++ '_volume_' ++ c, 1, 0.1)}); + sig = sig.collect({arg channel, c; Pan2.ar(channel, NamedControl.kr(\ ++ nameSpace ++ '_pan_' ++ c, i / (busArray.size - 1), 0.1) * 2 - 1)}); + sig = sig.collect({arg channel, c; channel * NamedControl.kr(\ ++ nameSpace ++ '_mute_' ++ c, 1, 0.1)}); + sig = Mix.ar(sig) * pow(NamedControl.kr(\ ++ nameSpace ++ '_volume_master', 1, 0.1), 2); + }); + + sigs = Mix.ar(sigs / 4); + Out.ar(0, sigs) +}).add; + +SynthDef(\bass, { + var switches, drone; + switches = {|i| Dust.kr(0.1)} ! 9; + drone = {|i| var harm = pow(2, 2 - (i / 3).trunc), amp = (1 / pow(harm, 2)); + SinOsc.ar(60 * harm + TRand.kr(-3, 3, switches[i]), 0, amp)} ! 9; + Out.ar(~bassBusArray[0], Mix.new(drone) * 0.2); +}).add; + +SynthDef(\sampler, { + Out.ar(~samplerBusArray, PlayBuf.ar(2, ~sBuf, BufRateScale.kr(~sBuf), doneAction: 2)) +}).add; + +// main routine +SynthDef(\hdust, { + arg gate = 0; + var hierarchical_dust, low_sine, high_sine, brown_noise, white_noise; + // this triggers the combinations of sources + // it is similar to the Supercollider UGen called dust but with a hierarchical structure + hierarchical_dust = ( + TIRand.kr(0, 1, Impulse.kr(100)) * + TIRand.kr(0, 1, Impulse.kr(10)) * + TIRand.kr(0, 1, Impulse.kr(1)) * + TIRand.kr(0, 1, Impulse.kr(0.1)) + ); + // adjust the multiplier at the end of each line for adjusting levels + // note with each trigger, each source has a 1 in 3 chance of sounding + low_sine = SinOsc.ar(76.midicps / 16) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.1; + high_sine = SinOsc.ar(76.midicps * 8) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.01; + brown_noise = BrownNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.025; + white_noise = WhiteNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.02; + Out.ar(~hdustBusArray[0], + ((low_sine + high_sine + brown_noise + white_noise) ) + ); +}).add; + +) + +( +var bass, hdust, sampler, mixer; +/* +bass = Synth.tail(~group, \bass); +hdust = Synth.tail(~group, \hdust); +sampler = Synth.head(~group, \sampler); +*/ +mixer = Synth.tail(~group, \mixer); + +OSCdef(\mixer, {arg msg, time, addr, port; + mixer.set((msg[1] ++ '_' ++ msg[2] ++ '_' ++ msg[3]), msg[4]) +}, \mixer); + +/* +OSCdef(\sampler, {arg msg, time, addr, port; + msg.postln; + sampler.free; + ~sBuf.free; + ~sBuf = Buffer.read(s, msg[1].asString.postln, action: {sampler = Synth.head(~group, \sampler)}); +}, \sampler); +*/ +) + +/* old something +( +SynthDef(\test, {arg freq, gate = 1, sustain, amp, dur; + var trig, exc, sig1, sig2, noHarms, freqFinal, start, end; + noHarms = 30; + freq = WhiteNoise.ar * 3 + freq; + freqFinal = Duty.ar((1/freq), 0, freq); + trig = Changed.ar(freqFinal); + start = Demand.ar(trig, 0, Dwhite(-1, -0.75)); + end = Demand.ar(trig, 0, Dwhite(0.75, 1)); + exc = Phasor.ar(trig, (end - start) * freqFinal / SampleRate.ir, start, end, 0) * 0.001 + Dust.ar(10000, 0.01); + + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(2, 3)}) ], exc) * 0.5).softclip; + sig1 = HPF.ar(sig1, 300); + Out.ar([0, 1], sig1 * EnvGen.kr(Env.adsr(0.3, 0.3, 0.9, 0.5, 0.9), gate, doneAction: 2)); +}).add; +) +*/ + diff --git a/resources/string_quartet_1/5e54c468/5e54c468_mus_model.json b/resources/string_quartet_1/5e54c468/5e54c468_mus_model.json new file mode 100644 index 0000000..f3f6493 --- /dev/null +++ b/resources/string_quartet_1/5e54c468/5e54c468_mus_model.json @@ -0,0 +1,70 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ 1, -1, 0, -1, 1, -1 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, -1 ], [ "Rest" ] ], 0.625 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, -1 ], [ "Rest" ] ], 2.5 ], + [ [ [ 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, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, -1 ], [ 2, -1, 1, -1, 1, -1 ] ], 3.875 ] + ], + [ + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ 2, -1, 1, -1, 1, -1 ] ], 2 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -1, 0, -1, 1, -1 ], [ "Rest" ], [ 2, -1, 1, -1, 1, -1 ] ], 6.5 ] + ], + [ + [ [ [ "Rest" ], [ 2, -1, 0, -1, 1, -1 ], [ "Rest" ], [ 2, -1, 1, -1, 1, -1 ] ], 0 ], + [ [ [ "Rest" ], [ 2, -1, 0, -1, 1, -1 ], [ "Rest" ], [ "Rest" ] ], 0.75 ], + [ [ [ "Rest" ], [ 2, -1, 0, -1, 1, -1 ], [ 2, -1, 0, 0, 1, -1 ], [ "Rest" ] ], 1.125 ], + [ [ [ "Rest" ], [ 2, -1, 0, -1, 1, -1 ], [ 2, -1, 0, -1, 1, 0 ], [ "Rest" ] ], 4.5 ] + ], + [ + [ [ [ "Rest" ], [ 2, -1, 0, -1, 1, -1 ], [ 2, -1, 0, -1, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 0.875 ], + [ [ [ "Rest" ], [ 2, -1, 0, -2, 1, 0 ], [ 2, -1, 0, -1, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 1.25 ], + [ [ [ "Rest" ], [ 1, 0, 1, -1, 1, -1 ], [ 2, -1, 0, -1, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 3.75 ] + ], + [ + [ [ [ "Rest" ], [ 1, -1, 1, -1, 1, 0 ], [ 2, -1, 0, -1, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 1 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, 0, -1, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 3.75 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, 0, -1, 1, 0 ], [ "Rest" ] ], 2 ], + [ [ [ "Rest" ], [ "Rest" ], [ 2, -1, 0, -1, 1, 0 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 4.5 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -1, 0, -1, 1, -1 ], [ 2, -1, 0, -1, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -1, 0, -2, 1, 0 ], [ 2, -1, 0, -1, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 1, 0, 1, -1, 1, -1 ], [ 2, -1, 0, -1, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 2, -1, 0, -1, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], + [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, 0, -1, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ] +], +"cur_uid": "5e54c468", +"ref_uid": "6fb60ab6", +"order_seed": 499586, +"dur_seed": 134526, +"motifs_seed": 636998, +"entrances_probs_vals": [ 0.34, 0.99, 3.1746031746032, 0.5, 2, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0.34, 1.5873015873016, 4.7222222222222, 0.5, 2.1153846153846, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0.34, 0.32, 1.9444444444444, 0.5, 2.12, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -3600, -312.07430340557 ], [ -1872, 1378.3281733746 ], [ -144.89164086687, 1582.6625386997 ], [ -182.04334365325, 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, 0 ], [ 3, 3 ], [ ] ], + [ [ 3, 0 ], [ 1 ], [ 2 ] ], + [ [ 1 ], [ 2, 2 ], [ 0, 3 ] ], + [ [ 3, 2 ], [ 1, 1 ], [ 0 ] ], + [ [ 3, 2 ], [ 1, 1 ], [ 0 ] ] +], +"sus_weights": [ 0.75, 0.25, 0.25 ], +"order_size": [ 3, 6 ], +"passages_size": [ 0, 3 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_1/5e54c468/lilypond/part_I.ly b/resources/string_quartet_1/5e54c468/lilypond/part_I.ly new file mode 100644 index 0000000..98ddfc8 --- /dev/null +++ b/resources/string_quartet_1/5e54c468/lilypond/part_I.ly @@ -0,0 +1,44 @@ +{ + { r1 } + \bar "|" + { r2 r16[ a'8.^\markup { \pad-markup #0.2 "-6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ a'4 ~ } + \bar "|" + { a'16[ gis'8.^\markup { \pad-markup #0.2 "+26"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}] ~ gis'2. ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'4 r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 r8.[ gis'16^\markup { \pad-markup #0.2 "+26"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] ~ gis'2 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'2. r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/5e54c468/lilypond/part_II.ly b/resources/string_quartet_1/5e54c468/lilypond/part_II.ly new file mode 100644 index 0000000..6b51d38 --- /dev/null +++ b/resources/string_quartet_1/5e54c468/lilypond/part_II.ly @@ -0,0 +1,44 @@ +{ + { e1^\markup { \pad-markup #0.2 "+40"} ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 r8[ d''8^\markup { \pad-markup #0.2 "+9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}] ~ d''4 ~ } + \bar "|" + { d''8.[ cis''16^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ cis''2. ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''2. r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/5e54c468/lilypond/part_III.ly b/resources/string_quartet_1/5e54c468/lilypond/part_III.ly new file mode 100644 index 0000000..2632120 --- /dev/null +++ b/resources/string_quartet_1/5e54c468/lilypond/part_III.ly @@ -0,0 +1,44 @@ +{ + { fis'1^\markup { \pad-markup #0.2 "-21"} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 } + \bar "|" + { e'1^\markup { \pad-markup #0.2 "+40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }} ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'2. ~ e'8[ dis'8^\markup { \pad-markup #0.2 "+12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }}] ~ } + \bar "|" + { dis'2 dis'2^\markup { \pad-markup #0.2 "+28"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }} ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'4 ~ dis'8[ f'8^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}] ~ f'4 ~ f'8[ fis'8^\markup { \pad-markup #0.2 "-21"}] ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'2. r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/5e54c468/lilypond/part_IV.ly b/resources/string_quartet_1/5e54c468/lilypond/part_IV.ly new file mode 100644 index 0000000..1f3a4d8 --- /dev/null +++ b/resources/string_quartet_1/5e54c468/lilypond/part_IV.ly @@ -0,0 +1,44 @@ +{ + { r4 r16[ cis'8.^\markup { \pad-markup #0.2 "-19"}] ~ cis'2 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'4 r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \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/string_quartet_1/61ce9067/61ce9067_code.scd b/resources/string_quartet_1/61ce9067/61ce9067_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_1/61ce9067/61ce9067_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/string_quartet_1/61ce9067/61ce9067_mus_model.json b/resources/string_quartet_1/61ce9067/61ce9067_mus_model.json new file mode 100644 index 0000000..bdc8f6f --- /dev/null +++ b/resources/string_quartet_1/61ce9067/61ce9067_mus_model.json @@ -0,0 +1,55 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, -1, 1, -2, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0.125 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 2, -1, 1, -3, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 2, -1, 1, -3, 1, 0 ], [ 3, -2, 1, -2, 1, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 2, -1, 1, -3, 1, 0 ], [ 3, -2, 1, -2, 1, 0 ], [ 3, -1, 1, -3, 1, 0 ] ], 0.375 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 3, -2, 1, -2, 1, 0 ], [ 3, -1, 1, -3, 1, 0 ] ], 1 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 3, -1, 1, -3, 1, 0 ] ], 0.5 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 2, -1, 1, -2, 1, 0 ] ], 0.75 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 2, -2, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 2, -1, 1, -2, 1, 0 ] ], 1.125 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 2, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 1, -2, 1, 0 ] ], 0.75 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 2, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 2, -2, 1, 0 ] ], 0.25 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 2, -2, 1, 0 ] ], 0.875 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ 2, -1, 2, -2, 1, 0 ] ], 0.875 ], + [ [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ 2, -1, 1, -2, 1, 0 ] ], 0.375 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ 2, -1, 1, -2, 1, 0 ] ], 0.375 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ "Rest" ] ], 0.375 ], + [ [ [ "Rest" ], [ 1, -1, 1, -2, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0.125 ] + ] + ] +], +"last_changes": +[ + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 2, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 1, -2, 1, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 2, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 2, -2, 1, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 2, 0 ], [ 2, -1, 2, -2, 1, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ 2, -1, 2, -2, 1, 0 ] ], + [ [ 0, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 0 ], [ 1, -1, 1, -2, 1, 1 ], [ 2, -1, 1, -2, 1, 0 ] ] +], +"cur_uid": "61ce9067", +"ref_uid": "4200a90d", +"order_seed": 278192, +"dur_seed": 660041, +"motifs_seed": 150685, +"entrances_probs_vals": [ 0, 0, 0, 0.19, 0.93406593406593, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0.2, 2.3015873015873, 0.08, 1.2912087912088, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0.16, 1.0714285714286, 0.16, 1.510989010989, 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.08641975308642, 0.9375, 0.50617283950617, 0.89772727272727, 0.69135802469136, 0, 1, 0 ], +"passages_weights": [ 0.27, 0, 0.6, 0, 1 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3 ], [ ] ] +], +"sus_weights": [ 0.62, 0.25, 0 ], +"order_size": [ 7, 10 ], +"passages_size": [ 0, 6 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_1/61ce9067/lilypond/part_I.ly b/resources/string_quartet_1/61ce9067/lilypond/part_I.ly new file mode 100644 index 0000000..36d5c00 --- /dev/null +++ b/resources/string_quartet_1/61ce9067/lilypond/part_I.ly @@ -0,0 +1,10 @@ +{ + { r2. r16[ a'8.^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ } + \bar "|" + { a'2. g'4^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'16[ b'8.^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ b'2.} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/61ce9067/lilypond/part_II.ly b/resources/string_quartet_1/61ce9067/lilypond/part_II.ly new file mode 100644 index 0000000..172d34d --- /dev/null +++ b/resources/string_quartet_1/61ce9067/lilypond/part_II.ly @@ -0,0 +1,10 @@ +{ + { r4 r8.[ c''16^\markup { \pad-markup #0.2 "-4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ c''2 ~ } + \bar "|" + { c''2 f'2^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} ~ } + \bar "|" + { f'2 ~ f'8.[ c'16^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ c'4 ~ } + \bar "|" + { c'2 ~ c'8[ dis'8^\markup { \pad-markup #0.2 "+39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}] ~ dis'4} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/61ce9067/lilypond/part_III.ly b/resources/string_quartet_1/61ce9067/lilypond/part_III.ly new file mode 100644 index 0000000..80195eb --- /dev/null +++ b/resources/string_quartet_1/61ce9067/lilypond/part_III.ly @@ -0,0 +1,10 @@ +{ + { r16[ a8.^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}] ~ a2. } + \bar "|" + { g1^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g8[ b8^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ b2. ~ } + \bar "|" + { b8.[ g16^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ g2.} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/61ce9067/lilypond/part_IV.ly b/resources/string_quartet_1/61ce9067/lilypond/part_IV.ly new file mode 100644 index 0000000..ce0c70d --- /dev/null +++ b/resources/string_quartet_1/61ce9067/lilypond/part_IV.ly @@ -0,0 +1,10 @@ +{ + { g,1^\markup { \pad-markup #0.2 "-2"} ~ } + \bar "|" + { g,1 ~ } + \bar "|" + { g,1 ~ } + \bar "|" + { g,1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/62820081/62820081_code.scd b/resources/string_quartet_1/62820081/62820081_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/62820081/62820081_mus_model.json b/resources/string_quartet_1/62820081/62820081_mus_model.json new file mode 100644 index 0000000..ab5d8c3 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/631e2af1/631e2af1_code.scd b/resources/string_quartet_1/631e2af1/631e2af1_code.scd new file mode 100644 index 0000000..8e9fe42 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/631e2af1/631e2af1_mus_model.json b/resources/string_quartet_1/631e2af1/631e2af1_mus_model.json new file mode 100644 index 0000000..78ae751 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/66f6a618/66f6a618_code.scd b/resources/string_quartet_1/66f6a618/66f6a618_code.scd new file mode 100644 index 0000000..3eb3cbd --- /dev/null +++ b/resources/string_quartet_1/66f6a618/66f6a618_code.scd @@ -0,0 +1,1058 @@ +( +// 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, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark +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; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file; + file = File(path, "w"); + 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"); +//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((dir +/+ ".." +/+ "resources" +/+ 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((dir +/+ ".." +/+ "resources" +/+ curUID).standardizePath); + File.copy(exPath, (dir +/+ ".." +/+ "resources" +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (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; + }); + 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((dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \".." +/+ ".." +/+ "resources" +/+ 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); + +) + +~transcribe.value(~seq, dir); + +( +//synthdefs +~stringModelBusArray = 4.collect({Bus.audio(s, 1)}); +~sineBusArray = 4.collect({Bus.audio(s, 1)}); +~bassBusArray = 1.collect({Bus.audio(s, 1)}); +~hdustBusArray = 1.collect({Bus.audio(s, 1)}); +~samplerBusArray = 2.collect({Bus.audio(s, 1)}); +~sBuf = Buffer.alloc(s, 10, 2); +SynthDef(\string_model, {arg freq, gate = 1, sustain, amp, dur, attack, release = 1, busIndex = 0; + var trig, exc, sig1, sig2, noHarms; + noHarms = rrand(20, 40); + exc = Saw.ar(freq, TRand.ar(0.5, 1, Impulse.ar(freq))) * 0.001 + Dust.ar(10000, 0.01); + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(1, 2)}) ], exc) * 0.8).softclip; + //sig1 = HPF.ar(sig1, 300); + Out.ar(Select.kr(busIndex, ~stringModelBusArray), sig1 * amp * EnvGen.kr(Env.adsr(attack, 0.3, 0.9, release, 0.9, -3), gate, doneAction: 2)); + //Out.ar([0, 1], sig1 * EnvGen.kr(Env.asr(dur, 0.3, 1), gate, doneAction: 2)); +}).add; + +SynthDef(\sine, {arg freq, gate = 1, sustain, amp, dur, busIndex = 0; + var sig; + sig = SinOsc.ar(freq); + Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.asr(0.3, 0.4, 0.3), gate, timeScale: dur, doneAction: 2)); + //Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.sine(dur), gate, doneAction: 2)); +}).add; + +SynthDef(\mixer, {arg freq, gate = 1, sustain, amp, dur, out; + var nameSpaces, sigs; + + sigs = [~stringModelBusArray, ~sineBusArray/*, ~bassBusArray, ~hdustBusArray, ~samplerBusArray*/].collect({arg busArray, i; + var nameSpace, sig; + nameSpace = ['string', 'sine', 'bass', 'hdust', 'sampler'][i]; + sig = busArray.collect({arg bus, c; In.ar(bus, 1) * NamedControl.kr(\ ++ nameSpace ++ '_volume_' ++ c, 1, 0.1)}); + sig = sig.collect({arg channel, c; Pan2.ar(channel, NamedControl.kr(\ ++ nameSpace ++ '_pan_' ++ c, i / (busArray.size - 1), 0.1) * 2 - 1)}); + sig = sig.collect({arg channel, c; channel * NamedControl.kr(\ ++ nameSpace ++ '_mute_' ++ c, 1, 0.1)}); + sig = Mix.ar(sig) * pow(NamedControl.kr(\ ++ nameSpace ++ '_volume_master', 1, 0.1), 2); + }); + + sigs = Mix.ar(sigs / 4); + Out.ar(0, sigs) +}).add; + +SynthDef(\bass, { + var switches, drone; + switches = {|i| Dust.kr(0.1)} ! 9; + drone = {|i| var harm = pow(2, 2 - (i / 3).trunc), amp = (1 / pow(harm, 2)); + SinOsc.ar(60 * harm + TRand.kr(-3, 3, switches[i]), 0, amp)} ! 9; + Out.ar(~bassBusArray[0], Mix.new(drone) * 0.2); +}).add; + +SynthDef(\sampler, { + Out.ar(~samplerBusArray, PlayBuf.ar(2, ~sBuf, BufRateScale.kr(~sBuf), doneAction: 2)) +}).add; + +// main routine +SynthDef(\hdust, { + arg gate = 0; + var hierarchical_dust, low_sine, high_sine, brown_noise, white_noise; + // this triggers the combinations of sources + // it is similar to the Supercollider UGen called dust but with a hierarchical structure + hierarchical_dust = ( + TIRand.kr(0, 1, Impulse.kr(100)) * + TIRand.kr(0, 1, Impulse.kr(10)) * + TIRand.kr(0, 1, Impulse.kr(1)) * + TIRand.kr(0, 1, Impulse.kr(0.1)) + ); + // adjust the multiplier at the end of each line for adjusting levels + // note with each trigger, each source has a 1 in 3 chance of sounding + low_sine = SinOsc.ar(76.midicps / 16) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.1; + high_sine = SinOsc.ar(76.midicps * 8) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.01; + brown_noise = BrownNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.025; + white_noise = WhiteNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.02; + Out.ar(~hdustBusArray[0], + ((low_sine + high_sine + brown_noise + white_noise) ) + ); +}).add; + +) + +( +var bass, hdust, sampler, mixer; +/* +bass = Synth.tail(~group, \bass); +hdust = Synth.tail(~group, \hdust); +sampler = Synth.head(~group, \sampler); +*/ +mixer = Synth.tail(~group, \mixer); + +OSCdef(\mixer, {arg msg, time, addr, port; + mixer.set((msg[1] ++ '_' ++ msg[2] ++ '_' ++ msg[3]), msg[4]) +}, \mixer); + +/* +OSCdef(\sampler, {arg msg, time, addr, port; + msg.postln; + sampler.free; + ~sBuf.free; + ~sBuf = Buffer.read(s, msg[1].asString.postln, action: {sampler = Synth.head(~group, \sampler)}); +}, \sampler); +*/ +) + +/* old something +( +SynthDef(\test, {arg freq, gate = 1, sustain, amp, dur; + var trig, exc, sig1, sig2, noHarms, freqFinal, start, end; + noHarms = 30; + freq = WhiteNoise.ar * 3 + freq; + freqFinal = Duty.ar((1/freq), 0, freq); + trig = Changed.ar(freqFinal); + start = Demand.ar(trig, 0, Dwhite(-1, -0.75)); + end = Demand.ar(trig, 0, Dwhite(0.75, 1)); + exc = Phasor.ar(trig, (end - start) * freqFinal / SampleRate.ir, start, end, 0) * 0.001 + Dust.ar(10000, 0.01); + + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(2, 3)}) ], exc) * 0.5).softclip; + sig1 = HPF.ar(sig1, 300); + Out.ar([0, 1], sig1 * EnvGen.kr(Env.adsr(0.3, 0.3, 0.9, 0.5, 0.9), gate, doneAction: 2)); +}).add; +) +*/ + diff --git a/resources/string_quartet_1/66f6a618/66f6a618_mus_model.json b/resources/string_quartet_1/66f6a618/66f6a618_mus_model.json new file mode 100644 index 0000000..fe45ed4 --- /dev/null +++ b/resources/string_quartet_1/66f6a618/66f6a618_mus_model.json @@ -0,0 +1,91 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 1, -1, 0, 0, -1, 0 ] ], 0.75 ], + [ [ [ "Rest" ], [ 1, -1, 0, 0, -1, 0 ], [ "Rest" ], [ 1, -1, 0, 0, -1, 0 ] ], 0.5 ], + [ [ [ "Rest" ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 1.375 ], + [ [ [ 0, -1, 0, 0, -1, 1 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 1.5 ], + [ [ [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.875 ], + [ [ [ 1, -2, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.75 ], + [ [ [ 0, -1, 1, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.75 ], + [ [ [ 1, -1, 0, -1, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 8.75 ] + ], + [ + [ [ [ 1, -1, 0, -1, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, -1, 0 ] ], 0.625 ], + [ [ [ 1, -1, 0, -1, -1, 0 ], [ 0, -1, 0, 1, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, -1, 0 ] ], 0.625 ], + [ [ [ 0, -1, 0, 0, -1, 0 ], [ 0, -1, 0, 1, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, -1, 0 ] ], 0.75 ], + [ [ [ 1, -1, 0, 0, -1, -1 ], [ 0, -1, 0, 1, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, -1, 0 ] ], 0.875 ], + [ [ [ 1, -1, 0, 0, -1, -1 ], [ 0, -1, 0, 1, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, 0, -1, -1 ] ], 1.25 ], + [ [ [ 1, -1, 0, 0, -1, -1 ], [ 1, -1, -1, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, 0, -1, -1 ] ], 1.75 ], + [ [ [ 1, -1, 0, 0, -1, -1 ], [ 1, -1, -1, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 1, -1, 0, 0, -1, -1 ], [ 0, -1, 0, 0, -1, 1 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, -1, 1, 0, -1, 0 ], [ 0, -1, 0, 0, -1, 1 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1.875 ], + [ [ [ 0, -1, 1, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 3 ] + ], + [ + [ [ [ 0, -1, -1, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, -1, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, -1, 0, -1, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1.625 ], + [ [ [ -1, -1, 0, 0, 0, 1 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 0.875 ], + [ [ [ 0, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1.25 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 4.625 ] + ], + [ + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, -1, 0, -1, 0 ] ], 1.75 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 2, 0, 0, -1, -1, 0 ] ], 1.875 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, 0, -1, 0, 0, 0 ] ], 0.625 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ] ], 0.625 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 7.25 ] + ], + [ + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 1.875 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 1, -1, -1, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 1.625 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, -2, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 1.625 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 1, -2, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 1 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, -1 ], [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 3.75 ] + ], + [ + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, -1 ], [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 0 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, -1 ], [ "Rest" ], [ 0, 0, 0, 0, 1, 0 ] ], 1.25 ], + [ [ [ "Rest" ], [ -2, 0, 0, 0, 1, 1 ], [ "Rest" ], [ 0, 0, 0, 0, 1, 0 ] ], 1.875 ], + [ [ [ "Rest" ], [ -2, 1, 0, 0, 1, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 1, 0 ] ], 1.25 ], + [ [ [ "Rest" ], [ -1, 0, -1, 0, 1, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 1, 0 ] ], 2 ], + [ [ [ "Rest" ], [ -2, 0, 0, 1, 1, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 1, 0 ] ], 2.625 ], + [ [ [ "Rest" ], [ -2, 0, 0, 1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0.25 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0.375 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ 0, 0, -1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ 0, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ] +], +"cur_uid": "66f6a618", +"ref_uid": "nil", +"order_seed": 209164, +"dur_seed": 417909, +"motifs_seed": 885208, +"entrances_probs_vals": [ 0.34, 0, 10, 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, 0, 10, 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, 10, 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 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 2 ], [ 0, 0, 0, 0 ], [ 1, 3 ] ] +], +"sus_weights": [ 0.75, 0.75, 0.75 ], +"order_size": [ 1, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_1/66f6a618/lilypond/part_I.ly b/resources/string_quartet_1/66f6a618/lilypond/part_I.ly new file mode 100644 index 0000000..d05522a --- /dev/null +++ b/resources/string_quartet_1/66f6a618/lilypond/part_I.ly @@ -0,0 +1,70 @@ +{ + { b1^\markup { \pad-markup #0.2 "+47"} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2 ~ b8[ d'8^\markup { \pad-markup #0.2 "-22"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ d'4 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'16[ dis'8.^\markup { \pad-markup #0.2 "+6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ dis'2. ~ } + \bar "|" + { dis'2 ~ dis'16[ f'8.^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↑" }}] ~ f'4 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'8.[ gis'16^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }}] ~ gis'2. ~ } + \bar "|" + { gis'16[ a'8.^\markup { \pad-markup #0.2 "-20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }}] ~ a'2. } + \bar "|" + { gis'4^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ gis'16[ fis'8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ fis'8[ fis'8^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ fis'4 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'2 ~ fis'8.[ r16] r4} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/66f6a618/lilypond/part_II.ly b/resources/string_quartet_1/66f6a618/lilypond/part_II.ly new file mode 100644 index 0000000..602b0d6 --- /dev/null +++ b/resources/string_quartet_1/66f6a618/lilypond/part_II.ly @@ -0,0 +1,70 @@ +{ + { r2 r8[ b8^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ b4 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b8.[ r16] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/66f6a618/lilypond/part_III.ly b/resources/string_quartet_1/66f6a618/lilypond/part_III.ly new file mode 100644 index 0000000..6d06a75 --- /dev/null +++ b/resources/string_quartet_1/66f6a618/lilypond/part_III.ly @@ -0,0 +1,70 @@ +{ + { r4 r8[ b8^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ~ b2 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2. ~ b8.[ a16^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a2 ~ a8.[ gis16^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }}] ~ gis4 ~ } + \bar "|" + { gis2 ~ gis16[ gis8.^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }}] ~ gis4 ~ } + \bar "|" + { gis2 fis2^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }} ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis4 f2.^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { f8.[ gis16^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }}] ~ gis2. } + \bar "|" + { fis2.^\markup { \pad-markup #0.2 "-5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↓" }} ~ fis16[ e8.^\markup { \pad-markup #0.2 "+45"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { e4 ~ e16[ e8.^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ e2 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e2. ~ e16[ d8.^\markup { \pad-markup #0.2 "-8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ~ } + \bar "|" + { d2. cis4^\markup { \pad-markup #0.2 "-47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }} ~ } + \bar "|" + { cis4 ~ cis8[ d8^\markup { \pad-markup #0.2 "-35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}] ~ d2 ~ } + \bar "|" + { d4 ~ d8[ dis8^\markup { \pad-markup #0.2 "+20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}] ~ dis2 ~ } + \bar "|" + { dis2. ~ dis16[ r8.]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/66f6a618/lilypond/part_IV.ly b/resources/string_quartet_1/66f6a618/lilypond/part_IV.ly new file mode 100644 index 0000000..bc81ade --- /dev/null +++ b/resources/string_quartet_1/66f6a618/lilypond/part_IV.ly @@ -0,0 +1,70 @@ +{ + { r1 } + \bar "|" + { r4 r16[ gis8.^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ gis2 ~ } + \bar "|" + { gis16[ fis8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}] ~ fis4 e4^\markup { \pad-markup #0.2 "+45"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ e8[ dis8^\markup { \pad-markup #0.2 "+33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { dis4 d2.^\markup { \pad-markup #0.2 "-22"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }} ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d4 b,4^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ b,8[ dis8^\markup { \pad-markup #0.2 "+6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↓" }}] ~ dis4 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis2 ~ dis16[ dis8.^\markup { \pad-markup #0.2 "+33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}] ~ dis4 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 } + \bar "|" + { cis2^\markup { \pad-markup #0.2 "+12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }} dis2^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }} } + \bar "|" + { d2.^\markup { \pad-markup #0.2 "-22"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }} ~ d16[ cis8.^\markup { \pad-markup #0.2 "+39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ~ } + \bar "|" + { cis4 b,2^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ b,8[ c8^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↑" }}] ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c8.[ r16] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/6d635e88/6d635e88_code.scd b/resources/string_quartet_1/6d635e88/6d635e88_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/6d635e88/6d635e88_mus_model.json b/resources/string_quartet_1/6d635e88/6d635e88_mus_model.json new file mode 100644 index 0000000..228456c --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/6d635e88/lilypond/part_I.ly b/resources/string_quartet_1/6d635e88/lilypond/part_I.ly new file mode 100644 index 0000000..ea663b7 --- /dev/null +++ b/resources/string_quartet_1/6d635e88/lilypond/part_I.ly @@ -0,0 +1,70 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/6d635e88/lilypond/part_II.ly b/resources/string_quartet_1/6d635e88/lilypond/part_II.ly new file mode 100644 index 0000000..8850de9 --- /dev/null +++ b/resources/string_quartet_1/6d635e88/lilypond/part_II.ly @@ -0,0 +1,70 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/6d635e88/lilypond/part_III.ly b/resources/string_quartet_1/6d635e88/lilypond/part_III.ly new file mode 100644 index 0000000..710041c --- /dev/null +++ b/resources/string_quartet_1/6d635e88/lilypond/part_III.ly @@ -0,0 +1,70 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/6d635e88/lilypond/part_IV.ly b/resources/string_quartet_1/6d635e88/lilypond/part_IV.ly new file mode 100644 index 0000000..8163bc3 --- /dev/null +++ b/resources/string_quartet_1/6d635e88/lilypond/part_IV.ly @@ -0,0 +1,70 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/6ed95c4c/6ed95c4c_code.scd b/resources/string_quartet_1/6ed95c4c/6ed95c4c_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/6ed95c4c/6ed95c4c_mus_model.json b/resources/string_quartet_1/6ed95c4c/6ed95c4c_mus_model.json new file mode 100644 index 0000000..c03359a --- /dev/null +++ b/resources/string_quartet_1/6ed95c4c/6ed95c4c_mus_model.json @@ -0,0 +1,66 @@ +{ +"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.625 ], + [ [ [ 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" ] ], 0.25 ] + ] + ] +], +"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/string_quartet_1/6ed95c4c/lilypond/part_I.ly b/resources/string_quartet_1/6ed95c4c/lilypond/part_I.ly new file mode 100644 index 0000000..4ccdb1e --- /dev/null +++ b/resources/string_quartet_1/6ed95c4c/lilypond/part_I.ly @@ -0,0 +1,42 @@ +{ + { 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'4 ~ ais'8.[ r16] r2 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/6ed95c4c/lilypond/part_II.ly b/resources/string_quartet_1/6ed95c4c/lilypond/part_II.ly new file mode 100644 index 0000000..dba1611 --- /dev/null +++ b/resources/string_quartet_1/6ed95c4c/lilypond/part_II.ly @@ -0,0 +1,42 @@ +{ + { 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'2. ~ g'8.[ r16] } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/6ed95c4c/lilypond/part_III.ly b/resources/string_quartet_1/6ed95c4c/lilypond/part_III.ly new file mode 100644 index 0000000..ce5e5ba --- /dev/null +++ b/resources/string_quartet_1/6ed95c4c/lilypond/part_III.ly @@ -0,0 +1,42 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/6ed95c4c/lilypond/part_IV.ly b/resources/string_quartet_1/6ed95c4c/lilypond/part_IV.ly new file mode 100644 index 0000000..18babdb --- /dev/null +++ b/resources/string_quartet_1/6ed95c4c/lilypond/part_IV.ly @@ -0,0 +1,42 @@ +{ + { 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 "|" + { c2. ~ c8[ r8]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/6fb60ab6/6fb60ab6_code.scd b/resources/string_quartet_1/6fb60ab6/6fb60ab6_code.scd new file mode 100644 index 0000000..3eb3cbd --- /dev/null +++ b/resources/string_quartet_1/6fb60ab6/6fb60ab6_code.scd @@ -0,0 +1,1058 @@ +( +// 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, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark +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; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file; + file = File(path, "w"); + 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"); +//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((dir +/+ ".." +/+ "resources" +/+ 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((dir +/+ ".." +/+ "resources" +/+ curUID).standardizePath); + File.copy(exPath, (dir +/+ ".." +/+ "resources" +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (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; + }); + 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((dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \".." +/+ ".." +/+ "resources" +/+ 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); + +) + +~transcribe.value(~seq, dir); + +( +//synthdefs +~stringModelBusArray = 4.collect({Bus.audio(s, 1)}); +~sineBusArray = 4.collect({Bus.audio(s, 1)}); +~bassBusArray = 1.collect({Bus.audio(s, 1)}); +~hdustBusArray = 1.collect({Bus.audio(s, 1)}); +~samplerBusArray = 2.collect({Bus.audio(s, 1)}); +~sBuf = Buffer.alloc(s, 10, 2); +SynthDef(\string_model, {arg freq, gate = 1, sustain, amp, dur, attack, release = 1, busIndex = 0; + var trig, exc, sig1, sig2, noHarms; + noHarms = rrand(20, 40); + exc = Saw.ar(freq, TRand.ar(0.5, 1, Impulse.ar(freq))) * 0.001 + Dust.ar(10000, 0.01); + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(1, 2)}) ], exc) * 0.8).softclip; + //sig1 = HPF.ar(sig1, 300); + Out.ar(Select.kr(busIndex, ~stringModelBusArray), sig1 * amp * EnvGen.kr(Env.adsr(attack, 0.3, 0.9, release, 0.9, -3), gate, doneAction: 2)); + //Out.ar([0, 1], sig1 * EnvGen.kr(Env.asr(dur, 0.3, 1), gate, doneAction: 2)); +}).add; + +SynthDef(\sine, {arg freq, gate = 1, sustain, amp, dur, busIndex = 0; + var sig; + sig = SinOsc.ar(freq); + Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.asr(0.3, 0.4, 0.3), gate, timeScale: dur, doneAction: 2)); + //Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.sine(dur), gate, doneAction: 2)); +}).add; + +SynthDef(\mixer, {arg freq, gate = 1, sustain, amp, dur, out; + var nameSpaces, sigs; + + sigs = [~stringModelBusArray, ~sineBusArray/*, ~bassBusArray, ~hdustBusArray, ~samplerBusArray*/].collect({arg busArray, i; + var nameSpace, sig; + nameSpace = ['string', 'sine', 'bass', 'hdust', 'sampler'][i]; + sig = busArray.collect({arg bus, c; In.ar(bus, 1) * NamedControl.kr(\ ++ nameSpace ++ '_volume_' ++ c, 1, 0.1)}); + sig = sig.collect({arg channel, c; Pan2.ar(channel, NamedControl.kr(\ ++ nameSpace ++ '_pan_' ++ c, i / (busArray.size - 1), 0.1) * 2 - 1)}); + sig = sig.collect({arg channel, c; channel * NamedControl.kr(\ ++ nameSpace ++ '_mute_' ++ c, 1, 0.1)}); + sig = Mix.ar(sig) * pow(NamedControl.kr(\ ++ nameSpace ++ '_volume_master', 1, 0.1), 2); + }); + + sigs = Mix.ar(sigs / 4); + Out.ar(0, sigs) +}).add; + +SynthDef(\bass, { + var switches, drone; + switches = {|i| Dust.kr(0.1)} ! 9; + drone = {|i| var harm = pow(2, 2 - (i / 3).trunc), amp = (1 / pow(harm, 2)); + SinOsc.ar(60 * harm + TRand.kr(-3, 3, switches[i]), 0, amp)} ! 9; + Out.ar(~bassBusArray[0], Mix.new(drone) * 0.2); +}).add; + +SynthDef(\sampler, { + Out.ar(~samplerBusArray, PlayBuf.ar(2, ~sBuf, BufRateScale.kr(~sBuf), doneAction: 2)) +}).add; + +// main routine +SynthDef(\hdust, { + arg gate = 0; + var hierarchical_dust, low_sine, high_sine, brown_noise, white_noise; + // this triggers the combinations of sources + // it is similar to the Supercollider UGen called dust but with a hierarchical structure + hierarchical_dust = ( + TIRand.kr(0, 1, Impulse.kr(100)) * + TIRand.kr(0, 1, Impulse.kr(10)) * + TIRand.kr(0, 1, Impulse.kr(1)) * + TIRand.kr(0, 1, Impulse.kr(0.1)) + ); + // adjust the multiplier at the end of each line for adjusting levels + // note with each trigger, each source has a 1 in 3 chance of sounding + low_sine = SinOsc.ar(76.midicps / 16) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.1; + high_sine = SinOsc.ar(76.midicps * 8) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.01; + brown_noise = BrownNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.025; + white_noise = WhiteNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.02; + Out.ar(~hdustBusArray[0], + ((low_sine + high_sine + brown_noise + white_noise) ) + ); +}).add; + +) + +( +var bass, hdust, sampler, mixer; +/* +bass = Synth.tail(~group, \bass); +hdust = Synth.tail(~group, \hdust); +sampler = Synth.head(~group, \sampler); +*/ +mixer = Synth.tail(~group, \mixer); + +OSCdef(\mixer, {arg msg, time, addr, port; + mixer.set((msg[1] ++ '_' ++ msg[2] ++ '_' ++ msg[3]), msg[4]) +}, \mixer); + +/* +OSCdef(\sampler, {arg msg, time, addr, port; + msg.postln; + sampler.free; + ~sBuf.free; + ~sBuf = Buffer.read(s, msg[1].asString.postln, action: {sampler = Synth.head(~group, \sampler)}); +}, \sampler); +*/ +) + +/* old something +( +SynthDef(\test, {arg freq, gate = 1, sustain, amp, dur; + var trig, exc, sig1, sig2, noHarms, freqFinal, start, end; + noHarms = 30; + freq = WhiteNoise.ar * 3 + freq; + freqFinal = Duty.ar((1/freq), 0, freq); + trig = Changed.ar(freqFinal); + start = Demand.ar(trig, 0, Dwhite(-1, -0.75)); + end = Demand.ar(trig, 0, Dwhite(0.75, 1)); + exc = Phasor.ar(trig, (end - start) * freqFinal / SampleRate.ir, start, end, 0) * 0.001 + Dust.ar(10000, 0.01); + + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(2, 3)}) ], exc) * 0.5).softclip; + sig1 = HPF.ar(sig1, 300); + Out.ar([0, 1], sig1 * EnvGen.kr(Env.adsr(0.3, 0.3, 0.9, 0.5, 0.9), gate, doneAction: 2)); +}).add; +) +*/ + diff --git a/resources/string_quartet_1/6fb60ab6/6fb60ab6_mus_model.json b/resources/string_quartet_1/6fb60ab6/6fb60ab6_mus_model.json new file mode 100644 index 0000000..1778bf5 --- /dev/null +++ b/resources/string_quartet_1/6fb60ab6/6fb60ab6_mus_model.json @@ -0,0 +1,56 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0.625 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, -1, 1, 0 ] ], 0 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ 2, -1, 0, -1, 1, -1 ], [ 1, 0, 0, -1, 1, 0 ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ 2, -1, 0, -1, 1, -1 ], [ 2, -1, 0, -1, 0, 0 ] ], 1.875 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ 2, -1, 0, -1, 1, -1 ], [ 1, -1, 0, -1, 2, 0 ] ], 1.5 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ 2, -1, 0, -1, 1, -1 ], [ 1, -1, 1, -1, 1, 0 ] ], 0.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ 2, -1, 0, -1, 1, -1 ], [ 2, -1, 0, -2, 1, 0 ] ], 1.25 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ 2, -1, 0, -1, 1, -1 ], [ 0, -1, 0, 0, 1, 0 ] ], 1.625 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ 2, -1, 0, -1, 1, -1 ], [ 1, 0, 0, -1, 1, 0 ] ], 1.875 ] + ], + [ + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, 0, -1, 1, -1 ], [ 1, 0, 0, -1, 1, 0 ] ], 1 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 2, 0 ], [ 1, 0, 0, -1, 1, 0 ] ], 1.125 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, 0, -1, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ] ], 0.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ 1, 0, 0, -1, 1, 0 ] ], 1.5 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -2, 0, -1, 1, 1 ], [ 1, 0, 0, -1, 1, 0 ] ], 1.5 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 1, -1 ], [ 1, 0, 0, -1, 1, 0 ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ 1, 0, 0, -1, 1, 0 ] ], 1.125 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0.5 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 5.5 ] + ] + ] +], +"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 ] ] +], +"cur_uid": "6fb60ab6", +"ref_uid": "nil", +"order_seed": 209164, +"dur_seed": 417909, +"motifs_seed": 885208, +"entrances_probs_vals": [ 0.34, 0, 10, 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, 0, 10, 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, 10, 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 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 2 ], [ 0, 0, 0, 0 ], [ 1, 3 ] ] +], +"sus_weights": [ 0.75, 0.75, 0.75 ], +"order_size": [ 1, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_1/6fb60ab6/lilypond/part_I.ly b/resources/string_quartet_1/6fb60ab6/lilypond/part_I.ly new file mode 100644 index 0000000..3ed19b7 --- /dev/null +++ b/resources/string_quartet_1/6fb60ab6/lilypond/part_I.ly @@ -0,0 +1,28 @@ +{ + { r4 r16[ gis'8.^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ gis'2 ~ } + \bar "|" + { gis'8.[ g'16^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}] ~ g'2. ~ } + \bar "|" + { g'8[ fis'8^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ fis'2 ~ fis'8[ f'8^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { f'4 dis'2^\markup { \pad-markup #0.2 "+12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }} ~ dis'8[ ais8^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { ais2 ~ ais8.[ gis'16^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ gis'4 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/6fb60ab6/lilypond/part_II.ly b/resources/string_quartet_1/6fb60ab6/lilypond/part_II.ly new file mode 100644 index 0000000..af38e4d --- /dev/null +++ b/resources/string_quartet_1/6fb60ab6/lilypond/part_II.ly @@ -0,0 +1,28 @@ +{ + { r4 r16[ e'8.^\markup { \pad-markup #0.2 "+40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ e'2 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'8[ fis'8^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ fis'4 ~ fis'8.[ e'16^\markup { \pad-markup #0.2 "-4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}] ~ e'4 ~ } + \bar "|" + { e'16[ d'8.^\markup { \pad-markup #0.2 "+31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }}] ~ d'2 ~ d'16[ d'8.^\markup { \pad-markup #0.2 "+19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ } + \bar "|" + { d'2 ~ d'16[ b8.^\markup { \pad-markup #0.2 "+42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↓" }}] ~ b4 ~ } + \bar "|" + { b4 ~ b8.[ r16] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/6fb60ab6/lilypond/part_III.ly b/resources/string_quartet_1/6fb60ab6/lilypond/part_III.ly new file mode 100644 index 0000000..ff528bb --- /dev/null +++ b/resources/string_quartet_1/6fb60ab6/lilypond/part_III.ly @@ -0,0 +1,28 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 r8[ fis'8^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ fis'4 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'4 r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/6fb60ab6/lilypond/part_IV.ly b/resources/string_quartet_1/6fb60ab6/lilypond/part_IV.ly new file mode 100644 index 0000000..1592265 --- /dev/null +++ b/resources/string_quartet_1/6fb60ab6/lilypond/part_IV.ly @@ -0,0 +1,28 @@ +{ + { cis'1^\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'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'4 r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/761e4585/761e4585_code.scd b/resources/string_quartet_1/761e4585/761e4585_code.scd new file mode 100644 index 0000000..3eb3cbd --- /dev/null +++ b/resources/string_quartet_1/761e4585/761e4585_code.scd @@ -0,0 +1,1058 @@ +( +// 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, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark +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; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file; + file = File(path, "w"); + 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"); +//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((dir +/+ ".." +/+ "resources" +/+ 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((dir +/+ ".." +/+ "resources" +/+ curUID).standardizePath); + File.copy(exPath, (dir +/+ ".." +/+ "resources" +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (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; + }); + 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((dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \".." +/+ ".." +/+ "resources" +/+ 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); + +) + +~transcribe.value(~seq, dir); + +( +//synthdefs +~stringModelBusArray = 4.collect({Bus.audio(s, 1)}); +~sineBusArray = 4.collect({Bus.audio(s, 1)}); +~bassBusArray = 1.collect({Bus.audio(s, 1)}); +~hdustBusArray = 1.collect({Bus.audio(s, 1)}); +~samplerBusArray = 2.collect({Bus.audio(s, 1)}); +~sBuf = Buffer.alloc(s, 10, 2); +SynthDef(\string_model, {arg freq, gate = 1, sustain, amp, dur, attack, release = 1, busIndex = 0; + var trig, exc, sig1, sig2, noHarms; + noHarms = rrand(20, 40); + exc = Saw.ar(freq, TRand.ar(0.5, 1, Impulse.ar(freq))) * 0.001 + Dust.ar(10000, 0.01); + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(1, 2)}) ], exc) * 0.8).softclip; + //sig1 = HPF.ar(sig1, 300); + Out.ar(Select.kr(busIndex, ~stringModelBusArray), sig1 * amp * EnvGen.kr(Env.adsr(attack, 0.3, 0.9, release, 0.9, -3), gate, doneAction: 2)); + //Out.ar([0, 1], sig1 * EnvGen.kr(Env.asr(dur, 0.3, 1), gate, doneAction: 2)); +}).add; + +SynthDef(\sine, {arg freq, gate = 1, sustain, amp, dur, busIndex = 0; + var sig; + sig = SinOsc.ar(freq); + Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.asr(0.3, 0.4, 0.3), gate, timeScale: dur, doneAction: 2)); + //Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.sine(dur), gate, doneAction: 2)); +}).add; + +SynthDef(\mixer, {arg freq, gate = 1, sustain, amp, dur, out; + var nameSpaces, sigs; + + sigs = [~stringModelBusArray, ~sineBusArray/*, ~bassBusArray, ~hdustBusArray, ~samplerBusArray*/].collect({arg busArray, i; + var nameSpace, sig; + nameSpace = ['string', 'sine', 'bass', 'hdust', 'sampler'][i]; + sig = busArray.collect({arg bus, c; In.ar(bus, 1) * NamedControl.kr(\ ++ nameSpace ++ '_volume_' ++ c, 1, 0.1)}); + sig = sig.collect({arg channel, c; Pan2.ar(channel, NamedControl.kr(\ ++ nameSpace ++ '_pan_' ++ c, i / (busArray.size - 1), 0.1) * 2 - 1)}); + sig = sig.collect({arg channel, c; channel * NamedControl.kr(\ ++ nameSpace ++ '_mute_' ++ c, 1, 0.1)}); + sig = Mix.ar(sig) * pow(NamedControl.kr(\ ++ nameSpace ++ '_volume_master', 1, 0.1), 2); + }); + + sigs = Mix.ar(sigs / 4); + Out.ar(0, sigs) +}).add; + +SynthDef(\bass, { + var switches, drone; + switches = {|i| Dust.kr(0.1)} ! 9; + drone = {|i| var harm = pow(2, 2 - (i / 3).trunc), amp = (1 / pow(harm, 2)); + SinOsc.ar(60 * harm + TRand.kr(-3, 3, switches[i]), 0, amp)} ! 9; + Out.ar(~bassBusArray[0], Mix.new(drone) * 0.2); +}).add; + +SynthDef(\sampler, { + Out.ar(~samplerBusArray, PlayBuf.ar(2, ~sBuf, BufRateScale.kr(~sBuf), doneAction: 2)) +}).add; + +// main routine +SynthDef(\hdust, { + arg gate = 0; + var hierarchical_dust, low_sine, high_sine, brown_noise, white_noise; + // this triggers the combinations of sources + // it is similar to the Supercollider UGen called dust but with a hierarchical structure + hierarchical_dust = ( + TIRand.kr(0, 1, Impulse.kr(100)) * + TIRand.kr(0, 1, Impulse.kr(10)) * + TIRand.kr(0, 1, Impulse.kr(1)) * + TIRand.kr(0, 1, Impulse.kr(0.1)) + ); + // adjust the multiplier at the end of each line for adjusting levels + // note with each trigger, each source has a 1 in 3 chance of sounding + low_sine = SinOsc.ar(76.midicps / 16) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.1; + high_sine = SinOsc.ar(76.midicps * 8) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.01; + brown_noise = BrownNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.025; + white_noise = WhiteNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.02; + Out.ar(~hdustBusArray[0], + ((low_sine + high_sine + brown_noise + white_noise) ) + ); +}).add; + +) + +( +var bass, hdust, sampler, mixer; +/* +bass = Synth.tail(~group, \bass); +hdust = Synth.tail(~group, \hdust); +sampler = Synth.head(~group, \sampler); +*/ +mixer = Synth.tail(~group, \mixer); + +OSCdef(\mixer, {arg msg, time, addr, port; + mixer.set((msg[1] ++ '_' ++ msg[2] ++ '_' ++ msg[3]), msg[4]) +}, \mixer); + +/* +OSCdef(\sampler, {arg msg, time, addr, port; + msg.postln; + sampler.free; + ~sBuf.free; + ~sBuf = Buffer.read(s, msg[1].asString.postln, action: {sampler = Synth.head(~group, \sampler)}); +}, \sampler); +*/ +) + +/* old something +( +SynthDef(\test, {arg freq, gate = 1, sustain, amp, dur; + var trig, exc, sig1, sig2, noHarms, freqFinal, start, end; + noHarms = 30; + freq = WhiteNoise.ar * 3 + freq; + freqFinal = Duty.ar((1/freq), 0, freq); + trig = Changed.ar(freqFinal); + start = Demand.ar(trig, 0, Dwhite(-1, -0.75)); + end = Demand.ar(trig, 0, Dwhite(0.75, 1)); + exc = Phasor.ar(trig, (end - start) * freqFinal / SampleRate.ir, start, end, 0) * 0.001 + Dust.ar(10000, 0.01); + + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(2, 3)}) ], exc) * 0.5).softclip; + sig1 = HPF.ar(sig1, 300); + Out.ar([0, 1], sig1 * EnvGen.kr(Env.adsr(0.3, 0.3, 0.9, 0.5, 0.9), gate, doneAction: 2)); +}).add; +) +*/ + diff --git a/resources/string_quartet_1/761e4585/761e4585_mus_model.json b/resources/string_quartet_1/761e4585/761e4585_mus_model.json new file mode 100644 index 0000000..f2ad529 --- /dev/null +++ b/resources/string_quartet_1/761e4585/761e4585_mus_model.json @@ -0,0 +1,80 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 1, -1, 0, -1, 1, 0 ] ], 1.75 ], + [ [ [ "Rest" ], [ "Rest" ], [ 1, -1, 1, -1, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 0 ], + [ [ [ "Rest" ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 0.625 ], + [ [ [ "Rest" ], [ 0, -1, 0, 0, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 0.875 ], + [ [ [ "Rest" ], [ 0, -1, 0, 0, 1, 0 ], [ 2, -1, 0, -1, 1, -1 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.375 ], + [ [ [ "Rest" ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 2, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 0 ], + [ [ [ "Rest" ], [ 0, 0, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 2, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 0.5 ], + [ [ [ "Rest" ], [ 0, -1, 0, -1, 1, 1 ], [ 1, -1, 0, -1, 2, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 0 ], + [ [ [ "Rest" ], [ 0, -1, 0, -1, 1, 1 ], [ 1, 0, 0, -1, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.25 ], + [ [ [ "Rest" ], [ 0, -1, 0, -1, 1, 1 ], [ 2, -1, -1, -1, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 0 ], + [ [ [ "Rest" ], [ 1, -1, -1, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.875 ] + ], + [ + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 1, -1, -1, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 1.625 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 1, -1, 0, -1, 1, 0 ] ], 0 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 2, -1, 0, -2, 1, 0 ] ], 0 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 1, -1, 0, -1, 2, 0 ], [ 2, -1, 0, -2, 1, 0 ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 0, -1, 0, 0, 1, 0 ], [ 2, -1, 0, -1, 0, 0 ], [ 2, -1, 0, -2, 1, 0 ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 0, -1, 0, -1, 1, 1 ], [ 2, -1, 0, -1, 0, 0 ], [ 2, -1, 0, -2, 1, 0 ] ], 0 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 0, -1, 0, -1, 1, 1 ], [ 1, -1, 0, -1, 1, 1 ], [ 2, -1, 0, -2, 1, 0 ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 1, -1, -1, -1, 1, 0 ], [ 1, -1, 0, -1, 1, 1 ], [ 2, -1, 0, -2, 1, 0 ] ], 1.875 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 1, -1, -1, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 2, -1, 0, -2, 1, 0 ] ], 1.375 ] + ], + [ + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 1, -1, -1, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ "Rest" ] ], 1.375 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 0, 0, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ "Rest" ] ], 1 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -1, -1, -2, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ "Rest" ] ], 1.125 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 1, -1, -1, -1, 2, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ "Rest" ] ], 1.625 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, -1, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ "Rest" ] ], 1.75 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 1, -1, 1, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ "Rest" ] ], 1.125 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ "Rest" ] ], 1.25 ] + ], + [ + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, -1, -1, 1, 0 ], [ 2, -1, 0, -2, 1, 0 ] ], 0 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 1, -1, 0, 0, 1, 0 ], [ 2, -1, 0, -2, 1, 0 ] ], 1.25 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -1, 0, -1, 1, 0 ], [ 2, -1, 0, -2, 1, 0 ] ], 1.25 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 1 ], [ 2, -1, 0, -2, 1, 0 ] ], 1.5 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ 3, -1, 0, -2, 1, 0 ], [ 2, -1, 0, -2, 1, 0 ] ], 2.375 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ 2, -1, 0, -2, 1, 0 ] ], 0 ], + [ [ [ 1, -1, 0, -1, 1, 0 ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ 0, 0, -1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], + [ [ 0, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ] +], +"cur_uid": "761e4585", +"ref_uid": "nil", +"order_seed": 209164, +"dur_seed": 417909, +"motifs_seed": 885208, +"entrances_probs_vals": [ 0.34, 0, 10, 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, 0, 10, 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, 10, 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 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 2 ], [ 0, 0, 0, 0 ], [ 1, 3 ] ] +], +"sus_weights": [ 0.75, 0.75, 0.75 ], +"order_size": [ 1, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_1/761e4585/lilypond/part_I.ly b/resources/string_quartet_1/761e4585/lilypond/part_I.ly new file mode 100644 index 0000000..8f69c77 --- /dev/null +++ b/resources/string_quartet_1/761e4585/lilypond/part_I.ly @@ -0,0 +1,36 @@ +{ + { cis'1^\markup { \pad-markup #0.2 "-19"} ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'2. ~ cis'8.[ dis'16^\markup { \pad-markup #0.2 "+12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}] ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'8.[ r16] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. r16[ dis'8.^\markup { \pad-markup #0.2 "+12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }}] ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/761e4585/lilypond/part_II.ly b/resources/string_quartet_1/761e4585/lilypond/part_II.ly new file mode 100644 index 0000000..af6a6cf --- /dev/null +++ b/resources/string_quartet_1/761e4585/lilypond/part_II.ly @@ -0,0 +1,36 @@ +{ + { r2. r8[ f'8^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { f'8.[ fis'16^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }}] ~ fis'4 ~ fis'8[ e'8^\markup { \pad-markup #0.2 "+40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↓" }}] ~ e'4 ~ } + \bar "|" + { e'4 ~ e'16[ fis'8.^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}] ~ fis'16[ gis'8.^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ gis'4 ~ } + \bar "|" + { gis'8.[ a'16^\markup { \pad-markup #0.2 "-6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}] ~ a'2. ~ } + \bar "|" + { a'2. ~ a'8.[ fis'16^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ } + \bar "|" + { fis'2. ~ fis'16[ g'8.^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}] ~ } + \bar "|" + { g'2 ~ g'8.[ a'16^\markup { \pad-markup #0.2 "+21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ a'4 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'2 a'2^\markup { \pad-markup #0.2 "-6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'2. ~ a'16[ ais'8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { ais'4 ~ ais'8.[ cis''16^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ cis''2 ~ } + \bar "|" + { cis''16[ d''8.^\markup { \pad-markup #0.2 "+19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ d''2 ~ d''16[ dis''8.^\markup { \pad-markup #0.2 "+12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ~ } + \bar "|" + { dis''1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/761e4585/lilypond/part_III.ly b/resources/string_quartet_1/761e4585/lilypond/part_III.ly new file mode 100644 index 0000000..144d704 --- /dev/null +++ b/resources/string_quartet_1/761e4585/lilypond/part_III.ly @@ -0,0 +1,36 @@ +{ + { r2. r8[ ais8^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais4 ~ ais16[ gis8.^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ gis16[ a8.^\markup { \pad-markup #0.2 "+21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ~ a4 ~ } + \bar "|" + { a8.[ a16^\markup { \pad-markup #0.2 "-6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ a2. ~ } + \bar "|" + { a2. ~ a8.[ ais16^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais2 ~ ais8.[ a16^\markup { \pad-markup #0.2 "+21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}] ~ a4 ~ } + \bar "|" + { a2 ~ a16[ a8.^\markup { \pad-markup #0.2 "-6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ a4 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a2. ~ a8[ gis8^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { gis4 ~ gis8[ b8^\markup { \pad-markup #0.2 "+25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }}] ~ b4 ~ b8.[ d'16^\markup { \pad-markup #0.2 "+46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↑" }}] ~ } + \bar "|" + { d'2. d'4^\markup { \pad-markup #0.2 "-8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }} ~ } + \bar "|" + { d'2 ~ d'8[ f'8^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ f'4 ~ } + \bar "|" + { f'8.[ fis'16^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ fis'2. ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/761e4585/lilypond/part_IV.ly b/resources/string_quartet_1/761e4585/lilypond/part_IV.ly new file mode 100644 index 0000000..89e838d --- /dev/null +++ b/resources/string_quartet_1/761e4585/lilypond/part_IV.ly @@ -0,0 +1,36 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r8[ cis'8^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ~ cis'2. ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/774ed940/774ed940_code.scd b/resources/string_quartet_1/774ed940/774ed940_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_1/774ed940/774ed940_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/string_quartet_1/774ed940/774ed940_mus_model.json b/resources/string_quartet_1/774ed940/774ed940_mus_model.json new file mode 100644 index 0000000..beff01e --- /dev/null +++ b/resources/string_quartet_1/774ed940/774ed940_mus_model.json @@ -0,0 +1,70 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 0.25 ], + [ [ [ 0, -1, 1, -2, 1, 1 ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 0.5 ], + [ [ [ 0, 0, 1, -2, 1, 0 ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 1.25 ], + [ [ [ 0, -1, 1, -2, 1, 1 ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 0.75 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ] ], 2.875 ] + ], + [ + [ [ [ 0, -1, 1, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ 0, 0, 1, -1, 1, 0 ] ], 0 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ "Rest" ], [ 0, -1, 1, 0, 1, 0 ], [ 0, 0, 1, -1, 1, 0 ] ], 0 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ 0, -1, 2, -1, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, 0, 1, -1, 1, 0 ] ], 1.5 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ 1, -1, 1, -1, 1, -1 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, 0, 1, -1, 1, 0 ] ], 1.5 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ -1, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, 0, 1, -1, 1, 0 ] ], 0 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ -1, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 1.5 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ 0, -1, 1, -1, 2, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 0.625 ], + [ [ [ 0, -1, 1, -1, 1, 0 ], [ 1, -2, 1, -1, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 1.25 ] + ], + [ + [ [ [ 0, -1, 1, -1, 1, 0 ], [ -1, -1, 1, 0, 1, 1 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 0 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 1, 0, 1, 1 ], [ 0, -1, 1, 0, 1, 0 ], [ 2, -1, 1, -1, 1, -1 ] ], 1 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 1, 0, 1, 1 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 1, 1, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], 1.25 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 0, 2, 0 ] ], 1.125 ], + [ [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], 0 ], + [ [ [ -1, -1, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], 0.875 ], + [ [ [ -1, -1, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], 2 ], + [ [ [ -1, -1, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], 0], + [ [ [ -1, -1, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 2.875 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 1, 1, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], + [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 1, 1, 0 ] ], + [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 0, -1, 1, 0, 2, 0 ] ], + [ [ -2, 0, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ], + [ [ -1, -1, 1, 0, 1, 0 ], [ -1, -1, 2, 0, 1, 0 ], [ 0, -1, 1, 0, 1, 0 ], [ 1, -1, 1, 0, 1, 0 ] ] +], +"cur_uid": "774ed940", +"ref_uid": "61ce9067", +"order_seed": 473248, +"dur_seed": 979780, +"motifs_seed": 262605, +"entrances_probs_vals": [ 1, 0, 0, 0.19, 1.7582417582418, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0.85, 0, 1.2301587301587, 0.54945054945055, 1.79, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0.16, 1.0714285714286, 0.16, 1.510989010989, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2560, 59.442724458204 ], [ -1074, 393.8080495356 ], [ 59.442724458205, 1638.3900928793 ], [ -52.012383900929, 1582.6625386997 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.092592592592593, 0.78977272727273, 0.16460905349794, 0, 0.24279835390947, 0, 0.60699588477366, 0.63636363636364, 0.73662551440329, 0, 1, 0 ], +"passages_weights": [ 1, 0, 0.6, 0, 0.54 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 0, 0, 0, 0 ], [ 1, 2 ] ], + [ [ 0 ], [ 3, 2, 1, 1, 1, 3, 1, 1 ], [ ] ], + [ [ 2 ], [ 1, 0, 3, 1, 1, 3, 3, 0 ], [ ] ] +], +"sus_weights": [ 0.69, 0, 0 ], +"order_size": [ 1, 7 ], +"passages_size": [ 3, 6 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_1/774ed940/lilypond/part_I.ly b/resources/string_quartet_1/774ed940/lilypond/part_I.ly new file mode 100644 index 0000000..2b16ea6 --- /dev/null +++ b/resources/string_quartet_1/774ed940/lilypond/part_I.ly @@ -0,0 +1,22 @@ +{ + { g'1^\markup { \pad-markup #0.2 "-2"} ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'2. ~ g'16[ c'8.^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'4 ~ c'16[ gis'8.^\markup { \pad-markup #0.2 "+26"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ gis'2 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'2 c''2^\markup { \pad-markup #0.2 "+5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }} ~ } + \bar "|" + { c''8[ gis'8^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↑" }}] ~ gis'4 ~ gis'8.[ d''16^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ d''4 ~ } + \bar "|" + { d''1 ~ } + \bar "|" + { d''8[ r8] r2. } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/774ed940/lilypond/part_II.ly b/resources/string_quartet_1/774ed940/lilypond/part_II.ly new file mode 100644 index 0000000..86115b1 --- /dev/null +++ b/resources/string_quartet_1/774ed940/lilypond/part_II.ly @@ -0,0 +1,22 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. r16[ d'8.^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'8[ r8] r2. } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/774ed940/lilypond/part_III.ly b/resources/string_quartet_1/774ed940/lilypond/part_III.ly new file mode 100644 index 0000000..9d65346 --- /dev/null +++ b/resources/string_quartet_1/774ed940/lilypond/part_III.ly @@ -0,0 +1,22 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. r16[ a8.^\markup { \pad-markup #0.2 "-47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { a2 ~ a16[ gis8.^\markup { \pad-markup #0.2 "+26"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ gis4 ~ } + \bar "|" + { gis4 ~ gis16[ d8.^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ d2 ~ } + \bar "|" + { d16[ ais8.^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ ais8[ ais8^\markup { \pad-markup #0.2 "-35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ ais2 } + \bar "|" + { b2^\markup { \pad-markup #0.2 "-24"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }} c'2^\markup { \pad-markup #0.2 "+5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c'8[ fis8^\markup { \pad-markup #0.2 "+22"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}] ~ fis2. ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis8[ r8] r2. } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/774ed940/lilypond/part_IV.ly b/resources/string_quartet_1/774ed940/lilypond/part_IV.ly new file mode 100644 index 0000000..c7d9483 --- /dev/null +++ b/resources/string_quartet_1/774ed940/lilypond/part_IV.ly @@ -0,0 +1,22 @@ +{ + { r8[ dis8^\markup { \pad-markup #0.2 "+39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ~ dis8[ d8^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ d2 } + \bar "|" + { dis4^\markup { \pad-markup #0.2 "+39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }} ~ dis8[ f8^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}] ~ f2 ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f1 } + \bar "|" + { a,1^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }} ~ } + \bar "|" + { a,2 ~ a,8.[ d16^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ d4 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d8[ r8] r2. } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/784130cc/784130cc_code.scd b/resources/string_quartet_1/784130cc/784130cc_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/784130cc/784130cc_mus_model.json b/resources/string_quartet_1/784130cc/784130cc_mus_model.json new file mode 100644 index 0000000..697284e --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/784130cc/lilypond/part_I.ly b/resources/string_quartet_1/784130cc/lilypond/part_I.ly new file mode 100644 index 0000000..18a5337 --- /dev/null +++ b/resources/string_quartet_1/784130cc/lilypond/part_I.ly @@ -0,0 +1,14 @@ +{ + { ais'1^\markup { \pad-markup #0.2 "+41"} ~ } + \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/string_quartet_1/784130cc/lilypond/part_II.ly b/resources/string_quartet_1/784130cc/lilypond/part_II.ly new file mode 100644 index 0000000..3a20dd1 --- /dev/null +++ b/resources/string_quartet_1/784130cc/lilypond/part_II.ly @@ -0,0 +1,14 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/784130cc/lilypond/part_III.ly b/resources/string_quartet_1/784130cc/lilypond/part_III.ly new file mode 100644 index 0000000..6d5d2d3 --- /dev/null +++ b/resources/string_quartet_1/784130cc/lilypond/part_III.ly @@ -0,0 +1,14 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/784130cc/lilypond/part_IV.ly b/resources/string_quartet_1/784130cc/lilypond/part_IV.ly new file mode 100644 index 0000000..ed76315 --- /dev/null +++ b/resources/string_quartet_1/784130cc/lilypond/part_IV.ly @@ -0,0 +1,14 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/79e0a4a7/79e0a4a7_code.scd b/resources/string_quartet_1/79e0a4a7/79e0a4a7_code.scd new file mode 100644 index 0000000..8e9fe42 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/79e0a4a7/79e0a4a7_mus_model.json b/resources/string_quartet_1/79e0a4a7/79e0a4a7_mus_model.json new file mode 100644 index 0000000..4a70a1c --- /dev/null +++ b/resources/string_quartet_1/79e0a4a7/79e0a4a7_mus_model.json @@ -0,0 +1,47 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 4.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 ] ], 4.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" ] ], 6.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/string_quartet_1/79e0a4a7/lilypond/part_I.ly b/resources/string_quartet_1/79e0a4a7/lilypond/part_I.ly new file mode 100644 index 0000000..3d68499 --- /dev/null +++ b/resources/string_quartet_1/79e0a4a7/lilypond/part_I.ly @@ -0,0 +1,28 @@ +{ + { r1 } + \bar "|" + { 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'8[ a'8^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }}] ~ a'2. ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'2 ~ a'8[ r8] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/79e0a4a7/lilypond/part_II.ly b/resources/string_quartet_1/79e0a4a7/lilypond/part_II.ly new file mode 100644 index 0000000..771f45e --- /dev/null +++ b/resources/string_quartet_1/79e0a4a7/lilypond/part_II.ly @@ -0,0 +1,28 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 r8[ gis'8^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ gis'2 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'2. ~ gis'8[ r8] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/79e0a4a7/lilypond/part_III.ly b/resources/string_quartet_1/79e0a4a7/lilypond/part_III.ly new file mode 100644 index 0000000..c1f7fda --- /dev/null +++ b/resources/string_quartet_1/79e0a4a7/lilypond/part_III.ly @@ -0,0 +1,28 @@ +{ + { 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'1 ~ } + \bar "|" + { fis'4 ~ fis'16[ r8.] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/79e0a4a7/lilypond/part_IV.ly b/resources/string_quartet_1/79e0a4a7/lilypond/part_IV.ly new file mode 100644 index 0000000..5984926 --- /dev/null +++ b/resources/string_quartet_1/79e0a4a7/lilypond/part_IV.ly @@ -0,0 +1,28 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 r8[ cis'8^\markup { \pad-markup #0.2 "-19"}] ~ cis'2 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/7d3c9a80/7d3c9a80_code.scd b/resources/string_quartet_1/7d3c9a80/7d3c9a80_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/7d3c9a80/7d3c9a80_mus_model.json b/resources/string_quartet_1/7d3c9a80/7d3c9a80_mus_model.json new file mode 100644 index 0000000..e6e1f1a --- /dev/null +++ b/resources/string_quartet_1/7d3c9a80/7d3c9a80_mus_model.json @@ -0,0 +1,48 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 2, -2, 0, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 6.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 ] ], 2.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" ] ], 5.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/string_quartet_1/7d3c9a80/lilypond/part_I.ly b/resources/string_quartet_1/7d3c9a80/lilypond/part_I.ly new file mode 100644 index 0000000..e9da74f --- /dev/null +++ b/resources/string_quartet_1/7d3c9a80/lilypond/part_I.ly @@ -0,0 +1,30 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 c''2.^\markup { \pad-markup #0.2 "+27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }} ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''2 cis''2^\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''1 ~ } + \bar "|" + { cis''4 ~ cis''16[ r8.] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/7d3c9a80/lilypond/part_II.ly b/resources/string_quartet_1/7d3c9a80/lilypond/part_II.ly new file mode 100644 index 0000000..544ce2c --- /dev/null +++ b/resources/string_quartet_1/7d3c9a80/lilypond/part_II.ly @@ -0,0 +1,30 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r8[ a'8^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }}] ~ a'2. ~ } + \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/string_quartet_1/7d3c9a80/lilypond/part_III.ly b/resources/string_quartet_1/7d3c9a80/lilypond/part_III.ly new file mode 100644 index 0000000..924f946 --- /dev/null +++ b/resources/string_quartet_1/7d3c9a80/lilypond/part_III.ly @@ -0,0 +1,30 @@ +{ + { 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'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'4 ~ fis'8.[ r16] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/7d3c9a80/lilypond/part_IV.ly b/resources/string_quartet_1/7d3c9a80/lilypond/part_IV.ly new file mode 100644 index 0000000..91a8502 --- /dev/null +++ b/resources/string_quartet_1/7d3c9a80/lilypond/part_IV.ly @@ -0,0 +1,30 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r8[ cis'8^\markup { \pad-markup #0.2 "-19"}] ~ cis'2. ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'2 ~ cis'8.[ r16] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/7edbdceb/7edbdceb_code.scd b/resources/string_quartet_1/7edbdceb/7edbdceb_code.scd new file mode 100644 index 0000000..a98b916 --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/7edbdceb/7edbdceb_mus_model.json b/resources/string_quartet_1/7edbdceb/7edbdceb_mus_model.json new file mode 100644 index 0000000..fe59e9c --- /dev/null +++ b/resources/string_quartet_1/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/string_quartet_1/7edbdceb/lilypond/part_I.ly b/resources/string_quartet_1/7edbdceb/lilypond/part_I.ly new file mode 100644 index 0000000..18a5337 --- /dev/null +++ b/resources/string_quartet_1/7edbdceb/lilypond/part_I.ly @@ -0,0 +1,14 @@ +{ + { ais'1^\markup { \pad-markup #0.2 "+41"} ~ } + \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/string_quartet_1/7edbdceb/lilypond/part_II.ly b/resources/string_quartet_1/7edbdceb/lilypond/part_II.ly new file mode 100644 index 0000000..d0647dd --- /dev/null +++ b/resources/string_quartet_1/7edbdceb/lilypond/part_II.ly @@ -0,0 +1,14 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/7edbdceb/lilypond/part_III.ly b/resources/string_quartet_1/7edbdceb/lilypond/part_III.ly new file mode 100644 index 0000000..6d5d2d3 --- /dev/null +++ b/resources/string_quartet_1/7edbdceb/lilypond/part_III.ly @@ -0,0 +1,14 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/7edbdceb/lilypond/part_IV.ly b/resources/string_quartet_1/7edbdceb/lilypond/part_IV.ly new file mode 100644 index 0000000..a86ce2c --- /dev/null +++ b/resources/string_quartet_1/7edbdceb/lilypond/part_IV.ly @@ -0,0 +1,14 @@ +{ + { 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 "||" +} \ No newline at end of file diff --git a/resources/string_quartet_1/tmp/tmp_mus_model.json b/resources/string_quartet_1/tmp/tmp_mus_model.json new file mode 100644 index 0000000..1beabb8 --- /dev/null +++ b/resources/string_quartet_1/tmp/tmp_mus_model.json @@ -0,0 +1,78 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ], [ "Rest" ] ], 2.75 ], + [ [ [ 2, -1, 1, -2, 0, 0 ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ], [ "Rest" ] ], 0.375 ], + [ [ [ 1, -1, 2, -2, 1, 0 ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ], [ "Rest" ] ], 3.75 ] + ], + [ + [ [ [ 1, -1, 2, -2, 1, 0 ], [ "Rest" ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 0 ], + [ [ [ 1, -1, 2, -2, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 2, -1, 1, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 3.375 ], + [ [ [ 1, -1, 2, -2, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 3, -3, 1, -2, 0, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 1 ], + [ [ [ 1, -1, 2, -2, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 2 ] + ], + [ + [ [ [ 0, 1, 2, -2, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 1.125 ], + [ [ [ 2, -3, 1, -1, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 2, -2, 1, -1, 1, 0 ] ], 3 ] + ], + [ + [ [ [ 2, -3, 1, -1, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 2, -3, 0, -1, 1, 0 ] ], 0.875 ], + [ [ [ 2, -3, 1, -1, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 1, -3, 1, 0, 1, 0 ] ], 1.375 ] + ], + [ + [ [ [ 1, -3, 1, 0, 0, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 1, -3, 1, 0, 1, 0 ] ], 1.125 ], + [ [ [ 3, -3, 1, -2, 1, -1 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 1, -3, 1, 0, 1, 0 ] ], 1.875 ] + ], + [ + [ [ [ 3, -3, 1, -2, 1, -1 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 1, 0, 1, -2, 1, 0 ] ], 0.875 ], + [ [ [ 3, -3, 1, -2, 1, -1 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 0, 0, 2, -2, 2, 0 ] ], 2.125 ] + ], + [ + [ [ [ 3, -4, 1, -2, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 0, 0, 2, -2, 2, 0 ] ], 0.875 ], + [ [ [ 3, -3, 1, -2, 0, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 0, 0, 2, -2, 2, 0 ] ], 1.625 ], + [ [ [ 3, -3, 1, -2, 0, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ "Rest" ], [ 0, 0, 2, -2, 2, 0 ] ], 0.75 ], + [ [ [ "Rest" ], [ 3, -3, 1, -2, 1, 0 ], [ "Rest" ], [ 0, 0, 2, -2, 2, 0 ] ], 1.125 ], + [ [ [ "Rest" ], [ 3, -3, 1, -2, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.25 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 4.75 ] + ] + ] +], +"last_changes": +[ + [ [ 3, -3, 1, -2, 1, -1 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 1, -3, 1, 0, 1, 0 ] ], + [ [ 3, -3, 1, -2, 1, -1 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 1, 0, 1, -2, 1, 0 ] ], + [ [ 3, -3, 1, -2, 1, -1 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 0, 0, 2, -2, 2, 0 ] ], + [ [ 3, -4, 1, -2, 1, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 0, 0, 2, -2, 2, 0 ] ], + [ [ 3, -3, 1, -2, 0, 0 ], [ 3, -3, 1, -2, 1, 0 ], [ 0, 0, 2, -2, 1, 0 ], [ 0, 0, 2, -2, 2, 0 ] ] +], +"cur_uid": "tmp", +"ref_uid": "6ed95c4c", +"order_seed": 290117, +"dur_seed": 200959, +"motifs_seed": 970117, +"entrances_probs_vals": [ 0.75, 0, 5.0793650793651, 0, 0.5, 0.26424870466321, 0.75675675675676, 0.5, 0.5, 0.58549222797927, 0.72635135135135, 1, 0.5 ], +"passages_probs_vals": [ 0.27, 0, 2.7380952380952, 0.24725274725275, 1.48, 0.20725388601036, 0.68581081081081, 0.24093264248705, 0.34121621621622, 0.5, 0.5, 0.67616580310881, 0.81081081081081, 1, 0.5 ], +"exits_probs_vals": [ 0.27, 0, 2.7380952380952, 0.24725274725275, 1.48, 0.20725388601036, 0.68581081081081, 0.24093264248705, 0.34121621621622, 0.5, 0.5, 0.67616580310881, 0.81081081081081, 1, 0.5 ], +"ranges": [ [ -1482.3529411765, 542 ], [ -1129.4117647059, 1378 ], [ -256.34674922601, 1601 ], [ -238, 1712.693498452 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.19135802469136, 0.27272727272727, 0.2962962962963, 0, 0.33539094650206, 0, 0.34362139917695, 0, 0.38271604938272, 0.15340909090909, 0.39711934156379, 0.56818181818182, 0.43415637860082, 0, 0.51028806584362, 0, 0.56172839506173, 0.84090909090909, 0.61316872427984, 0, 0.69135802469136, 0.28977272727273, 0.73662551440329, 0, 0.97736625514403, 0 ], +"passages_weights": [ 0.75, 0.75, 0.75, 0.75, 0.75 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 2 ], [ 0, 0 ], [ 3, 1 ] ], + [ [ 0, 3, 1 ], [ 2, 2 ], [ ] ], + [ [ 1, 3, 2 ], [ 0, 0 ], [ ] ], + [ [ 2, 0, 1 ], [ 3, 3 ], [ ] ], + [ [ 3, 1, 2 ], [ 0, 0 ], [ ] ], + [ [ 0, 2, 1 ], [ 3, 3 ], [ ] ], + [ [ 2, 3, 1 ], [ 0, 0 ], [ ] ] +], +"sus_weights": [ 0.45, 0.3, 0.38 ], +"order_size": [ 6.0510204081633, 15.142857142857 ], +"passages_size": [ 1, 1 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2.json b/resources/string_quartet_2.json new file mode 100644 index 0000000..56e0507 --- /dev/null +++ b/resources/string_quartet_2.json @@ -0,0 +1,16 @@ +{ +"ledger": +[ + "5201b8af", + "525274f2", + "41e447bc", + "74307bb4", + "628706ec", + "4c059f33", + "60adbbef", + "74b8f8d9", + "62300302", + "72dc057f", + "76e45e56" +] +} \ No newline at end of file diff --git a/resources/string_quartet_2.json_bak b/resources/string_quartet_2.json_bak new file mode 100644 index 0000000..d5a6964 --- /dev/null +++ b/resources/string_quartet_2.json_bak @@ -0,0 +1,15 @@ +{ +"ledger": +[ + "5201b8af", + "525274f2", + "41e447bc", + "74307bb4", + "628706ec", + "4c059f33", + "60adbbef", + "74b8f8d9", + "62300302", + "72dc057f" +] +} \ No newline at end of file diff --git a/resources/string_quartet_2/40119c5a/40119c5a_code.scd b/resources/string_quartet_2/40119c5a/40119c5a_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/40119c5a/40119c5a_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/string_quartet_2/40119c5a/40119c5a_mus_model.json b/resources/string_quartet_2/40119c5a/40119c5a_mus_model.json new file mode 100644 index 0000000..776b240 --- /dev/null +++ b/resources/string_quartet_2/40119c5a/40119c5a_mus_model.json @@ -0,0 +1,58 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 4.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], 4.75 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 4.125 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ "Rest" ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ "Rest" ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ] +], +"cur_uid": "40119c5a", +"ref_uid": "77b0d2dc", +"order_seed": 921767, +"dur_seed": 954688, +"motifs_seed": 995213, +"entrances_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/41e447bc/41e447bc_code.scd b/resources/string_quartet_2/41e447bc/41e447bc_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/41e447bc/41e447bc_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/string_quartet_2/41e447bc/41e447bc_mus_model.json b/resources/string_quartet_2/41e447bc/41e447bc_mus_model.json new file mode 100644 index 0000000..105ee42 --- /dev/null +++ b/resources/string_quartet_2/41e447bc/41e447bc_mus_model.json @@ -0,0 +1,64 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 2.125 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 4.75 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], 2.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 3.375 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 1.375 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 4.625 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 1, 0, 0, 0 ], [ "Rest" ], [ 0, 1, -1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ] +], +"cur_uid": "41e447bc", +"ref_uid": "77b0d2dc", +"order_seed": 921767, +"dur_seed": 954688, +"motifs_seed": 995213, +"entrances_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/41e447bc/lilypond/part_I.ly b/resources/string_quartet_2/41e447bc/lilypond/part_I.ly new file mode 100644 index 0000000..9aaa7f4 --- /dev/null +++ b/resources/string_quartet_2/41e447bc/lilypond/part_I.ly @@ -0,0 +1,20 @@ +{ + { r1 } + \bar "|" + { r16[ c''8.^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ c''2. ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''4 ~ c''8.[ e'16^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ e'2 ~ } + \bar "|" + { e'2. ~ e'8[ e'8^\markup { \pad-markup #0.2 "-41"}] ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'2 ~ e'16[ dis'8.^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}] ~ dis'4 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/41e447bc/lilypond/part_II.ly b/resources/string_quartet_2/41e447bc/lilypond/part_II.ly new file mode 100644 index 0000000..2fe1107 --- /dev/null +++ b/resources/string_quartet_2/41e447bc/lilypond/part_II.ly @@ -0,0 +1,20 @@ +{ + { r1 } + \bar "|" + { r16[ e'8.^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ e'2. ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'4 ~ e'8.[ e'16^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ e'2 ~ } + \bar "|" + { e'2. ~ e'8[ f'8^\markup { \pad-markup #0.2 "-2"}] ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/41e447bc/lilypond/part_III.ly b/resources/string_quartet_2/41e447bc/lilypond/part_III.ly new file mode 100644 index 0000000..bfe5f3a --- /dev/null +++ b/resources/string_quartet_2/41e447bc/lilypond/part_III.ly @@ -0,0 +1,20 @@ +{ + { r1 } + \bar "|" + { r16[ fis'8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}] ~ fis'2. ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'4 ~ fis'8.[ g'16^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ g'2 ~ } + \bar "|" + { g'2. ~ g'8[ gis'8^\markup { \pad-markup #0.2 "+14"}] ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'4 a'2.^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }} ~ } + \bar "|" + { a'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/41e447bc/lilypond/part_IV.ly b/resources/string_quartet_2/41e447bc/lilypond/part_IV.ly new file mode 100644 index 0000000..0787499 --- /dev/null +++ b/resources/string_quartet_2/41e447bc/lilypond/part_IV.ly @@ -0,0 +1,20 @@ +{ + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/42c3365b/42c3365b_code.scd b/resources/string_quartet_2/42c3365b/42c3365b_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/42c3365b/42c3365b_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/string_quartet_2/42c3365b/42c3365b_mus_model.json b/resources/string_quartet_2/42c3365b/42c3365b_mus_model.json new file mode 100644 index 0000000..710f1b3 --- /dev/null +++ b/resources/string_quartet_2/42c3365b/42c3365b_mus_model.json @@ -0,0 +1,123 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 1.5 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 1.25 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0.5 ], + [ [ [ 1, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0.875 ], + [ [ [ 1, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 1 ] ], 1.25 ], + [ [ [ -1, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 1 ] ], 0 ] + ], + [ + [ [ [ -1, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0.75 ], + [ [ [ -1, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 1.375 ], + [ [ [ 0, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 0.5 ], + [ [ [ 0, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 1.375 ], + [ [ [ 0, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 0.125 ], + [ [ [ -1, 0, 0, 0, 1, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 1 ], + [ [ [ 0, -1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 1.375 ], + [ [ [ -1, 0, 1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 0.5 ] + ], + [ + [ [ [ -1, 0, 1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0.75 ], + [ [ [ -1, 0, 1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ "Rest" ], [ 2, -1, 0, 0, 0, -1 ] ], 0.125 ], + [ [ [ -1, -1, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ "Rest" ], [ 2, -1, 0, 0, 0, -1 ] ], 1.375 ], + [ [ [ -1, -1, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ "Rest" ], [ 2, -1, 0, -1, 0, 0 ] ], 0.125 ] + ], + [ + [ [ [ -1, -1, 0, 1, 0, 0 ], [ 1, -1, 0, -1, 0, 1 ], [ "Rest" ], [ 2, -1, 0, -1, 0, 0 ] ], 0.75 ], + [ [ [ 0, -1, 0, -1, 0, 1 ], [ 1, -1, 0, -1, 0, 1 ], [ "Rest" ], [ 2, -1, 0, -1, 0, 0 ] ], 0 ], + [ [ [ 0, -1, 0, -1, 0, 1 ], [ 2, -1, -1, -1, 0, 0 ], [ "Rest" ], [ 2, -1, 0, -1, 0, 0 ] ], 0.875 ], + [ [ [ 0, 0, 0, -1, 0, 0 ], [ 2, -1, -1, -1, 0, 0 ], [ "Rest" ], [ 2, -1, 0, -1, 0, 0 ] ], 0.125 ], + [ [ [ 0, 0, 0, -1, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ "Rest" ], [ 2, -1, 0, -1, 0, 0 ] ], 0.75 ], + [ [ [ 1, -1, 0, -1, -1, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ "Rest" ], [ 2, -1, 0, -1, 0, 0 ] ], 1.375 ], + [ [ [ 0, -1, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ "Rest" ], [ 2, -1, 0, -1, 0, 0 ] ], 0.25 ] + ], + [ + [ [ [ 0, -1, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ "Rest" ], [ 1, 0, 1, -1, 0, 0 ] ], 1.125 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ "Rest" ], [ 1, 0, 1, -1, 0, 0 ] ], 1.375 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ 0, 0, 0, -1, 0, 1 ], [ 1, 0, 1, -1, 0, 0 ] ], 0.25 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ 1, 0, -1, -1, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ] ], 0.625 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ 1, 0, -1, -1, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0.375 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ 1, 0, -1, -1, 0, 0 ], [ 1, 0, 0, -1, 0, 1 ] ], 0.125 ], + [ [ [ 1, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ 1, 0, -1, -1, 0, 0 ], [ 1, 0, 0, -1, 0, 1 ] ], 1.5 ], + [ [ [ 1, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 0, 0, -1, 0, 1 ] ], 0.875 ] + ], + [ + [ [ [ -1, 2, 0, -1, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 0, 0, -1, 0, 1 ] ], 0.625 ], + [ [ [ -1, 2, 0, -1, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 0, 0 ] ], 0.125 ], + [ [ [ -1, 2, 0, -1, 0, 0 ], [ 0, 1, 1, -1, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 0, 0 ] ], 0.125 ] + ], + [ + [ [ [ -1, 2, 0, -1, 0, 0 ], [ 0, 1, 1, -1, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 2, 0, -1, 0, 0 ] ], 0.375 ], + [ [ [ -1, 2, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 0, -1 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 2, 0, -1, 0, 0 ] ], 0.25 ], + [ [ [ 0, 1, 0, -1, -1, 0 ], [ 1, 1, 0, -1, 0, -1 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 2, 0, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 1, 0, -1, -1, 0 ], [ 1, 1, 0, -1, 0, -1 ], [ 0, 1, 0, -1, 0, 0 ], [ 2, 1, 0, -1, -1, 0 ] ], 1 ], + [ [ [ 0, 1, 0, -1, -1, 0 ], [ 1, 1, 0, -1, 0, -1 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 1, 0 ] ], 0.875 ], + [ [ [ -1, 1, 0, -1, 1, 0 ], [ 1, 1, 0, -1, 0, -1 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 1, 0 ] ], 0.875 ], + [ [ [ 0, 0, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 0, -1 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 1, 0 ] ], 1.25 ], + [ [ [ 0, 1, 0, -1, 0, -1 ], [ 1, 1, 0, -1, 0, -1 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 1, 0 ] ], 0.375 ], + [ [ [ 0, 1, 0, -1, 0, -1 ], [ 0, 1, 1, -1, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 1, 0 ] ], 1.5 ], + [ [ [ 0, 1, 0, -2, 0, 0 ], [ 0, 1, 1, -1, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 1, 0 ] ], 0.375 ], + [ [ [ 0, 1, 0, -2, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 1, 0 ] ], 0.25 ], + [ [ [ 0, 1, 0, -2, 0, 0 ], [ 0, 1, 0, -1, 1, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 1, 0 ] ], 0.875 ] + ], + [ + [ [ [ "Rest" ], [ 0, 1, 0, -1, 1, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 1, 0 ] ], 0.625 ], + [ [ [ "Rest" ], [ 0, 1, 0, -1, 1, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, 0, -1, 1, -1 ] ], 0.5 ], + [ [ [ "Rest" ], [ 0, 1, 0, -1, 1, 0 ], [ -1, 2, 0, -1, 1, 0 ], [ 1, 1, 0, -1, 1, -1 ] ], 1 ], + [ [ [ "Rest" ], [ 0, 1, 0, -1, 1, 0 ], [ 0, 1, -1, -1, 1, 0 ], [ 1, 1, 0, -1, 1, -1 ] ], 1.125 ], + [ [ [ "Rest" ], [ 0, 1, 0, -1, 1, 0 ], [ -1, 1, 0, -1, 1, 1 ], [ 1, 1, 0, -1, 1, -1 ] ], 1.375 ], + [ [ [ "Rest" ], [ 0, 1, 0, -1, 1, 0 ], [ -1, 1, 0, 0, 1, 0 ], [ 1, 1, 0, -1, 1, -1 ] ], 0 ], + [ [ [ "Rest" ], [ 0, 1, 0, -1, 1, 0 ], [ -1, 1, 0, 0, 1, 0 ], [ 0, 1, 1, -1, 1, 0 ] ], 0.125 ], + [ [ [ "Rest" ], [ 0, 1, 0, -1, 1, 0 ], [ -1, 1, 0, 0, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ] ], 1.375 ], + [ [ [ "Rest" ], [ 0, 1, 0, -1, 1, 0 ], [ -1, 2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ] ], 0.875 ], + [ [ [ "Rest" ], [ 0, 1, 0, -1, 1, 0 ], [ 0, 1, -1, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ] ], 0.375 ], + [ [ [ "Rest" ], [ 0, 1, 0, -1, 1, 0 ], [ 0, 1, -1, -1, 1, 0 ], [ 0, 1, 0, -1, 2, 0 ] ], 0.625 ], + [ [ [ "Rest" ], [ 0, 1, 0, -1, 1, 0 ], [ 0, 1, -1, -1, 1, 0 ], [ "Rest" ] ], 0.5 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, -1, 1, 0 ], [ "Rest" ] ], 0.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 5.125 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 1, 0, -2, 0, 0 ], [ 0, 1, 0, -1, 1, 0 ], [ -1, 1, 0, 0, 1, 0 ], [ 0, 1, 1, -1, 1, 0 ] ], + [ [ 0, 1, 0, -2, 0, 0 ], [ 0, 1, 0, -1, 1, 0 ], [ -1, 1, 0, 0, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ] ], + [ [ 0, 1, 0, -2, 0, 0 ], [ 0, 1, 0, -1, 1, 0 ], [ -1, 2, 0, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ] ], + [ [ 0, 1, 0, -2, 0, 0 ], [ 0, 1, 0, -1, 1, 0 ], [ 0, 1, -1, -1, 1, 0 ], [ 1, 0, 0, -1, 1, 0 ] ], + [ [ 0, 1, 0, -2, 0, 0 ], [ 0, 1, 0, -1, 1, 0 ], [ 0, 1, -1, -1, 1, 0 ], [ 0, 1, 0, -1, 2, 0 ] ] +], +"cur_uid": "42c3365b", +"ref_uid": "nil", +"order_seed": 921767, +"dur_seed": 787806, +"motifs_seed": 995213, +"entrances_probs_vals": [ 0, 0, 0, 0, 1.6208791208791, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 0, 1.6208791208791, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 0, 1.6208791208791, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 1 ], [ 0, 3, 0, 3, 0 ], [ 2 ] ], + [ [ 2 ], [ 0, 1, 1, 1, 0, 0, 0 ], [ 3 ] ], + [ [ 1 ], [ 3, 0, 3 ], [ 2 ] ], + [ [ 3 ], [ 1, 0, 1, 0, 1, 0, 0 ], [ 2 ] ], + [ [ 1 ], [ 3, 0, 2, 2, 3, 3, 0, 2 ], [ ] ], + [ [ 2 ], [ 0, 3, 1 ], [ ] ], + [ [ 2 ], [ 3, 1, 0, 3, 3, 0, 0, 0, 1, 0, 1, 1 ], [ ] ], + [ [ 1 ], [ 3, 2, 2, 2, 2, 3, 3, 2, 2, 3 ], [ 0 ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/4660b5c4/4660b5c4_code.scd b/resources/string_quartet_2/4660b5c4/4660b5c4_code.scd new file mode 100644 index 0000000..57e638d --- /dev/null +++ b/resources/string_quartet_2/4660b5c4/4660b5c4_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(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + 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.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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/string_quartet_2/4660b5c4/4660b5c4_mus_model.json b/resources/string_quartet_2/4660b5c4/4660b5c4_mus_model.json new file mode 100644 index 0000000..fba5fcb --- /dev/null +++ b/resources/string_quartet_2/4660b5c4/4660b5c4_mus_model.json @@ -0,0 +1,59 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 4.625 ], + [ [ [ 0, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 3, -2, -2, 1, 0 ] ], 0 ], + [ [ [ 0, 3, -2, -1, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -2, 1, 0 ] ], 0 ], + [ [ [ 0, 3, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -2, 1, 0 ] ], 5.875 ] + ], + [ + [ [ [ 0, 3, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 2, 0 ], [ -1, 3, -2, -2, 1, 0 ] ], 0 ], + [ [ [ 0, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 2, 0 ], [ -1, 3, -2, -2, 1, 0 ] ], 0 ], + [ [ [ 0, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 2, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], 6.625 ] + ], + [ + [ [ [ 0, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ 0, 3, -2, -1, 0, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ 0, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ 0, 3, -2, -1, 0, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ 0, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ 0, 3, -2, -1, 0, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], 4.875 ], + [ [ [ 0, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ "Rest" ], [ -2, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ 0, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ -2, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ 0, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 6.0 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 2, 0 ], [ -1, 3, -2, -2, 1, 0 ] ], + [ [ 0, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 2, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], + [ [ 0, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ 0, 3, -2, -1, 0, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], + [ [ 0, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ 0, 3, -2, -1, 0, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], + [ [ 0, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ 0, 3, -2, -1, 0, 0 ], [ -2, 3, -1, -1, 1, 0 ] ] +], +"cur_uid": "4660b5c4", +"ref_uid": "4c059f33", +"order_seed": 706160, +"dur_seed": 659279, +"motifs_seed": 885545, +"entrances_probs_vals": [ 1, 0, 5.0793650793651, 0.47, 2.8021978021978, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 2.7380952380952, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 2.7380952380952, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8.0714285714286, 10.091836734694 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/4a8a6e53/4a8a6e53_code.scd b/resources/string_quartet_2/4a8a6e53/4a8a6e53_code.scd new file mode 100644 index 0000000..3eb3cbd --- /dev/null +++ b/resources/string_quartet_2/4a8a6e53/4a8a6e53_code.scd @@ -0,0 +1,1058 @@ +( +// 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, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark +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; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file; + file = File(path, "w"); + 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"); +//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((dir +/+ ".." +/+ "resources" +/+ 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((dir +/+ ".." +/+ "resources" +/+ curUID).standardizePath); + File.copy(exPath, (dir +/+ ".." +/+ "resources" +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (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; + }); + 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((dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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 = (dir +/+ ".." +/+ "resources" +/+ 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, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "resources" +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \".." +/+ ".." +/+ "resources" +/+ 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); + +) + +~transcribe.value(~seq, dir); + +( +//synthdefs +~stringModelBusArray = 4.collect({Bus.audio(s, 1)}); +~sineBusArray = 4.collect({Bus.audio(s, 1)}); +~bassBusArray = 1.collect({Bus.audio(s, 1)}); +~hdustBusArray = 1.collect({Bus.audio(s, 1)}); +~samplerBusArray = 2.collect({Bus.audio(s, 1)}); +~sBuf = Buffer.alloc(s, 10, 2); +SynthDef(\string_model, {arg freq, gate = 1, sustain, amp, dur, attack, release = 1, busIndex = 0; + var trig, exc, sig1, sig2, noHarms; + noHarms = rrand(20, 40); + exc = Saw.ar(freq, TRand.ar(0.5, 1, Impulse.ar(freq))) * 0.001 + Dust.ar(10000, 0.01); + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(1, 2)}) ], exc) * 0.8).softclip; + //sig1 = HPF.ar(sig1, 300); + Out.ar(Select.kr(busIndex, ~stringModelBusArray), sig1 * amp * EnvGen.kr(Env.adsr(attack, 0.3, 0.9, release, 0.9, -3), gate, doneAction: 2)); + //Out.ar([0, 1], sig1 * EnvGen.kr(Env.asr(dur, 0.3, 1), gate, doneAction: 2)); +}).add; + +SynthDef(\sine, {arg freq, gate = 1, sustain, amp, dur, busIndex = 0; + var sig; + sig = SinOsc.ar(freq); + Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.asr(0.3, 0.4, 0.3), gate, timeScale: dur, doneAction: 2)); + //Out.ar(Select.kr(busIndex, ~sineBusArray), sig * EnvGen.kr(Env.sine(dur), gate, doneAction: 2)); +}).add; + +SynthDef(\mixer, {arg freq, gate = 1, sustain, amp, dur, out; + var nameSpaces, sigs; + + sigs = [~stringModelBusArray, ~sineBusArray/*, ~bassBusArray, ~hdustBusArray, ~samplerBusArray*/].collect({arg busArray, i; + var nameSpace, sig; + nameSpace = ['string', 'sine', 'bass', 'hdust', 'sampler'][i]; + sig = busArray.collect({arg bus, c; In.ar(bus, 1) * NamedControl.kr(\ ++ nameSpace ++ '_volume_' ++ c, 1, 0.1)}); + sig = sig.collect({arg channel, c; Pan2.ar(channel, NamedControl.kr(\ ++ nameSpace ++ '_pan_' ++ c, i / (busArray.size - 1), 0.1) * 2 - 1)}); + sig = sig.collect({arg channel, c; channel * NamedControl.kr(\ ++ nameSpace ++ '_mute_' ++ c, 1, 0.1)}); + sig = Mix.ar(sig) * pow(NamedControl.kr(\ ++ nameSpace ++ '_volume_master', 1, 0.1), 2); + }); + + sigs = Mix.ar(sigs / 4); + Out.ar(0, sigs) +}).add; + +SynthDef(\bass, { + var switches, drone; + switches = {|i| Dust.kr(0.1)} ! 9; + drone = {|i| var harm = pow(2, 2 - (i / 3).trunc), amp = (1 / pow(harm, 2)); + SinOsc.ar(60 * harm + TRand.kr(-3, 3, switches[i]), 0, amp)} ! 9; + Out.ar(~bassBusArray[0], Mix.new(drone) * 0.2); +}).add; + +SynthDef(\sampler, { + Out.ar(~samplerBusArray, PlayBuf.ar(2, ~sBuf, BufRateScale.kr(~sBuf), doneAction: 2)) +}).add; + +// main routine +SynthDef(\hdust, { + arg gate = 0; + var hierarchical_dust, low_sine, high_sine, brown_noise, white_noise; + // this triggers the combinations of sources + // it is similar to the Supercollider UGen called dust but with a hierarchical structure + hierarchical_dust = ( + TIRand.kr(0, 1, Impulse.kr(100)) * + TIRand.kr(0, 1, Impulse.kr(10)) * + TIRand.kr(0, 1, Impulse.kr(1)) * + TIRand.kr(0, 1, Impulse.kr(0.1)) + ); + // adjust the multiplier at the end of each line for adjusting levels + // note with each trigger, each source has a 1 in 3 chance of sounding + low_sine = SinOsc.ar(76.midicps / 16) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.1; + high_sine = SinOsc.ar(76.midicps * 8) * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.01; + brown_noise = BrownNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.025; + white_noise = WhiteNoise.ar() * (TIRand.kr(0, 2, hierarchical_dust) < 1) * 0.02; + Out.ar(~hdustBusArray[0], + ((low_sine + high_sine + brown_noise + white_noise) ) + ); +}).add; + +) + +( +var bass, hdust, sampler, mixer; +/* +bass = Synth.tail(~group, \bass); +hdust = Synth.tail(~group, \hdust); +sampler = Synth.head(~group, \sampler); +*/ +mixer = Synth.tail(~group, \mixer); + +OSCdef(\mixer, {arg msg, time, addr, port; + mixer.set((msg[1] ++ '_' ++ msg[2] ++ '_' ++ msg[3]), msg[4]) +}, \mixer); + +/* +OSCdef(\sampler, {arg msg, time, addr, port; + msg.postln; + sampler.free; + ~sBuf.free; + ~sBuf = Buffer.read(s, msg[1].asString.postln, action: {sampler = Synth.head(~group, \sampler)}); +}, \sampler); +*/ +) + +/* old something +( +SynthDef(\test, {arg freq, gate = 1, sustain, amp, dur; + var trig, exc, sig1, sig2, noHarms, freqFinal, start, end; + noHarms = 30; + freq = WhiteNoise.ar * 3 + freq; + freqFinal = Duty.ar((1/freq), 0, freq); + trig = Changed.ar(freqFinal); + start = Demand.ar(trig, 0, Dwhite(-1, -0.75)); + end = Demand.ar(trig, 0, Dwhite(0.75, 1)); + exc = Phasor.ar(trig, (end - start) * freqFinal / SampleRate.ir, start, end, 0) * 0.001 + Dust.ar(10000, 0.01); + + sig1 = (Klank.ar(`[ Array.series(noHarms, freq, freq), + Array.geom(noHarms, 1, 0.2) + Array.fill(noHarms, {rrand(0.01, 0.03)}), + Array.fill(noHarms, {rrand(2, 3)}) ], exc) * 0.5).softclip; + sig1 = HPF.ar(sig1, 300); + Out.ar([0, 1], sig1 * EnvGen.kr(Env.adsr(0.3, 0.3, 0.9, 0.5, 0.9), gate, doneAction: 2)); +}).add; +) +*/ + diff --git a/resources/string_quartet_2/4a8a6e53/4a8a6e53_mus_model.json b/resources/string_quartet_2/4a8a6e53/4a8a6e53_mus_model.json new file mode 100644 index 0000000..150baf5 --- /dev/null +++ b/resources/string_quartet_2/4a8a6e53/4a8a6e53_mus_model.json @@ -0,0 +1,97 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 0.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 0.625 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 1.5 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 0.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 1.25 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ] ], 0.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ] ], 0.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1.25 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0.75 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 0.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ], [ "Rest" ] ], 1.5 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 1, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ "Rest" ] ], 1.25 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ "Rest" ] ], 1.375 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ "Rest" ] ], 0.625 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 0, 1 ], [ "Rest" ] ], 1.125 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ] ], 1.25 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ "Rest" ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ "Rest" ] ], 0.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, -1, 0, -1, 0 ] ], 0.875 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, -1, 0, -1, 0 ] ], 0.75 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 1, -1, 0 ], [ 1, 0, -1, 0, -1, 0 ] ], 1.5 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 1, -1, 0 ], [ 0, 0, 0, 0, -1, 1 ] ], 1.25 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 1, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 0.625 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 0.625 ], + [ [ [ 0, 0, 1, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 0.625 ], + [ [ [ 1, 0, 0, -1, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 1.375 ], + [ [ [ 1, 0, 0, -1, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 1, 0, -1, 0 ] ], 1.375 ], + [ [ [ 1, 0, 0, -1, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 2, 0, 0, -1, -1, 0 ], [ 0, 0, 1, 0, -1, 0 ] ], 1.5 ], + [ [ [ 1, 0, 0, -1, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 2, 0, 0, 0, -1, -1 ], [ 0, 0, 1, 0, -1, 0 ] ], 0.75 ], + [ [ [ 1, 0, 0, -1, -1, 0 ], [ 0, 0, 0, 0, -1, 0 ], [ 2, 0, 0, 0, -1, -1 ], [ 1, -1, 0, 0, -1, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, 0, -1, -1, 0 ], [ "Rest" ], [ 2, 0, 0, 0, -1, -1 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.75 ], + [ [ [ 0, -1, 0, 1, -1, 0 ], [ "Rest" ], [ 2, 0, 0, 0, -1, -1 ], [ 1, -1, 0, 0, -1, 0 ] ], 1 ], + [ [ [ 0, -1, 0, 1, -1, 0 ], [ "Rest" ], [ 2, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.875 ], + [ [ [ 0, -1, 0, 0, -1, 1 ], [ "Rest" ], [ 2, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 1.25 ], + [ [ [ 0, -1, 0, 0, -1, 1 ], [ "Rest" ], [ 1, -1, 0, 1, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 1.125 ], + [ [ [ 1, -1, -1, 0, -1, 0 ], [ "Rest" ], [ 1, -1, 0, 1, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 1 ], + [ [ [ 1, -1, -1, 0, -1, 0 ], [ "Rest" ], [ 1, 0, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.75 ], + [ [ [ 1, -1, -1, 0, -1, 0 ], [ "Rest" ], [ 1, -1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.625 ], + [ [ [ 0, -1, 0, 0, 0, 0 ], [ "Rest" ], [ 1, -1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.75 ], + [ [ [ 1, -1, 0, 0, -2, 0 ], [ "Rest" ], [ 1, -1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 1.375 ], + [ [ [ 1, -1, 0, 0, -2, 0 ], [ "Rest" ], [ 2, -2, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 0.875 ], + [ [ [ 0, -1, 0, 0, -1, 1 ], [ "Rest" ], [ 2, -2, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 3.375 ], + [ [ [ 0, -1, 0, 0, -1, 1 ], [ "Rest" ], [ 2, -2, 0, 0, -1, 0 ], [ "Rest" ] ], 1.5 ], + [ [ [ 0, -1, 0, 0, -1, 1 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1.375 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 2 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 1, 0, -1 ], [ 0, 0, 0, 0, 0, 0 ], [ 2, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, -2 ] ], + [ [ 0, 0, 0, 1, 0, -1 ], [ 0, 0, 0, 0, 0, 0 ], [ 2, 0, 0, 0, 0, -1 ], [ -1, 0, 0, 1, 0, -1 ] ], + [ [ 0, 0, 0, 1, 0, -1 ], [ 0, 0, 0, 0, 0, 0 ], [ 2, 0, 0, 0, 0, -1 ], [ 0, 0, 0, 0, -1, 0 ] ], + [ [ 0, 0, 0, 1, 0, -1 ], [ 0, 0, 0, 0, 0, 0 ], [ 2, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ] ], + [ [ 1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 2, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, -1, 0 ] ] +], +"cur_uid": "4a8a6e53", +"ref_uid": "nil", +"order_seed": 320463, +"dur_seed": 903977, +"motifs_seed": 895384, +"entrances_probs_vals": [ 0.75, 0, 10, 0, 0.5, 0.26424870466321, 0.75675675675676, 0.5, 0.5, 0.58549222797927, 0.72635135135135, 1, 0.5 ], +"passages_probs_vals": [ 0.75, 0, 10, 0, 0.5, 0.20725388601036, 0.68581081081081, 0.24093264248705, 0.34121621621622, 0.5, 0.5, 0.67616580310881, 0.81081081081081, 1, 0.5 ], +"exits_probs_vals": [ 0.75, 0, 10, 0, 0.5, 0.20725388601036, 0.68581081081081, 0.24093264248705, 0.34121621621622, 0.5, 0.5, 0.67616580310881, 0.81081081081081, 1, 0.5 ], +"ranges": [ [ -384, 2400 ], [ -507, 2400 ], [ -282, 2237 ], [ -1200, 2053 ] ], +"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 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 1 ], [ 0, 3, 2, 3, 3, 2 ], [ ] ], + [ [ 2, 1 ], [ 3, 0, 3 ], [ ] ], + [ [ 1 ], [ 3, 2, 0 ], [ ] ] +], +"sus_weights": [ 0.75, 0.69, 0.75 ], +"order_size": [ 2, 6 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/4a8a6e53/lilypond/part_I.ly b/resources/string_quartet_2/4a8a6e53/lilypond/part_I.ly new file mode 100644 index 0000000..ce99136 --- /dev/null +++ b/resources/string_quartet_2/4a8a6e53/lilypond/part_I.ly @@ -0,0 +1,56 @@ +{ \numericTimeSignature + { r2. e'4^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }} ~ } + \bar "|" + { e'2 e'4^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ e'8.[ f'16^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { f'4 ~ f'8[ g'8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ g'4 ~ g'8[ ais'8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { ais'4 ~ ais'8[ c''8^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ c''2 } + \bar "|" + { gis'4^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }} ~ gis'8.[ gis'16^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ gis'4 ~ gis'8[ f'8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { f'2 e'4^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ e'8[ r8] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 r16[ dis'8.^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}] ~ dis'4 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'8[ dis'8^\markup { \pad-markup #0.2 "-11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ dis'2 d'4^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'4 ~ d'8[ ais8^\markup { \pad-markup #0.2 "+35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] ~ ais2 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais8.[ b16^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }}] ~ b2. ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2 ~ b16[ r8.] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/4a8a6e53/lilypond/part_II.ly b/resources/string_quartet_2/4a8a6e53/lilypond/part_II.ly new file mode 100644 index 0000000..3c00ff8 --- /dev/null +++ b/resources/string_quartet_2/4a8a6e53/lilypond/part_II.ly @@ -0,0 +1,56 @@ +{ + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'4 ~ c'16[ e'8.^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ e'2 ~ } + \bar "|" + { e'2 ~ e'8.[ g'16^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ g'4 ~ } + \bar "|" + { g'4 ~ g'8[ ais'8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ ais'2 ~ } + \bar "|" + { ais'4 ~ ais'8.[ gis'16^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}] ~ gis'2 } + \bar "|" + { gis'2^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ gis'8[ g'8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ g'4 ~ } + \bar "|" + { g'8[ f'8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ f'2. ~ } + \bar "|" + { f'4 ~ f'8[ e'8^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}] ~ e'2 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'16[ fis'8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ fis'2. ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'16[ a'8.^\markup { \pad-markup #0.2 "-20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ a'2 ~ a'16[ ais'8.^\markup { \pad-markup #0.2 "+8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'2 ~ ais'16[ b'8.^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ~ b'4 ~ } + \bar "|" + { b'2 ~ b'8[ a'8^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}] ~ a'4 ~ } + \bar "|" + { a'2 ~ a'8.[ fis'16^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ fis'4 ~ } + \bar "|" + { fis'16[ f'8.^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}] ~ f'2. ~ } + \bar "|" + { f'4 ~ f'8.[ e'16^\markup { \pad-markup #0.2 "+45"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }}] ~ e'2 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'4 ~ e'16[ r8.] r2 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/4a8a6e53/lilypond/part_III.ly b/resources/string_quartet_2/4a8a6e53/lilypond/part_III.ly new file mode 100644 index 0000000..75dcb1c --- /dev/null +++ b/resources/string_quartet_2/4a8a6e53/lilypond/part_III.ly @@ -0,0 +1,56 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r16[ ais8.^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ ais2. ~ } + \bar "|" + { ais2. gis4^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { gis16[ fis8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}] ~ fis2. ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis2 ~ fis8.[ r16] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \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/string_quartet_2/4a8a6e53/lilypond/part_IV.ly b/resources/string_quartet_2/4a8a6e53/lilypond/part_IV.ly new file mode 100644 index 0000000..41471e8 --- /dev/null +++ b/resources/string_quartet_2/4a8a6e53/lilypond/part_IV.ly @@ -0,0 +1,56 @@ +{ + { r4 r8.[ c'16^\markup { \pad-markup #0.2 "+0"}] ~ c'2 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 } + \bar "|" + { b1^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b4 ~ b8[ ais8^\markup { \pad-markup #0.2 "+35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] ~ ais8.[ a16^\markup { \pad-markup #0.2 "-20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }}] ~ a4 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a16[ a8.^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}] ~ a2. } + \bar "|" + { gis1^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }} ~ } + \bar "|" + { gis8.[ gis16^\markup { \pad-markup #0.2 "-40"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}] ~ gis2. ~ } + \bar "|" + { gis4 ~ gis8[ f8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ f4 fis4^\markup { \pad-markup #0.2 "-5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }} ~ } + \bar "|" + { fis2. ~ fis8[ gis8^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ~ } + \bar "|" + { gis1 ~ } + \bar "|" + { gis1 ~ } + \bar "|" + { gis1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/4c059f33/4c059f33_code.scd b/resources/string_quartet_2/4c059f33/4c059f33_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/4c059f33/4c059f33_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/string_quartet_2/4c059f33/4c059f33_mus_model.json b/resources/string_quartet_2/4c059f33/4c059f33_mus_model.json new file mode 100644 index 0000000..fd5c9df --- /dev/null +++ b/resources/string_quartet_2/4c059f33/4c059f33_mus_model.json @@ -0,0 +1,58 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 3, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ -2, 3, -1, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ] ], 4.875 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -1, 3, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -1, 3, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -2, 3, -1, -1, 1, 0 ] ], 4.75 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], 4.125 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ "Rest" ], [ -1, 3, -3, -1, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ "Rest" ], [ -1, 3, -3, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 2, 0 ], [ 0, 3, -1, -1, 1, 0 ], [ -1, 4, -2, -1, 1, 0 ] ], + [ [ 0, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 2, 0 ], [ 0, 3, -1, -1, 1, 0 ], [ 0, 3, -3, -1, 1, 0 ] ], + [ [ 0, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 2, 0 ], [ 1, 3, -2, -1, 1, -1 ], [ 0, 3, -3, -1, 1, 0 ] ], + [ [ 0, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 2, 0 ], [ 1, 3, -2, -1, 1, -1 ], [ -1, 3, -2, -1, 1, 1 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -1, 3, -2, -1, 1, 1 ] ] +], +"cur_uid": "4c059f33", +"ref_uid": "628706ec", +"order_seed": 706160, +"dur_seed": 146047, +"motifs_seed": 575591, +"entrances_probs_vals": [ 1, 0, 0, 0.47, 2.8021978021978, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0.77, 0, 0, 0, 1.6208791208791, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/4c059f33/lilypond/part_I.ly b/resources/string_quartet_2/4c059f33/lilypond/part_I.ly new file mode 100644 index 0000000..e46bd01 --- /dev/null +++ b/resources/string_quartet_2/4c059f33/lilypond/part_I.ly @@ -0,0 +1,16 @@ +{ + { r2 a2^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a2. ~ a8.[ cis16^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis4 ~ cis16[ cis8.^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ cis2 ~ } + \bar "|" + { cis1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/4c059f33/lilypond/part_II.ly b/resources/string_quartet_2/4c059f33/lilypond/part_II.ly new file mode 100644 index 0000000..c17645f --- /dev/null +++ b/resources/string_quartet_2/4c059f33/lilypond/part_II.ly @@ -0,0 +1,16 @@ +{ + { r2 cis2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis2. ~ cis8.[ cis16^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis4 ~ cis16[ d8.^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ d2 ~ } + \bar "|" + { d1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/4c059f33/lilypond/part_III.ly b/resources/string_quartet_2/4c059f33/lilypond/part_III.ly new file mode 100644 index 0000000..f07edd8 --- /dev/null +++ b/resources/string_quartet_2/4c059f33/lilypond/part_III.ly @@ -0,0 +1,16 @@ +{ + { r2 e2^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e2. ~ e8.[ e16^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e4 ~ e16[ f8.^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ f2 ~ } + \bar "|" + { f1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/4c059f33/lilypond/part_IV.ly b/resources/string_quartet_2/4c059f33/lilypond/part_IV.ly new file mode 100644 index 0000000..4db8109 --- /dev/null +++ b/resources/string_quartet_2/4c059f33/lilypond/part_IV.ly @@ -0,0 +1,16 @@ +{ + { a,1^\markup { \pad-markup #0.2 "+16"} ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/5201b8af/5201b8af_code.scd b/resources/string_quartet_2/5201b8af/5201b8af_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/5201b8af/5201b8af_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/string_quartet_2/5201b8af/5201b8af_mus_model.json b/resources/string_quartet_2/5201b8af/5201b8af_mus_model.json new file mode 100644 index 0000000..aa90760 --- /dev/null +++ b/resources/string_quartet_2/5201b8af/5201b8af_mus_model.json @@ -0,0 +1,58 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 4.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], 4.75 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 4.125 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ "Rest" ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ "Rest" ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ] +], +"cur_uid": "5201b8af", +"ref_uid": "77b0d2dc", +"order_seed": 921767, +"dur_seed": 954688, +"motifs_seed": 995213, +"entrances_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/5201b8af/lilypond/part_I.ly b/resources/string_quartet_2/5201b8af/lilypond/part_I.ly new file mode 100644 index 0000000..01ab702 --- /dev/null +++ b/resources/string_quartet_2/5201b8af/lilypond/part_I.ly @@ -0,0 +1,16 @@ +{ + { r2 c''2^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''2. ~ c''8.[ e'16^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'4 ~ e'16[ e'8.^\markup { \pad-markup #0.2 "-41"}] ~ e'2 ~ } + \bar "|" + { e'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/5201b8af/lilypond/part_II.ly b/resources/string_quartet_2/5201b8af/lilypond/part_II.ly new file mode 100644 index 0000000..f176e75 --- /dev/null +++ b/resources/string_quartet_2/5201b8af/lilypond/part_II.ly @@ -0,0 +1,16 @@ +{ + { r2 e'2^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'2. ~ e'8.[ e'16^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'4 ~ e'16[ f'8.^\markup { \pad-markup #0.2 "-2"}] ~ f'2 ~ } + \bar "|" + { f'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/5201b8af/lilypond/part_III.ly b/resources/string_quartet_2/5201b8af/lilypond/part_III.ly new file mode 100644 index 0000000..5f83063 --- /dev/null +++ b/resources/string_quartet_2/5201b8af/lilypond/part_III.ly @@ -0,0 +1,16 @@ +{ + { r2 fis'2^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'2. ~ fis'8.[ g'16^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'4 ~ g'16[ gis'8.^\markup { \pad-markup #0.2 "+14"}] ~ gis'2 ~ } + \bar "|" + { gis'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/5201b8af/lilypond/part_IV.ly b/resources/string_quartet_2/5201b8af/lilypond/part_IV.ly new file mode 100644 index 0000000..67fc5d0 --- /dev/null +++ b/resources/string_quartet_2/5201b8af/lilypond/part_IV.ly @@ -0,0 +1,16 @@ +{ + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/525274f2/525274f2_code.scd b/resources/string_quartet_2/525274f2/525274f2_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/525274f2/525274f2_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/string_quartet_2/525274f2/525274f2_mus_model.json b/resources/string_quartet_2/525274f2/525274f2_mus_model.json new file mode 100644 index 0000000..a8be70b --- /dev/null +++ b/resources/string_quartet_2/525274f2/525274f2_mus_model.json @@ -0,0 +1,66 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 3.375 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], 1.25 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 3.125 ] + ], + [ + [ [ [ 1, 0, -1, 0, 0, -1 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 1, 0, -1, 0, 0, -1 ], [ 1, 0, 1, 0, 0, -1 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 2.25 ] + ], + [ + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 1, 0, 1, 0, 0, -1 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 2.625 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ "Rest" ] ], 0 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 2, -1, 0, -1, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ] +], +"cur_uid": "525274f2", +"ref_uid": "77b0d2dc", +"order_seed": 921767, +"dur_seed": 954688, +"motifs_seed": 995213, +"entrances_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/525274f2/lilypond/part_I.ly b/resources/string_quartet_2/525274f2/lilypond/part_I.ly new file mode 100644 index 0000000..a568867 --- /dev/null +++ b/resources/string_quartet_2/525274f2/lilypond/part_I.ly @@ -0,0 +1,14 @@ +{ + { r2 c''2^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''8.[ e'16^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ e'2 ~ e'16[ e'8.^\markup { \pad-markup #0.2 "-41"}] ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/525274f2/lilypond/part_II.ly b/resources/string_quartet_2/525274f2/lilypond/part_II.ly new file mode 100644 index 0000000..9a7f7ed --- /dev/null +++ b/resources/string_quartet_2/525274f2/lilypond/part_II.ly @@ -0,0 +1,14 @@ +{ + { r2 e'2^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'8.[ e'16^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ e'2 ~ e'16[ f'8.^\markup { \pad-markup #0.2 "-2"}] ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/525274f2/lilypond/part_III.ly b/resources/string_quartet_2/525274f2/lilypond/part_III.ly new file mode 100644 index 0000000..ac151b0 --- /dev/null +++ b/resources/string_quartet_2/525274f2/lilypond/part_III.ly @@ -0,0 +1,14 @@ +{ + { r2 fis'2^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'8.[ g'16^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ g'2 ~ g'16[ gis'8.^\markup { \pad-markup #0.2 "+14"}] ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'4 ~ gis'8[ g'8^\markup { \pad-markup #0.2 "+46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}] ~ g'2 ~ } + \bar "|" + { g'2 g'2^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/525274f2/lilypond/part_IV.ly b/resources/string_quartet_2/525274f2/lilypond/part_IV.ly new file mode 100644 index 0000000..e646018 --- /dev/null +++ b/resources/string_quartet_2/525274f2/lilypond/part_IV.ly @@ -0,0 +1,14 @@ +{ + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'4 ~ c'8[ c'8^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }}] ~ c'2 ~ } + \bar "|" + { c'2 b2^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/60adbbef/60adbbef_code.scd b/resources/string_quartet_2/60adbbef/60adbbef_code.scd new file mode 100644 index 0000000..57e638d --- /dev/null +++ b/resources/string_quartet_2/60adbbef/60adbbef_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(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + 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.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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/string_quartet_2/60adbbef/60adbbef_mus_model.json b/resources/string_quartet_2/60adbbef/60adbbef_mus_model.json new file mode 100644 index 0000000..748c135 --- /dev/null +++ b/resources/string_quartet_2/60adbbef/60adbbef_mus_model.json @@ -0,0 +1,65 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.75 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ -2, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 4.75 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 2, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 2, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ] ], 7.125 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -2, 3, -2, -1, 2, 0 ], [ -1, 3, -3, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -2, 3, -2, -1, 2, 0 ], [ 0, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -2, 3, -2, -1, 2, 0 ], [ 0, 3, -2, -1, 1, -1 ] ], 4.5 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ 0, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ -1, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ -1, 3, -1, -1, 1, 0 ] ], 6.875 ], + [ [ [ "Rest" ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ -1, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ "Rest" ], [ -1, 3, -2, -1, 1, 0 ], [ "Rest" ], [ -1, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ -1, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 10.75 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -2, 3, -2, -1, 2, 0 ], [ 0, 3, -2, -1, 1, -1 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -2, 3, -2, -1, 2, 0 ], [ 0, 3, -2, -1, 1, -1 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ 0, 3, -2, -1, 1, -1 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ -1, 3, -1, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ -1, 3, -1, -1, 1, 0 ] ] +], +"cur_uid": "60adbbef", +"ref_uid": "4c059f33", +"order_seed": 824152, +"dur_seed": 266394, +"motifs_seed": 206072, +"entrances_probs_vals": [ 1, 0, 5.0793650793651, 0.47, 2.8021978021978, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 2.7380952380952, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 2.7380952380952, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8.0714285714286, 10.091836734694 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/60adbbef/lilypond/part_I.ly b/resources/string_quartet_2/60adbbef/lilypond/part_I.ly new file mode 100644 index 0000000..708ce11 --- /dev/null +++ b/resources/string_quartet_2/60adbbef/lilypond/part_I.ly @@ -0,0 +1,38 @@ +{ + { r1 } + \bar "|" + { r2. r8[ e8^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e4 f2.^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f2. ~ f16[ cis'8.^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'16[ cis'8.^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ cis'2. ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'2 r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/60adbbef/lilypond/part_II.ly b/resources/string_quartet_2/60adbbef/lilypond/part_II.ly new file mode 100644 index 0000000..7fea40e --- /dev/null +++ b/resources/string_quartet_2/60adbbef/lilypond/part_II.ly @@ -0,0 +1,38 @@ +{ + { r1 } + \bar "|" + { r2. r8[ cis8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis4 d2.^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d2. ~ d16[ dis8.^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis16[ e8.^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}] ~ e2. ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e2 r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/60adbbef/lilypond/part_III.ly b/resources/string_quartet_2/60adbbef/lilypond/part_III.ly new file mode 100644 index 0000000..dde5fb8 --- /dev/null +++ b/resources/string_quartet_2/60adbbef/lilypond/part_III.ly @@ -0,0 +1,38 @@ +{ + { r1 } + \bar "|" + { r2. r8[ a8^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a4 fis2.^\markup { \pad-markup #0.2 "-44"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }} ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis2. ~ fis16[ g8.^\markup { \pad-markup #0.2 "-15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g16[ a8.^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ a2. ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a2 r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/60adbbef/lilypond/part_IV.ly b/resources/string_quartet_2/60adbbef/lilypond/part_IV.ly new file mode 100644 index 0000000..98319b9 --- /dev/null +++ b/resources/string_quartet_2/60adbbef/lilypond/part_IV.ly @@ -0,0 +1,38 @@ +{ + { a,1^\markup { \pad-markup #0.2 "+16"} ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,2 r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/61e92979/61e92979_code.scd b/resources/string_quartet_2/61e92979/61e92979_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/61e92979/61e92979_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/string_quartet_2/61e92979/61e92979_mus_model.json b/resources/string_quartet_2/61e92979/61e92979_mus_model.json new file mode 100644 index 0000000..413129a --- /dev/null +++ b/resources/string_quartet_2/61e92979/61e92979_mus_model.json @@ -0,0 +1,67 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 3.375 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], 1.25 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 3.125 ] + ], + [ + [ [ [ 1, 0, -1, 0, 0, -1 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 1, 0, -1, 0, 0, -1 ], [ 1, 0, 1, 0, 0, -1 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 2.25 ] + ], + [ + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 1, 0, 1, 0, 0, -1 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 2.625 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ "Rest" ] ], 0 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 2, -1, 0, -1, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 10 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ] +], +"cur_uid": "61e92979", +"ref_uid": "nil", +"order_seed": 921767, +"dur_seed": 954688, +"motifs_seed": 995213, +"entrances_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/62300302/62300302_code.scd b/resources/string_quartet_2/62300302/62300302_code.scd new file mode 100644 index 0000000..57e638d --- /dev/null +++ b/resources/string_quartet_2/62300302/62300302_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(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + 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.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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/string_quartet_2/62300302/62300302_mus_model.json b/resources/string_quartet_2/62300302/62300302_mus_model.json new file mode 100644 index 0000000..dfaef7d --- /dev/null +++ b/resources/string_quartet_2/62300302/62300302_mus_model.json @@ -0,0 +1,77 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 2.25 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ -2, 3, -2, 0, 1, 0 ] ], 1.5 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ 0, 2, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ] ], 1.875 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 2, 0 ], [ 0, 2, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ] ], 2.625 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 2, 0 ], [ -1, 3, -2, -1, 2, 0 ], [ -2, 3, -2, 0, 1, 0 ] ], 1.25 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ -1, 3, -2, -1, 2, 0 ], [ -2, 3, -2, 0, 1, 0 ] ], 1.5 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ -1, 3, -2, -1, 2, 0 ], [ -1, 3, -2, -2, 1, 0 ] ], 2.25 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ 0, 3, -2, -1, 0, 0 ], [ -1, 3, -2, -2, 1, 0 ] ], 1.5 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ 0, 3, -2, -1, 0, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], 2 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ 0, 3, -2, -1, 0, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], 1.75 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ 0, 3, -2, -2, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], 1.375 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ 0, 3, -2, -2, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 2.125 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ 0, 3, -2, -2, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 1.125 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ 0, 3, -2, -1, 1, -1 ], [ -1, 2, -2, -1, 1, 0 ] ], 2.125 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ 0, 3, -2, -1, 1, -1 ], [ -1, 2, -2, -1, 1, 0 ] ], 1.75 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ 0, 3, -2, -1, 1, -1 ], [ -2, 3, -2, -1, 2, 0 ] ], 2 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ 0, 3, -2, -1, 1, -1 ], [ -1, 3, -2, -1, 0, 0 ] ], 1 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ], 1 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ], 1.5 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 0, 0 ] ], 1 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 1.375 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ 0, 3, -2, -1, 1, -1 ], [ -1, 2, -2, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ 0, 3, -2, -1, 1, -1 ], [ -2, 3, -2, -1, 2, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ 0, 3, -2, -1, 1, -1 ], [ -1, 3, -2, -1, 0, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ] +], +"cur_uid": "62300302", +"ref_uid": "74b8f8d9", +"order_seed": 799630, +"dur_seed": 442647, +"motifs_seed": 452591, +"entrances_probs_vals": [ 1, 0, 2.9365079365079, 0.47, 2.8021978021978, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 2.7380952380952, 0.96, 2.4725274725275, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 2.7380952380952, 0.96, 2.4725274725275, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8.0714285714286, 10.091836734694 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/62300302/lilypond/part_I.ly b/resources/string_quartet_2/62300302/lilypond/part_I.ly new file mode 100644 index 0000000..f4f96ba --- /dev/null +++ b/resources/string_quartet_2/62300302/lilypond/part_I.ly @@ -0,0 +1,36 @@ +{ + { r1 } + \bar "|" + { r8[ g8^\markup { \pad-markup #0.2 "-15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ g2. ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g2 b,2^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }} ~ } + \bar "|" + { b,1 ~ } + \bar "|" + { b,4 ~ b,8[ cis8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ cis2 ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis2. ~ cis8.[ d16^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d2 dis2^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }} ~ } + \bar "|" + { dis2 e2^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e2. r4} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/62300302/lilypond/part_II.ly b/resources/string_quartet_2/62300302/lilypond/part_II.ly new file mode 100644 index 0000000..158f676 --- /dev/null +++ b/resources/string_quartet_2/62300302/lilypond/part_II.ly @@ -0,0 +1,36 @@ +{ + { r1 } + \bar "|" + { r2. r8[ d'8^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'8[ dis'8^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ dis'2. ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'2 ~ dis'8[ e'8^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ e'4 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'4 b2.^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2 ~ b16[ cis'8.^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ cis'4 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 } + \bar "|" + { d'1^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { d'4 r2. } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/62300302/lilypond/part_III.ly b/resources/string_quartet_2/62300302/lilypond/part_III.ly new file mode 100644 index 0000000..cebac3f --- /dev/null +++ b/resources/string_quartet_2/62300302/lilypond/part_III.ly @@ -0,0 +1,36 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. r16[ dis8.^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis2. e4^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e4 ~ e8[ e8^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ e2 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 } + \bar "|" + { fis1^\markup { \pad-markup #0.2 "-44"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }} ~ } + \bar "|" + { fis2 ~ fis8[ g8^\markup { \pad-markup #0.2 "-15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ g4 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g2 a2^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { a1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/62300302/lilypond/part_IV.ly b/resources/string_quartet_2/62300302/lilypond/part_IV.ly new file mode 100644 index 0000000..19d2217 --- /dev/null +++ b/resources/string_quartet_2/62300302/lilypond/part_IV.ly @@ -0,0 +1,36 @@ +{ + { a,1^\markup { \pad-markup #0.2 "+16"} ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/628706ec/628706ec_code.scd b/resources/string_quartet_2/628706ec/628706ec_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/628706ec/628706ec_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/string_quartet_2/628706ec/628706ec_mus_model.json b/resources/string_quartet_2/628706ec/628706ec_mus_model.json new file mode 100644 index 0000000..7226160 --- /dev/null +++ b/resources/string_quartet_2/628706ec/628706ec_mus_model.json @@ -0,0 +1,117 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 1.5 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 1.25 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 2, -1, 0, 0, 0 ] ], 0.5 ], + [ [ [ 1, 2, -1, -1, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 2, -1, 0, 0, 0 ] ], 0.875 ], + [ [ [ 1, 2, -1, -1, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ], [ 0, 2, -1, 0, 0, 1 ] ], 1.25 ], + [ [ [ -1, 3, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ], [ 0, 2, -1, 0, 0, 1 ] ], 0 ] + ], + [ + [ [ [ -1, 3, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0.75 ], + [ [ [ -1, 3, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ] ], 1.375 ], + [ [ [ 0, 2, -1, 0, -1, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ] ], 0.5 ], + [ [ [ 0, 2, -1, 0, -1, 0 ], [ 1, 2, -1, 0, -1, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ] ], 1.375 ], + [ [ [ 0, 2, -1, 0, -1, 0 ], [ 0, 2, -1, 0, 1, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ] ], 0.75 ], + [ [ [ 0, 2, -1, 0, -1, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ] ], 0.125 ], + [ [ [ -1, 2, -1, 0, 1, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ] ], 1 ], + [ [ [ 0, 1, -1, 0, 0, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ] ], 1.375 ], + [ [ [ -1, 2, 0, 0, 0, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ] ], 0.5 ] + ], + [ + [ [ [ -1, 2, 0, 0, 0, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0.75 ], + [ [ [ -1, 2, 0, 0, 0, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ "Rest" ], [ 2, 1, -1, 0, 0, -1 ] ], 0.125 ], + [ [ [ -1, 1, -1, 1, 0, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ "Rest" ], [ 2, 1, -1, 0, 0, -1 ] ], 1.375 ], + [ [ [ -1, 1, -1, 1, 0, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ "Rest" ], [ 2, 1, -1, -1, 0, 0 ] ], 0.125 ] + ], + [ + [ [ [ -1, 1, -1, 1, 0, 0 ], [ 1, 1, -1, -1, 0, 1 ], [ "Rest" ], [ 2, 1, -1, -1, 0, 0 ] ], 0.75 ], + [ [ [ 0, 1, -1, -1, 0, 1 ], [ 1, 1, -1, -1, 0, 1 ], [ "Rest" ], [ 2, 1, -1, -1, 0, 0 ] ], 0 ], + [ [ [ 0, 1, -1, -1, 0, 1 ], [ 2, 1, -2, -1, 0, 0 ], [ "Rest" ], [ 2, 1, -1, -1, 0, 0 ] ], 0.875 ], + [ [ [ 0, 2, -1, -1, 0, 0 ], [ 2, 1, -2, -1, 0, 0 ], [ "Rest" ], [ 2, 1, -1, -1, 0, 0 ] ], 0.125 ], + [ [ [ 0, 2, -1, -1, 0, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ "Rest" ], [ 2, 1, -1, -1, 0, 0 ] ], 0.75 ], + [ [ [ 1, 1, -1, -1, -1, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ "Rest" ], [ 2, 1, -1, -1, 0, 0 ] ], 1.375 ], + [ [ [ 0, 1, -1, -1, 1, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ "Rest" ], [ 2, 1, -1, -1, 0, 0 ] ], 0.25 ] + ], + [ + [ [ [ 0, 1, -1, -1, 1, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ "Rest" ], [ 1, 2, 0, -1, 0, 0 ] ], 1.125 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ "Rest" ], [ 1, 2, 0, -1, 0, 0 ] ], 1.375 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ 0, 2, -1, -1, 0, 1 ], [ 1, 2, 0, -1, 0, 0 ] ], 0.25 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ 1, 2, -2, -1, 0, 0 ], [ 1, 2, 0, -1, 0, 0 ] ], 0.625 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ 1, 2, -2, -1, 0, 0 ], [ 1, 2, -1, 0, 0, 0 ] ], 0.375 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ 1, 2, -2, -1, 0, 0 ], [ 1, 2, -1, -1, 0, 1 ] ], 0.125 ], + [ [ [ 1, 2, -1, -1, -1, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ 1, 2, -2, -1, 0, 0 ], [ 1, 2, -1, -1, 0, 1 ] ], 1.5 ], + [ [ [ 1, 2, -1, -1, -1, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 2, -1, -1, 0, 1 ] ], 0.875 ] + ], + [ + [ [ [ -1, 4, -1, -1, 0, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 2, -1, -1, 0, 1 ] ], 0.625 ], + [ [ [ -1, 4, -1, -1, 0, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 0, 0 ] ], 0.125 ], + [ [ [ -1, 4, -1, -1, 0, 0 ], [ 0, 3, 0, -1, 0, 0 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 0, 0 ] ], 0.125 ] + ], + [ + [ [ [ -1, 4, -1, -1, 0, 0 ], [ 0, 3, 0, -1, 0, 0 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 4, -1, -1, 0, 0 ] ], 0.375 ], + [ [ [ -1, 4, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 0, -1 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 4, -1, -1, 0, 0 ] ], 0.25 ], + [ [ [ 0, 3, -1, -1, -1, 0 ], [ 1, 3, -1, -1, 0, -1 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 4, -1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 3, -1, -1, -1, 0 ], [ 1, 3, -1, -1, 0, -1 ], [ 0, 3, -1, -1, 0, 0 ], [ 2, 3, -1, -1, -1, 0 ] ], 1 ], + [ [ [ 0, 3, -1, -1, -1, 0 ], [ 1, 3, -1, -1, 0, -1 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 1, 0 ] ], 0.875 ], + [ [ [ -1, 3, -1, -1, 1, 0 ], [ 1, 3, -1, -1, 0, -1 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 1, 0 ] ], 0.875 ], + [ [ [ 0, 2, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 0, -1 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 1, 0 ] ], 1.25 ], + [ [ [ 0, 3, -1, -1, 0, -1 ], [ 1, 3, -1, -1, 0, -1 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 1, 0 ] ], 0.375 ], + [ [ [ 0, 3, -1, -1, 0, -1 ], [ 0, 3, 0, -1, 0, 0 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 1, 0 ] ], 1.5 ], + [ [ [ 0, 3, -1, -2, 0, 0 ], [ 0, 3, 0, -1, 0, 0 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 1, 0 ] ], 0.375 ], + [ [ [ 0, 3, -1, -2, 0, 0 ], [ 1, 2, -1, -1, 0, 0 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 1, 0 ] ], 0.25 ], + [ [ [ 0, 3, -1, -2, 0, 0 ], [ 0, 3, -1, -1, 1, 0 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 1, 0 ] ], 0.875 ] + ], + [ + [ [ [ "Rest" ], [ 0, 3, -1, -1, 1, 0 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 1, 0 ] ], 0.625 ], + [ [ [ "Rest" ], [ 0, 3, -1, -1, 1, 0 ], [ 0, 3, -1, -1, 0, 0 ], [ 1, 3, -1, -1, 1, -1 ] ], 0.5 ], + [ [ [ "Rest" ], [ 0, 3, -1, -1, 1, 0 ], [ -1, 4, -1, -1, 1, 0 ], [ 1, 3, -1, -1, 1, -1 ] ], 1 ], + [ [ [ "Rest" ], [ 0, 3, -1, -1, 1, 0 ], [ 0, 3, -2, -1, 1, 0 ], [ 1, 3, -1, -1, 1, -1 ] ], 1.125 ], + [ [ [ "Rest" ], [ 0, 3, -1, -1, 1, 0 ], [ -1, 3, -1, -1, 1, 1 ], [ 1, 3, -1, -1, 1, -1 ] ], 1.375 ], + [ [ [ "Rest" ], [ 0, 3, -1, -1, 1, 0 ], [ -1, 3, -1, 0, 1, 0 ], [ 1, 3, -1, -1, 1, -1 ] ], 0 ], + [ [ [ "Rest" ], [ 0, 3, -1, -1, 1, 0 ], [ -1, 3, -1, 0, 1, 0 ], [ 0, 3, 0, -1, 1, 0 ] ], 0.125 ], + [ [ [ "Rest" ], [ 0, 3, -1, -1, 1, 0 ], [ -1, 3, -1, 0, 1, 0 ], [ 1, 2, -1, -1, 1, 0 ] ], 1.375 ], + [ [ [ "Rest" ], [ 0, 3, -1, -1, 1, 0 ], [ -1, 4, -1, -1, 1, 0 ], [ 1, 2, -1, -1, 1, 0 ] ], 0.875 ], + [ [ [ "Rest" ], [ 0, 3, -1, -1, 1, 0 ], [ 0, 3, -2, -1, 1, 0 ], [ 1, 2, -1, -1, 1, 0 ] ], 0.375 ], + [ [ [ "Rest" ], [ 0, 3, -1, -1, 1, 0 ], [ 0, 3, -2, -1, 1, 0 ], [ 0, 3, -1, -1, 2, 0 ] ], 0.625 ], + [ [ [ "Rest" ], [ 0, 3, -1, -1, 1, 0 ], [ 0, 3, -2, -1, 1, 0 ], [ "Rest" ] ], 0.5 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 3, -2, -1, 1, 0 ], [ "Rest" ] ], 0.625 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], + [ [ 0, 3, -2, -1, 1, 0 ], [ 0, 3, -2, -1, 1, 0 ], [ 0, 3, -2, -1, 1, 0 ], [ 0, 3, -2, -1, 1, 0 ] ] +], +"cur_uid": "628706ec", +"ref_uid": "77b0d2dc", +"order_seed": 921767, +"dur_seed": 954688, +"motifs_seed": 995213, +"entrances_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/628706ec/lilypond/part_I.ly b/resources/string_quartet_2/628706ec/lilypond/part_I.ly new file mode 100644 index 0000000..5057e30 --- /dev/null +++ b/resources/string_quartet_2/628706ec/lilypond/part_I.ly @@ -0,0 +1,46 @@ +{ + { r1 } + \bar "|" + { r4 r8[ ais''8^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ ais''2 ~ } + \bar "|" + { ais''16[ g''8.^\markup { \pad-markup #0.2 "-42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ g''4 ~ g''8.[ r16] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. r8.[ g''16^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { g''2 ~ g''8.[ f''16^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }}] ~ f''4 ~ } + \bar "|" + { f''1 ~ } + \bar "|" + { f''2. ~ f''16[ e''8.^\markup { \pad-markup #0.2 "+35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { e''1 ~ } + \bar "|" + { e''2 ais''8.^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}[ a''16^\markup { \pad-markup #0.2 "-11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }}] ~ a''4 ~ } + \bar "|" + { a''1 ~ } + \bar "|" + { a''4 gis''8^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}[ dis'''8^\markup { \pad-markup #0.2 "-47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ dis'''2 ~ } + \bar "|" + { dis'''8.[ d'''16^\markup { \pad-markup #0.2 "-1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ d'''4 ~ d'''8.[ cis'''16^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↑" }}] ~ cis'''4 ~ } + \bar "|" + { cis'''1 ~ } + \bar "|" + { cis'''1 ~ } + \bar "|" + { cis'''1 ~ } + \bar "|" + { cis'''8.[ f''16^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }}] ~ f''2. ~ } + \bar "|" + { f''1 ~ } + \bar "|" + { f''8.[ f''16^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}] fis''2.^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { fis''2 ~ fis''16[ g''8.^\markup { \pad-markup #0.2 "-47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↑" }}] ~ g''8[ r8]} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/628706ec/lilypond/part_II.ly b/resources/string_quartet_2/628706ec/lilypond/part_II.ly new file mode 100644 index 0000000..488d911 --- /dev/null +++ b/resources/string_quartet_2/628706ec/lilypond/part_II.ly @@ -0,0 +1,46 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r16[ ais'8.^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ ais'2. ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'2 ~ ais'16[ r8.] r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r16[ a'8^\markup { \pad-markup #0.2 "-11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }} a'16^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}] ~ a'2. ~ } + \bar "|" + { a'2 gis'2^\markup { \pad-markup #0.2 "-49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'4 ~ gis'8.[ gis'16^\markup { \pad-markup #0.2 "+4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}] ~ gis'4 ~ gis'8.[ a'16^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}] ~ } + \bar "|" + { a'2 a'2^\markup { \pad-markup #0.2 "+43"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }} ~ } + \bar "|" + { a'8.[ b'16^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}] ~ b'2 ~ b'8.[ gis'16^\markup { \pad-markup #0.2 "+4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { gis'4 ~ gis'8[ a'8^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}] ~ a'2} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/628706ec/lilypond/part_III.ly b/resources/string_quartet_2/628706ec/lilypond/part_III.ly new file mode 100644 index 0000000..e86a63f --- /dev/null +++ b/resources/string_quartet_2/628706ec/lilypond/part_III.ly @@ -0,0 +1,46 @@ +{ + { ais'1^\markup { \pad-markup #0.2 "+18"} ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 } + \bar "|" + { f''2^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ f''8.[ e''16^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↑" }}] ~ e''4 ~ } + \bar "|" + { e''16[ dis''8.^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }}] ~ dis''2. ~ } + \bar "|" + { dis''1 ~ } + \bar "|" + { dis''2. d''4^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }} ~ } + \bar "|" + { d''8[ d''8^\markup { \pad-markup #0.2 "-39"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}] ~ d''4 ~ d''8[ c''8^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ c''4 ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''4 ~ c''16[ b'8.^\markup { \pad-markup #0.2 "+37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}] ~ b'16[ b'8.^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↓" }}] ~ b'4 ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'4 ~ b'8[ b'8^\markup { \pad-markup #0.2 "+37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}] ~ b'2 ~ } + \bar "|" + { b'4 ~ b'16[ c''8^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }} cis''16^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ~ cis''2 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/628706ec/lilypond/part_IV.ly b/resources/string_quartet_2/628706ec/lilypond/part_IV.ly new file mode 100644 index 0000000..ce23b81 --- /dev/null +++ b/resources/string_quartet_2/628706ec/lilypond/part_IV.ly @@ -0,0 +1,46 @@ +{ + { r2. ais4^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { ais2 ~ ais8[ c''8^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }}] ~ c''4 ~ } + \bar "|" + { c''2 ~ c''8.[ f'16^\markup { \pad-markup #0.2 "+20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}] ~ f'4 ~ } + \bar "|" + { f'2. f'4^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }} ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'8[ e'8^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↑" }}] ~ e'4 ~ e'8[ dis'8^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ dis'4 ~ } + \bar "|" + { dis'4 ~ dis'16[ d'8.^\markup { \pad-markup #0.2 "+4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}] ~ d'2 } + \bar "|" + { cis'1^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }} ~ } + \bar "|" + { cis'8[ d'8^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ d'4 ~ d'16[ c'8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ c'4 } + \bar "|" + { c'2^\markup { \pad-markup #0.2 "-5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }} ~ c'8.[ b16^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}] ~ b4 ~ } + \bar "|" + { b4 ~ b8[ ais8^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}] ~ ais2 ~ } + \bar "|" + { ais2. g'4^\markup { \pad-markup #0.2 "-3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }} ~ } + \bar "|" + { g'2. ~ g'8.[ dis'16^\markup { \pad-markup #0.2 "-47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { dis'2 ~ dis'8.[ d'16^\markup { \pad-markup #0.2 "-1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↓" }}] ~ d'4 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'8[ cis'8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ~ cis'4 ~ cis'16[ c'8.^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }}] ~ c'4 ~ } + \bar "|" + { c'8.[ b16^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ b2. ~ } + \bar "|" + { b8[ ais8^\markup { \pad-markup #0.2 "-18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }}] ~ ais2 ~ ais8[ r8] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/63bf7942/63bf7942_code.scd b/resources/string_quartet_2/63bf7942/63bf7942_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/63bf7942/63bf7942_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/string_quartet_2/63bf7942/63bf7942_mus_model.json b/resources/string_quartet_2/63bf7942/63bf7942_mus_model.json new file mode 100644 index 0000000..792f760 --- /dev/null +++ b/resources/string_quartet_2/63bf7942/63bf7942_mus_model.json @@ -0,0 +1,67 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 2.125 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 4.75 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], 2.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 3.375 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 1.375 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 4.625 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 1, 0, 0, 0 ], [ "Rest" ], [ 0, 1, -1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 9.125 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ] +], +"cur_uid": "63bf7942", +"ref_uid": "nil", +"order_seed": 921767, +"dur_seed": 345812, +"motifs_seed": 995213, +"entrances_probs_vals": [ 1, 0, 0, 0.88, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 0.88, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 0.88, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/6a7a8dd9/6a7a8dd9_code.scd b/resources/string_quartet_2/6a7a8dd9/6a7a8dd9_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/6a7a8dd9/6a7a8dd9_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/string_quartet_2/6a7a8dd9/6a7a8dd9_mus_model.json b/resources/string_quartet_2/6a7a8dd9/6a7a8dd9_mus_model.json new file mode 100644 index 0000000..bc3aae4 --- /dev/null +++ b/resources/string_quartet_2/6a7a8dd9/6a7a8dd9_mus_model.json @@ -0,0 +1,126 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 0, 0, 0 ] ], 3.875 ], + [ [ [ "Rest" ], [ "Rest" ], [ 1, 1, -1, -1, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 0 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ "Rest" ], [ 1, 1, -1, -1, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 0 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 1, 1, -1, 0, -1, 0 ], [ 1, 1, -1, -1, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 4.25 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 1, 1, -1, 0, -1, 0 ], [ 1, 1, -1, 0, 0, -1 ], [ 0, 1, -1, 0, 0, 0 ] ], 4.375 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 1, 1, -1, 0, -1, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 4 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 1, 1, -1, 0, -1, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 0 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 0, 2, -1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 0, 2, -1, 0, 0, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 3.75 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 0, 2, -1, 0, 0, 0 ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 2.25 ] + ], + [ + [ [ [ -1, 1, -1, 0, 0, 1 ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -1, 1, 0, -1 ] ], 4.375 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 2.5 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 0, -1, 1, 0, 0 ] ], 1.375 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 1, -1, 1, 1, 0 ] ], 3.75 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -1, 1, -1, 0 ] ], 1.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ] ], 1.125 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -2, 1, 0, 0 ] ], 5 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 1, -1, 1, 0, 1 ] ], 1.625 ] + ], + [ + [ [ [ -2, 1, -1, 2, 0, 0 ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 1, -1, 1, 0, 1 ] ], 0 ], + [ [ [ -2, 1, -1, 2, 0, 0 ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 1, -1, 2, 0, 0 ] ], 5 ], + [ [ [ -2, 1, -1, 2, 0, 0 ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -1, 1, 0, -1 ] ], 2.125 ], + [ [ [ -2, 1, -1, 2, 0, 0 ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 0, -1, 1, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ -2, 1, -1, 2, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 0, -1, 1, 0, 0 ] ], 4.75 ], + [ [ [ -2, 1, -1, 2, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 3, -1, 0, 0, 0 ] ], 0 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 3, -1, 0, 0, 0 ] ], 0 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 1, 2, -1, 0, 0, -1 ], [ -1, 3, -1, 0, 0, 0 ] ], 4 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 1, 2, -1, 0, 0, -1 ], [ 0, 2, -2, 0, 0, 0 ] ], 4 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 1, 2, -1, 0, 0, -1 ], [ -1, 2, -1, 0, 0, 1 ] ], 0 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ -1, 2, -1, 0, 0, 1 ] ], 2.75 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ] ], 4.625 ] + ], + [ + [ [ [ -1, 2, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 1, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ] ], 0 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 0 ], + [ [ [ -1, 2, -1, 1, 0, -1 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 4 ], + [ [ [ -2, 2, 0, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 4.75 ], + [ [ [ -1, 1, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 3.25 ], + [ [ [ -2, 2, -1, 1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 3.75 ], + [ [ [ -1, 2, -1, 1, -1, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 4.125 ], + [ [ [ -2, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 2.625 ], + [ [ [ -3, 2, -1, 1, 0, 1 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 4.5 ], + [ [ [ -2, 2, -2, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 2.625 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 3.375 ] + ], + [ + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -1, 3, -1, 1, -1, 0 ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -1, 3, -1, 1, -1, 0 ], [ -1, 3, -1, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -1, 3, -1, 1, -1, 0 ], [ -1, 3, -1, 1, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ] ], 4.5 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -1, 3, -1, 1, -1, 0 ], [ -1, 3, -1, 1, 0, 0 ], [ -1, 3, -1, 1, 0, -1 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -1, 3, -1, 1, -1, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 3, -1, 1, 0, -1 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -2, 4, -1, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 3, -1, 1, 0, -1 ] ], 4.5 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -2, 4, -1, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -2, 3, -1, 2, 0, 0 ] ], 4.875 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -2, 4, -1, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -3, 3, -1, 2, 0, 0 ] ], 4.25 ] + ], + [ + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ], [ -3, 3, -1, 2, 0, 0 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -3, 3, -1, 2, 0, 0 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -3, 3, -1, 1, 0, 1 ] ], 2.125 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -3, 4, -1, 1, 0, 0 ] ], 2 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -2, 3, -2, 1, 0, 0 ] ], 1.125 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -2, 3, -1, 1, 0, 0 ] ], 0.875 ] + ], + [ + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ], [ -2, 3, -1, 1, 0, 0 ] ], 1.875 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 2, -2, 1, 0, 0 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 2, -2, 1, 0, 0 ] ], 0 ], + [ [ [ -2, 2, -2, 1, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 2, -2, 1, 0, 0 ] ], 4.375 ], + [ [ [ -3, 2, -1, 1, 0, 1 ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 2, -2, 1, 0, 0 ] ], 4.375 ], + [ [ [ -3, 2, -1, 1, 0, 1 ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 9.625 ] + ] + ] +], +"last_changes": +[ + [ [ -3, 3, -1, 1, 0, 0 ], [ -2, 4, -1, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -2, 3, -1, 1, 0, 0 ] ], + [ [ -3, 3, -1, 1, 0, 0 ], [ -2, 4, -1, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 2, -2, 1, 0, 0 ] ], + [ [ -3, 3, -1, 1, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 2, -2, 1, 0, 0 ] ], + [ [ -2, 2, -2, 1, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 2, -2, 1, 0, 0 ] ], + [ [ -3, 2, -1, 1, 0, 1 ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 2, -2, 1, 0, 0 ] ] +], +"cur_uid": "6a7a8dd9", +"ref_uid": "63bf7942", +"order_seed": 327691, +"dur_seed": 667550, +"motifs_seed": 549585, +"entrances_probs_vals": [ 1, 0, 0, 0.88, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 0.88, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 0.88, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 2, 0, 1, 2, 2, 2, 1, 2, 2 ], [ ] ], + [ [ 2 ], [ 3, 3, 3, 3, 3, 3, 3, 3 ], [ 1, 0 ] ], + [ [ 2 ], [ 0, 3, 3, 3 ], [ 1 ] ], + [ [ 1 ], [ 3, 0, 2, 3, 3, 2, 3 ], [ ] ], + [ [ 3 ], [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 1, 2 ] ], + [ [ 0 ], [ 1, 2, 3, 3, 2, 1, 3, 3 ], [ ] ], + [ [ 0 ], [ 3, 3, 3, 3 ], [ 1, 2 ] ], + [ [ 2 ], [ 3, 1, 0, 0 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/71add9fc/71add9fc_code.scd b/resources/string_quartet_2/71add9fc/71add9fc_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/71add9fc/71add9fc_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/string_quartet_2/71add9fc/71add9fc_mus_model.json b/resources/string_quartet_2/71add9fc/71add9fc_mus_model.json new file mode 100644 index 0000000..ab65a8d --- /dev/null +++ b/resources/string_quartet_2/71add9fc/71add9fc_mus_model.json @@ -0,0 +1,210 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 4.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], 4.75 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 4.125 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], 4.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, -1, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, -1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, -1, 0, 0, 0 ] ], 4.125 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ -1, 1, 0, 0, 0, 1 ] ], 0 ], + [ [ [ -1, 1, 1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ -1, 1, 0, 0, 0, 1 ] ], 0 ], + [ [ [ -1, 1, 1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 1 ] ], 4.875 ] + ], + [ + [ [ [ -1, 1, 1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 0 ], + [ [ [ 0, 1, 0, 0, 0, -1 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 0 ], + [ [ [ 0, 1, 0, 0, 0, -1 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 1 ], [ -1, 1, 0, 1, 0, 0 ] ], 4 ] + ], + [ + [ [ [ 0, 1, 0, 0, 0, -1 ], [ 0, 1, 0, 0, 1, 0 ], [ -1, 1, 0, 0, 0, 1 ], [ -1, 1, 0, 1, 0, 0 ] ], 3.75 ], + [ [ [ 0, 1, 0, 0, 0, -1 ], [ 0, 1, 0, 0, 1, 0 ], [ -1, 1, 0, 0, 0, 1 ], [ 0, 1, 0, 0, 1, -1 ] ], 0 ], + [ [ [ 0, 1, 0, 0, 0, -1 ], [ 0, 1, 0, 0, 1, 0 ], [ -1, 1, 1, 0, 1, 0 ], [ 0, 1, 0, 0, 1, -1 ] ], 0 ], + [ [ [ 0, 1, 0, 0, 0, -1 ], [ -1, 1, 0, 1, 1, 0 ], [ -1, 1, 1, 0, 1, 0 ], [ 0, 1, 0, 0, 1, -1 ] ], 4.25 ] + ], + [ + [ [ [ 0, 1, 0, 0, 0, -1 ], [ -1, 1, 0, 1, 1, 0 ], [ -1, 1, 1, 0, 1, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 1, 0 ], [ -1, 1, 1, 0, 1, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 4.625 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 2, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 4.625 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 2, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -1, 1, 0, 1, 1, -1 ], [ -2, 1, 0, 1, 2, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ], [ -1, 1, 0, 1, 1, -1 ], [ -2, 1, 0, 1, 2, 0 ] ], 4.875 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ], [ -1, 1, 0, 0, 1, 0 ], [ -2, 1, 0, 1, 2, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ], [ -1, 1, 0, 0, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, -1, 1, 1, 0 ], [ -1, 1, 0, 0, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ] ], 4.125 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, -1, 1, 1, 0 ], [ 0, 1, 0, 1, 0, 0 ], [ -1, 0, 0, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, -1, 1, 1, 0 ], [ 0, 1, 0, 1, 0, 0 ], [ -2, 1, 1, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 1, 0 ], [ 0, 1, 0, 1, 0, 0 ], [ -2, 1, 1, 1, 1, 0 ] ], 4 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ 0, 1, 0, 1, 0, 0 ], [ -2, 1, 1, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ 0, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 1, 1, -1 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 1, 2, 0 ], [ -1, 1, 0, 1, 1, -1 ] ], 4.25 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ 0, 0, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 1, -1 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ 0, 0, 0, 1, 1, 0 ], [ -1, 1, 0, 0, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 1, 0 ], [ 0, 0, 0, 1, 1, 0 ], [ -1, 1, 0, 0, 1, 0 ] ], 4 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 1, 0 ], [ 0, 1, 0, 1, 1, -1 ], [ -1, 1, 0, 0, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 1, 0 ], [ 0, 1, 0, 1, 1, -1 ], [ -2, 1, 0, 2, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, -1, 1, 1, 0 ], [ 0, 1, 0, 1, 1, -1 ], [ -2, 1, 0, 2, 1, 0 ] ], 4.375 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ], [ 0, 1, 0, 1, 1, -1 ], [ -2, 1, 0, 2, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ], [ -1, 1, 1, 1, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ], [ -1, 1, 1, 1, 1, 0 ], [ -1, 1, 0, 1, 1, -1 ] ], 4 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ], [ 0, 0, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 1, -1 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ], [ 0, 0, 0, 1, 1, 0 ], [ -2, 1, 1, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 1, 0 ], [ 0, 0, 0, 1, 1, 0 ], [ -2, 1, 1, 1, 1, 0 ] ], 4.625 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 1, 0 ], [ 0, 0, 0, 1, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, -1, 1, 1, 0 ], [ 0, 0, 0, 1, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, -1, 1, 1, 0 ], [ -1, 1, 0, 1, 2, 0 ], [ -1, 0, 0, 1, 1, 0 ] ], 4.625 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, -1, 1, 1, 0 ], [ 0, 1, 0, 1, 0, 0 ], [ -1, 0, 0, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ], [ 0, 1, 0, 1, 0, 0 ], [ -1, 0, 0, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ], [ 0, 1, 0, 1, 0, 0 ], [ -2, 1, 0, 1, 2, 0 ] ], 4.5 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ 0, 1, 0, 1, 0, 0 ], [ -2, 1, 0, 1, 2, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ 0, 0, 0, 1, 1, 0 ], [ -2, 1, 0, 1, 2, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ 0, 0, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 4.375 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ 0, 0, 0, 1, 1, 0 ], [ -1, 1, -1, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 1, -1 ], [ 0, 0, 0, 1, 1, 0 ], [ -1, 1, -1, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 1, -1 ], [ -1, 1, 1, 1, 1, 0 ], [ -1, 1, -1, 1, 1, 0 ] ], 4.125 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 1, -1 ], [ -1, 1, 1, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 0, 1, 0 ], [ -1, 1, 1, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 0, 1, 0 ], [ 0, 1, 0, 1, 1, -1 ], [ -2, 1, 0, 1, 1, 1 ] ], 4.5 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 0, 1, 0 ], [ 0, 1, 0, 1, 1, -1 ], [ -2, 2, 0, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 0, 1, 0 ], [ -1, 1, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 3.875 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 1, 2, 0 ], [ -1, 1, -1, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ 0, 0, 0, 1, 1, 0 ], [ -1, 1, -1, 1, 1, 0 ] ], 3.75 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ 0, 1, 0, 1, 1, -1 ], [ -1, 1, -1, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ 0, 1, 0, 1, 1, -1 ], [ -2, 1, 0, 1, 1, 1 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 1, 0 ], [ 0, 1, 0, 1, 1, -1 ], [ -2, 1, 0, 1, 1, 1 ] ], 4.5 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 1, 0 ], [ -1, 1, 1, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 1, 1, 1, 0 ], [ -1, 1, 1, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 1, 1, 1, 0 ], [ -1, 1, 1, 1, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ] ], 4.75 ] + ], + [ + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ], [ -1, 1, 1, 1, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ], [ -1, 1, 1, 1, 1, 0 ], [ 0, 0, 0, 1, 1, 0 ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ], [ 0, 1, 0, 1, 1, -1 ], [ 0, 0, 0, 1, 1, 0 ] ], 4.75 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ], [ 0, 1, 0, 1, 1, -1 ], [ "Rest" ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ "Rest" ], [ 0, 1, 0, 1, 1, -1 ], [ "Rest" ] ], 0 ], + [ [ [ -2, 1, 0, 1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 5.25 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 1, 1, 1, 0 ], [ -1, 1, 1, 1, 1, 0 ], [ -2, 1, 0, 1, 1, 1 ] ], + [ [ -2, 1, 0, 1, 1, 0 ], [ -2, 1, 1, 1, 1, 0 ], [ -1, 1, 1, 1, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ] ], + [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ], [ -1, 1, 1, 1, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ] ], + [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ], [ -1, 1, 1, 1, 1, 0 ], [ 0, 0, 0, 1, 1, 0 ] ], + [ [ -2, 1, 0, 1, 1, 0 ], [ -1, 0, 0, 1, 1, 0 ], [ 0, 1, 0, 1, 1, -1 ], [ 0, 0, 0, 1, 1, 0 ] ] +], +"cur_uid": "71add9fc", +"ref_uid": "nil", +"order_seed": 921767, +"dur_seed": 954688, +"motifs_seed": 995213, +"entrances_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 1 ], [ 3, 2, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 1, 2, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 1, 2, 3 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/72dc057f/72dc057f_code.scd b/resources/string_quartet_2/72dc057f/72dc057f_code.scd new file mode 100644 index 0000000..57e638d --- /dev/null +++ b/resources/string_quartet_2/72dc057f/72dc057f_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(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + 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.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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/string_quartet_2/72dc057f/72dc057f_mus_model.json b/resources/string_quartet_2/72dc057f/72dc057f_mus_model.json new file mode 100644 index 0000000..5541d17 --- /dev/null +++ b/resources/string_quartet_2/72dc057f/72dc057f_mus_model.json @@ -0,0 +1,59 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 5 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 2, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -1, 3, -2, -1, 2, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 6.875 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -1, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -1, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -1, 3, -1, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ], 3.875 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 6.125 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 6.125 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -1, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -1, 3, -1, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ] +], +"cur_uid": "72dc057f", +"ref_uid": 62300302, +"order_seed": 209649, +"dur_seed": 795391, +"motifs_seed": 821280, +"entrances_probs_vals": [ 1, 0, 2.9365079365079, 0.47, 2.8021978021978, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 2.7380952380952, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 2.7380952380952, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.35390946502058, 0, 0.5, 0.5, 0.62139917695473, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8.0714285714286, 10.091836734694 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/72dc057f/lilypond/part_I.ly b/resources/string_quartet_2/72dc057f/lilypond/part_I.ly new file mode 100644 index 0000000..91049e5 --- /dev/null +++ b/resources/string_quartet_2/72dc057f/lilypond/part_I.ly @@ -0,0 +1,30 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 e2^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e2. ~ e8.[ e16^\markup { \pad-markup #0.2 "-36"}] ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e2. ~ e8[ d8^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }}] ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d2. ~ d8.[ r16] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/72dc057f/lilypond/part_II.ly b/resources/string_quartet_2/72dc057f/lilypond/part_II.ly new file mode 100644 index 0000000..80a76d1 --- /dev/null +++ b/resources/string_quartet_2/72dc057f/lilypond/part_II.ly @@ -0,0 +1,30 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 dis'2^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }} ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'2. ~ dis'8.[ cis'16^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'2. ~ cis'8[ d'8^\markup { \pad-markup #0.2 "+14"}] ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'2. ~ d'8.[ r16] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/72dc057f/lilypond/part_III.ly b/resources/string_quartet_2/72dc057f/lilypond/part_III.ly new file mode 100644 index 0000000..0b76537 --- /dev/null +++ b/resources/string_quartet_2/72dc057f/lilypond/part_III.ly @@ -0,0 +1,30 @@ +{ + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 cis2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis2. ~ cis8.[ cis16^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis2. ~ cis8[ f8^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f2. ~ f8.[ r16] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/72dc057f/lilypond/part_IV.ly b/resources/string_quartet_2/72dc057f/lilypond/part_IV.ly new file mode 100644 index 0000000..e37440b --- /dev/null +++ b/resources/string_quartet_2/72dc057f/lilypond/part_IV.ly @@ -0,0 +1,30 @@ +{ + { a,1^\markup { \pad-markup #0.2 "+16"} ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,2. ~ a,8.[ r16] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/74307bb4/74307bb4_code.scd b/resources/string_quartet_2/74307bb4/74307bb4_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/74307bb4/74307bb4_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/string_quartet_2/74307bb4/74307bb4_mus_model.json b/resources/string_quartet_2/74307bb4/74307bb4_mus_model.json new file mode 100644 index 0000000..6d6bd44 --- /dev/null +++ b/resources/string_quartet_2/74307bb4/74307bb4_mus_model.json @@ -0,0 +1,120 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 0, 0, 0 ] ], 3.875 ], + [ [ [ "Rest" ], [ "Rest" ], [ 1, 1, -1, -1, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 0 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ "Rest" ], [ 1, 1, -1, -1, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 0 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 1, 1, -1, 0, -1, 0 ], [ 1, 1, -1, -1, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 4.25 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 1, 1, -1, 0, -1, 0 ], [ 1, 1, -1, 0, 0, -1 ], [ 0, 1, -1, 0, 0, 0 ] ], 4.375 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 1, 1, -1, 0, -1, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 4 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 1, 1, -1, 0, -1, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 0 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 0, 2, -1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 0, 2, -1, 0, 0, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 3.75 ], + [ [ [ -1, 1, -1, 0, 0, 1 ], [ 0, 2, -1, 0, 0, 0 ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 2.25 ] + ], + [ + [ [ [ -1, 1, -1, 0, 0, 1 ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -1, 1, 0, -1 ] ], 4.375 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 2.5 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 0, -1, 1, 0, 0 ] ], 1.375 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 1, -1, 1, 1, 0 ] ], 3.75 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -1, 1, -1, 0 ] ], 1.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ] ], 1.125 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -2, 1, 0, 0 ] ], 5 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 1, -1, 1, 0, 1 ] ], 1.625 ] + ], + [ + [ [ [ -2, 1, -1, 2, 0, 0 ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 1, -1, 1, 0, 1 ] ], 0 ], + [ [ [ -2, 1, -1, 2, 0, 0 ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 1, -1, 2, 0, 0 ] ], 5 ], + [ [ [ -2, 1, -1, 2, 0, 0 ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 1, -1, 1, 0, -1 ] ], 2.125 ], + [ [ [ -2, 1, -1, 2, 0, 0 ], [ "Rest" ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 0, -1, 1, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ -2, 1, -1, 2, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 0, 1, -1, 1, 0, 0 ], [ 0, 0, -1, 1, 0, 0 ] ], 4.75 ], + [ [ [ -2, 1, -1, 2, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 3, -1, 0, 0, 0 ] ], 0 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 0, 1, -1, 1, 0, 0 ], [ -1, 3, -1, 0, 0, 0 ] ], 0 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 1, 2, -1, 0, 0, -1 ], [ -1, 3, -1, 0, 0, 0 ] ], 4 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 1, 2, -1, 0, 0, -1 ], [ 0, 2, -2, 0, 0, 0 ] ], 4 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 1, 2, -1, 0, 0, -1 ], [ -1, 2, -1, 0, 0, 1 ] ], 0 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ -1, 2, -1, 0, 0, 1 ] ], 2.75 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ 1, 1, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ] ], 4.625 ] + ], + [ + [ [ [ -1, 2, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 1, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ] ], 0 ], + [ [ [ -1, 2, -1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 0 ], + [ [ [ -1, 2, -1, 1, 0, -1 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 4 ], + [ [ [ -2, 2, 0, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 4.75 ], + [ [ [ -1, 1, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 3.25 ], + [ [ [ -2, 2, -1, 1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 3.75 ], + [ [ [ -1, 2, -1, 1, -1, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 4.125 ], + [ [ [ -2, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 2.625 ], + [ [ [ -3, 2, -1, 1, 0, 1 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 4.5 ], + [ [ [ -2, 2, -2, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 2.625 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 3.375 ] + ], + [ + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -1, 3, -1, 1, -1, 0 ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -1, 3, -1, 1, -1, 0 ], [ -1, 3, -1, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -1, 3, -1, 1, -1, 0 ], [ -1, 3, -1, 1, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ] ], 4.5 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -1, 3, -1, 1, -1, 0 ], [ -1, 3, -1, 1, 0, 0 ], [ -1, 3, -1, 1, 0, -1 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -1, 3, -1, 1, -1, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 3, -1, 1, 0, -1 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -2, 4, -1, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 3, -1, 1, 0, -1 ] ], 4.5 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -2, 4, -1, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -2, 3, -1, 2, 0, 0 ] ], 4.875 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ -2, 4, -1, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -3, 3, -1, 2, 0, 0 ] ], 4.25 ] + ], + [ + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ], [ -3, 3, -1, 2, 0, 0 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -3, 3, -1, 2, 0, 0 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -3, 3, -1, 1, 0, 1 ] ], 2.125 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -3, 4, -1, 1, 0, 0 ] ], 2 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -2, 3, -2, 1, 0, 0 ] ], 1.125 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ "Rest" ], [ -2, 3, -1, 1, 0, 0 ] ], 0.875 ] + ], + [ + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ], [ -2, 3, -1, 1, 0, 0 ] ], 1.875 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ "Rest" ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 2, -2, 1, 0, 0 ] ], 0 ], + [ [ [ -3, 3, -1, 1, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 2, -2, 1, 0, 0 ] ], 0 ], + [ [ [ -2, 2, -2, 1, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 2, -2, 1, 0, 0 ] ], 4.375 ], + [ [ [ -3, 2, -1, 1, 0, 1 ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -1, 2, -2, 1, 0, 0 ] ], 4.375 ], + [ [ [ -3, 2, -1, 1, 0, 1 ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 0, 2, -1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ] +], +"cur_uid": "74307bb4", +"ref_uid": "77b0d2dc", +"order_seed": 921767, +"dur_seed": 954688, +"motifs_seed": 995213, +"entrances_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/74307bb4/lilypond/part_I.ly b/resources/string_quartet_2/74307bb4/lilypond/part_I.ly new file mode 100644 index 0000000..8b64a06 --- /dev/null +++ b/resources/string_quartet_2/74307bb4/lilypond/part_I.ly @@ -0,0 +1,142 @@ +{ + { dis'1^\markup { \pad-markup #0.2 "+16"} ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'2. e'4^\markup { \pad-markup #0.2 "+44"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↓" }} ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'2. ~ e'8.[ f'16^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'8.[ fis'16^\markup { \pad-markup #0.2 "-17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }}] ~ fis'2 ~ fis'8[ fis'8^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↑" }}] ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'2. g'4^\markup { \pad-markup #0.2 "+33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↓" }} ~ } + \bar "|" + { g'2 ~ g'16[ gis'8.^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }}] ~ gis'4 ~ } + \bar "|" + { gis'8[ a'8^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }}] ~ a'2. ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'2 ~ a'8[ a'8^\markup { \pad-markup #0.2 "+25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }}] ~ a'4 ~ } + \bar "|" + { a'4 ~ a'8.[ b'16^\markup { \pad-markup #0.2 "-47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ b'2 ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'2. ~ b'8.[ e'16^\markup { \pad-markup #0.2 "+44"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { e'1 } + \bar "|" + { fis'1^\markup { \pad-markup #0.2 "-17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'8.[ f'16^\markup { \pad-markup #0.2 "+20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }}] ~ f'2. ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'8.[ fis'16^\markup { \pad-markup #0.2 "+31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ fis'2. ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'8.[ g'16^\markup { \pad-markup #0.2 "-42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}] ~ g'2. ~ } + \bar "|" + { g'2 ~ g'16[ gis'8.^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ gis'4 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'4 ~ gis'8[ g'8^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ g'2 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'2 ~ g'8[ fis'8^\markup { \pad-markup #0.2 "+48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ fis'4 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'2. ~ fis'8[ cis''8^\markup { \pad-markup #0.2 "-43"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''4 ~ cis''16[ cis'8.^\markup { \pad-markup #0.2 "-43"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ cis'2 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'4 ~ cis'8.[ b16^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}] ~ b2 ~ } + \bar "|" + { b2 ais2^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { ais2 b2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { b16[ dis'8.^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ dis'2. ~ } + \bar "|" + { dis'4 ~ dis'8.[ e'16^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }}] ~ e'2 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/74307bb4/lilypond/part_II.ly b/resources/string_quartet_2/74307bb4/lilypond/part_II.ly new file mode 100644 index 0000000..222dcb4 --- /dev/null +++ b/resources/string_quartet_2/74307bb4/lilypond/part_II.ly @@ -0,0 +1,142 @@ +{ + { r1 } + \bar "|" + { r2. r8.[ f'16^\markup { \pad-markup #0.2 "+47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↓" }}] ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'16[ g'8.^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↓" }}] ~ g'2. ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'4 gis'2.^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'4 g'2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }} dis''4^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { dis''1 ~ } + \bar "|" + { dis''2 ~ dis''8[ cis''8^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}] ~ cis''4 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''1 ~ } + \bar "|" + { cis''8.[ d''16^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ d''2. ~ } + \bar "|" + { d''1 ~ } + \bar "|" + { d''1 ~ } + \bar "|" + { d''1 ~ } + \bar "|" + { d''8.[ dis''16^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ dis''2. ~ } + \bar "|" + { dis''1 ~ } + \bar "|" + { dis''1 ~ } + \bar "|" + { dis''2. ~ dis''8[ r8] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 r8[ dis''8^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ dis''2 ~ } + \bar "|" + { dis''1 ~ } + \bar "|" + { dis''2 ~ dis''8[ gis'8^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ gis'4 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'4 ~ gis'8.[ r16] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2 gis'2^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/74307bb4/lilypond/part_III.ly b/resources/string_quartet_2/74307bb4/lilypond/part_III.ly new file mode 100644 index 0000000..0991a78 --- /dev/null +++ b/resources/string_quartet_2/74307bb4/lilypond/part_III.ly @@ -0,0 +1,142 @@ +{ + { r1 } + \bar "|" + { r2. r8.[ ais'16^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }}] ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'4 ais'2.^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }} ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'2. r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r2. r16[ ais'8.^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 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'2. ~ ais'8[ r8] } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 r8[ a'8^\markup { \pad-markup #0.2 "+37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}] ~ a'2 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'2 ~ a'8[ ais'8^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ ais'4 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'4 ~ ais'8.[ r16] r2 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 r8.[ ais'16^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }}] ~ ais'2 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/74307bb4/lilypond/part_IV.ly b/resources/string_quartet_2/74307bb4/lilypond/part_IV.ly new file mode 100644 index 0000000..0d3677b --- /dev/null +++ b/resources/string_quartet_2/74307bb4/lilypond/part_IV.ly @@ -0,0 +1,142 @@ +{ + { r1 } + \bar "|" + { r2. r8.[ c'16^\markup { \pad-markup #0.2 "-44"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'2. r4 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r4 r8.[ b16^\markup { \pad-markup #0.2 "-47"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }}] ~ b2 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b8.[ ais16^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }}] ~ ais2. ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais2. ~ ais8[ b8^\markup { \pad-markup #0.2 "+46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2. ~ b8[ c'8^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'4 cis'2.^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} ~ } + \bar "|" + { cis'2. ~ cis'8[ cis'8^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↑" }}] ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'2. d'4^\markup { \pad-markup #0.2 "+35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }} ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'2. ~ d'16[ dis'8.^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'8[ e8^\markup { \pad-markup #0.2 "+27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }}] ~ e2. ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e4 ~ e8[ e8^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}] ~ e2 ~ } + \bar "|" + { e2 ~ e8.[ dis16^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }}] ~ dis4 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis4 ~ dis8.[ e16^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}] ~ e2 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e2 ~ e8[ e8^\markup { \pad-markup #0.2 "+27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }}] ~ e4 ~ } + \bar "|" + { e1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/74b8f8d9/74b8f8d9_code.scd b/resources/string_quartet_2/74b8f8d9/74b8f8d9_code.scd new file mode 100644 index 0000000..57e638d --- /dev/null +++ b/resources/string_quartet_2/74b8f8d9/74b8f8d9_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(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + 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.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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/string_quartet_2/74b8f8d9/74b8f8d9_mus_model.json b/resources/string_quartet_2/74b8f8d9/74b8f8d9_mus_model.json new file mode 100644 index 0000000..c472d47 --- /dev/null +++ b/resources/string_quartet_2/74b8f8d9/74b8f8d9_mus_model.json @@ -0,0 +1,77 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.75 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ -2, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 4.75 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 2, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 2, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ] ], 7.125 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -2, 3, -2, -1, 2, 0 ], [ -1, 3, -3, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -2, 3, -2, -1, 2, 0 ], [ 0, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -2, 3, -2, -1, 2, 0 ], [ 0, 3, -2, -1, 1, -1 ] ], 4.5 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ 0, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ -1, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ], [ -1, 3, -1, -1, 1, 0 ] ], 6.875 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ -1, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ -1, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ 0, 2, -2, -1, 1, 0 ] ], 5.25 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -3, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -3, -1, 1, 0 ] ], 6.125 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ -1, 2, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ -1, 2, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 10.125 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ -1, 3, -1, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ 0, 2, -2, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -3, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -3, -1, 1, 0 ] ] +], +"cur_uid": "74b8f8d9", +"ref_uid": "4c059f33", +"order_seed": 824152, +"dur_seed": 266394, +"motifs_seed": 206072, +"entrances_probs_vals": [ 1, 0, 5.0793650793651, 0.47, 2.8021978021978, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 2.7380952380952, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 2.7380952380952, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8.0714285714286, 10.091836734694 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/74b8f8d9/lilypond/part_I.ly b/resources/string_quartet_2/74b8f8d9/lilypond/part_I.ly new file mode 100644 index 0000000..2d48723 --- /dev/null +++ b/resources/string_quartet_2/74b8f8d9/lilypond/part_I.ly @@ -0,0 +1,50 @@ +{ + { r1 } + \bar "|" + { r2. r8[ e8^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e4 f2.^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f2. ~ f16[ cis'8.^\markup { \pad-markup #0.2 "-25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'16[ cis'8.^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ cis'2. ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'2 d'2^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'8[ f8^\markup { \pad-markup #0.2 "+29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}] ~ f2. ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f8.[ r16] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/74b8f8d9/lilypond/part_II.ly b/resources/string_quartet_2/74b8f8d9/lilypond/part_II.ly new file mode 100644 index 0000000..1c284cd --- /dev/null +++ b/resources/string_quartet_2/74b8f8d9/lilypond/part_II.ly @@ -0,0 +1,50 @@ +{ + { r1 } + \bar "|" + { r2. r8[ cis8^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis4 d2.^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d2. ~ d16[ dis8.^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }}] ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis1 ~ } + \bar "|" + { dis16[ e8.^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}] ~ e2. ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e2 e2^\markup { \pad-markup #0.2 "+18"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e8[ fis8^\markup { \pad-markup #0.2 "-44"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }}] ~ fis2. ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis8.[ r16] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/74b8f8d9/lilypond/part_III.ly b/resources/string_quartet_2/74b8f8d9/lilypond/part_III.ly new file mode 100644 index 0000000..ecf1ce2 --- /dev/null +++ b/resources/string_quartet_2/74b8f8d9/lilypond/part_III.ly @@ -0,0 +1,50 @@ +{ + { r1 } + \bar "|" + { r2. r8[ a8^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a4 fis2.^\markup { \pad-markup #0.2 "-44"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }} ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis2. ~ fis16[ g8.^\markup { \pad-markup #0.2 "-15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }}] ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g16[ a8.^\markup { \pad-markup #0.2 "+16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }}] ~ a2. ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a2 cis2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis8[ d8^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }}] ~ d2. ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d8.[ r16] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/74b8f8d9/lilypond/part_IV.ly b/resources/string_quartet_2/74b8f8d9/lilypond/part_IV.ly new file mode 100644 index 0000000..eca3efa --- /dev/null +++ b/resources/string_quartet_2/74b8f8d9/lilypond/part_IV.ly @@ -0,0 +1,50 @@ +{ + { a,1^\markup { \pad-markup #0.2 "+16"} ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,1 ~ } + \bar "|" + { a,8.[ r16] r2. } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } + \bar "|" + { r1 } +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_2/76e45e56/76e45e56_code.scd b/resources/string_quartet_2/76e45e56/76e45e56_code.scd new file mode 100644 index 0000000..57e638d --- /dev/null +++ b/resources/string_quartet_2/76e45e56/76e45e56_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(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + 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.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + noProgIns = (popSize - noSusIns).rand + 1; + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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/string_quartet_2/76e45e56/76e45e56_mus_model.json b/resources/string_quartet_2/76e45e56/76e45e56_mus_model.json new file mode 100644 index 0000000..e1d66b2 --- /dev/null +++ b/resources/string_quartet_2/76e45e56/76e45e56_mus_model.json @@ -0,0 +1,71 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 2, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -1, 3, -2, -1, 2, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 7 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -1, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -1, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -1, 3, -1, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ], 5.5 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 4 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ 0, 3, -2, -2, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ 0, 3, -2, -2, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ 0, 3, -2, -2, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], 5.625 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], 6.5 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ "Rest" ], [ -2, 3, -2, 0, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 6.375 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ 0, 3, -2, -2, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ 0, 3, -2, -2, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ] ] +], +"cur_uid": "76e45e56", +"ref_uid": 62300302, +"order_seed": 209649, +"dur_seed": 306774, +"motifs_seed": 821280, +"entrances_probs_vals": [ 1, 0, 2.9365079365079, 0.47, 2.8021978021978, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 2.7380952380952, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 2.7380952380952, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.35390946502058, 0, 0.5, 0.5, 0.62139917695473, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8.0714285714286, 10.091836734694 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/77b0d2dc/77b0d2dc_code.scd b/resources/string_quartet_2/77b0d2dc/77b0d2dc_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/77b0d2dc/77b0d2dc_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/string_quartet_2/77b0d2dc/77b0d2dc_mus_model.json b/resources/string_quartet_2/77b0d2dc/77b0d2dc_mus_model.json new file mode 100644 index 0000000..6f0561e --- /dev/null +++ b/resources/string_quartet_2/77b0d2dc/77b0d2dc_mus_model.json @@ -0,0 +1,59 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 4.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], 4.75 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 4.125 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ "Rest" ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ "Rest" ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 9.625 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ] +], +"cur_uid": "77b0d2dc", +"ref_uid": "nil", +"order_seed": 921767, +"dur_seed": 954688, +"motifs_seed": 995213, +"entrances_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/7bb0e931/7bb0e931_code.scd b/resources/string_quartet_2/7bb0e931/7bb0e931_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/7bb0e931/7bb0e931_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/string_quartet_2/7bb0e931/7bb0e931_mus_model.json b/resources/string_quartet_2/7bb0e931/7bb0e931_mus_model.json new file mode 100644 index 0000000..97c68b2 --- /dev/null +++ b/resources/string_quartet_2/7bb0e931/7bb0e931_mus_model.json @@ -0,0 +1,69 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 3.375 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], 1.25 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 3.125 ] + ], + [ + [ [ [ 1, 0, -1, 0, 0, -1 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 1, 0, -1, 0, 0, -1 ], [ 1, 0, 1, 0, 0, -1 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 2.25 ] + ], + [ + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 1, 0, 1, 0, 0, -1 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 2.625 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ "Rest" ] ], 0 ], + [ [ [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ 2, -1, 0, -1, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 10.0 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 1, 0, -1, 0, 0, -1 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 1, 0, -1, 0, 0, -1 ], [ 1, 0, 1, 0, 0, -1 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 1, -1, 0, 0, -1, 0 ], [ 1, 0, 1, 0, 0, -1 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 1, -1, 0, 0, -1, 0 ], [ 2, -1, 0, -1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ] +], +"cur_uid": "7bb0e931", +"ref_uid": "nil", +"order_seed": 921767, +"dur_seed": 233566, +"motifs_seed": 995213, +"entrances_probs_vals": [ 1, 0, 0, 0.88, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 0.88, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 0.88, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/7df3df4c/7df3df4c_code.scd b/resources/string_quartet_2/7df3df4c/7df3df4c_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/7df3df4c/7df3df4c_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/string_quartet_2/7df3df4c/7df3df4c_mus_model.json b/resources/string_quartet_2/7df3df4c/7df3df4c_mus_model.json new file mode 100644 index 0000000..e06a740 --- /dev/null +++ b/resources/string_quartet_2/7df3df4c/7df3df4c_mus_model.json @@ -0,0 +1,59 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, -1, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, -1, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ], [ 1, 0, 0, 0, -1, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], 4.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 4.75 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 4.125 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 9.625 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ] +], +"cur_uid": "7df3df4c", +"ref_uid": "77b0d2dc", +"order_seed": 921767, +"dur_seed": 954688, +"motifs_seed": 995213, +"entrances_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/7fe1da13/7fe1da13_code.scd b/resources/string_quartet_2/7fe1da13/7fe1da13_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_2/7fe1da13/7fe1da13_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/string_quartet_2/7fe1da13/7fe1da13_mus_model.json b/resources/string_quartet_2/7fe1da13/7fe1da13_mus_model.json new file mode 100644 index 0000000..61971c6 --- /dev/null +++ b/resources/string_quartet_2/7fe1da13/7fe1da13_mus_model.json @@ -0,0 +1,198 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 4.25 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 2, 0, 0, -1, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 4.25 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 2, 0, 0, -1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 4.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 4.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1, 0 ], [ -1, 0, 0, 1, 0, 1 ] ], 0 ], + [ [ [ 0, -1, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1, 0 ], [ -1, 0, 0, 1, 0, 1 ] ], 0 ], + [ [ [ 0, -1, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0, 1 ], [ -1, 0, 0, 0, 0, 2 ], [ -1, 0, 0, 1, 0, 1 ] ], 3.875 ] + ], + [ + [ [ [ 0, -1, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0, 1 ], [ -1, 0, 0, 0, 0, 2 ], [ 0, 0, 0, 0, 1, 1 ] ], 0 ], + [ [ [ -1, 0, 1, 0, 0, 1 ], [ 0, 0, 0, 0, 0, 1 ], [ -1, 0, 0, 0, 0, 2 ], [ 0, 0, 0, 0, 1, 1 ] ], 0 ], + [ [ [ -1, 0, 1, 0, 0, 1 ], [ 0, 0, 0, 0, 0, 1 ], [ -1, 1, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1, 1 ] ], 4.125 ] + ], + [ + [ [ [ -1, 0, 1, 0, 0, 1 ], [ -1, 0, 0, 1, 0, 1 ], [ -1, 1, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1, 1 ] ], 4.875 ], + [ [ [ -1, 0, 1, 0, 0, 1 ], [ -1, 0, 0, 1, 0, 1 ], [ -1, 1, 0, 0, 0, 1 ], [ -1, 0, 0, 1, 0, 2 ] ], 0 ], + [ [ [ -1, 0, 1, 0, 0, 1 ], [ -1, 0, 0, 1, 0, 1 ], [ -2, 0, 0, 1, 0, 2 ], [ -1, 0, 0, 1, 0, 2 ] ], 0 ], + [ [ [ -1, 0, 1, 0, 0, 1 ], [ 0, 0, -1, 1, 0, 1 ], [ -2, 0, 0, 1, 0, 2 ], [ -1, 0, 0, 1, 0, 2 ] ], 4.375 ] + ], + [ + [ [ [ -1, 0, 1, 0, 0, 1 ], [ 0, 0, -1, 1, 0, 1 ], [ -2, 0, 0, 1, 0, 2 ], [ 0, -1, -1, 1, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ 0, 0, -1, 1, 0, 1 ], [ -2, 0, 0, 1, 0, 2 ], [ 0, -1, -1, 1, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ 0, 0, -1, 1, 0, 1 ], [ -1, 0, -1, 1, 0, 1 ], [ 0, -1, -1, 1, 0, 1 ] ], 3.875 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ 0, 0, -1, 1, 0, 1 ], [ -1, 0, -1, 1, 0, 1 ], [ -2, 0, -1, 2, 0, 2 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ 0, 0, -1, 1, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -2, 0, -1, 2, 0, 2 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 1, -1, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -2, 0, -1, 2, 0, 2 ] ], 4.875 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 1, -1, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -1, 0, -2, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 1, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ], [ -1, 0, -2, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 2, -1, 1 ], [ -2, 0, 0, 2, 0, 1 ], [ -1, 0, -2, 2, 0, 1 ] ], 4.125 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 2, -1, 1 ], [ -1, -1, -1, 2, 0, 1 ], [ -1, 0, -2, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 2, -1, 1 ], [ -1, -1, -1, 2, 0, 1 ], [ -2, 1, -1, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ -1, -1, -1, 2, 0, 1 ], [ -2, 1, -1, 2, 0, 1 ] ], 4.75 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -2, 1, -1, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -1, 0, -1, 2, -1, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, -1, -1, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -1, 0, -1, 2, -1, 1 ] ], 4.875 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -1, 0, -1, 2, -1, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -2, 0, -1, 2, 1, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ], [ -1, -1, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ] ], 4.375 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ], [ -1, 0, -1, 2, -1, 1 ], [ -2, 0, -1, 2, 1, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ], [ -1, 0, -1, 2, -1, 1 ], [ -1, -1, -1, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -1, 0, -1, 2, -1, 1 ], [ -1, -1, -1, 2, 0, 1 ] ], 5 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -2, 1, -1, 2, 0, 1 ], [ -1, -1, -1, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -2, 1, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, -1, -1, 2, 0, 1 ], [ -2, 1, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ] ], 4.25 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ -2, 1, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ -1, 0, -2, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ -1, 0, -2, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ] ], 3.875 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ -2, 0, -1, 2, 0, 2 ], [ -1, 0, -1, 2, 0, 0 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ -2, 0, -1, 2, 0, 2 ], [ -1, 0, -1, 1, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, -1, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 0, 2 ], [ -1, 0, -1, 1, 0, 1 ] ], 4.375 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, -1, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 0, 2 ], [ -1, 0, -2, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -2, 0, -1, 2, 0, 2 ], [ -1, 0, -2, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -2, 1, -1, 2, 0, 1 ], [ -1, 0, -2, 2, 0, 1 ] ], 4.375 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -1, 0, -1, 2, -1, 1 ], [ -1, 0, -2, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 1, 0, 1 ], [ -1, 0, -1, 2, -1, 1 ], [ -1, 0, -2, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 1, 0, 1 ], [ -1, 0, -1, 2, -1, 1 ], [ -2, 1, -1, 2, 0, 1 ] ], 4.125 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ], [ -1, 0, -1, 2, -1, 1 ], [ -2, 1, -1, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ -2, 1, -1, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ -1, 0, -1, 2, -1, 1 ] ], 3.875 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ 0, 0, -1, 1, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -2, 0, -1, 2, 1, 1 ], [ 0, 0, -1, 1, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -1, -1, -1, 2, 0, 1 ], [ 0, 0, -1, 1, 0, 1 ] ], 4.5 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -1, -1, -1, 2, 0, 1 ], [ 0, 0, -1, 2, 0, 0 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 1, 0, 1 ], [ -1, -1, -1, 2, 0, 1 ], [ 0, 0, -1, 2, 0, 0 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 1, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ], [ 0, 0, -1, 2, 0, 0 ] ], 4.625 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 1, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ], [ 0, -1, -1, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -1, 1, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ 0, -1, -1, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 0, 2 ], [ -1, 0, -1, 2, 0, 0 ], [ 0, -1, -1, 2, 0, 1 ] ], 4.875 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -2, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ 0, -1, -1, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -2, 2, 0, 1 ], [ -1, 0, -1, 2, 0, 0 ], [ -1, 0, 0, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -2, 2, 0, 1 ], [ -1, -1, -1, 2, 0, 1 ], [ -1, 0, 0, 2, 0, 1 ] ], 4.25 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -2, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ -1, 0, 0, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -1, 0, -2, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ 0, 0, -1, 2, 0, 0 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 1, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 1, 1 ], [ 0, 0, -1, 2, 0, 0 ] ], 4.875 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 1, -1, 2, 0, 1 ], [ -1, 0, -1, 2, -1, 1 ], [ 0, 0, -1, 2, 0, 0 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 0, 2 ], [ -1, 0, -1, 2, -1, 1 ], [ 0, 0, -1, 2, 0, 0 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 0, 2 ], [ -1, 0, -1, 2, -1, 1 ], [ 0, 0, -1, 1, 0, 1 ] ], 4.5 ] + ], + [ + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 3, 0, 1 ], [ -1, 0, -1, 2, -1, 1 ], [ 0, 0, -1, 1, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 3, 0, 1 ], [ -1, 0, -1, 2, -1, 1 ], [ -2, 0, 0, 2, 0, 1 ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 3, 0, 1 ], [ -2, 1, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ] ], 3.875 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 3, 0, 1 ], [ -2, 1, -1, 2, 0, 1 ], [ "Rest" ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 3, 0, 1 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ -2, 0, -1, 2, 0, 1 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 8.5 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 0, 2 ], [ -1, 0, -1, 2, -1, 1 ], [ 0, 0, -1, 2, 0, 0 ] ], + [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 2, 0, 2 ], [ -1, 0, -1, 2, -1, 1 ], [ 0, 0, -1, 1, 0, 1 ] ], + [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 3, 0, 1 ], [ -1, 0, -1, 2, -1, 1 ], [ 0, 0, -1, 1, 0, 1 ] ], + [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 3, 0, 1 ], [ -1, 0, -1, 2, -1, 1 ], [ -2, 0, 0, 2, 0, 1 ] ], + [ [ -2, 0, -1, 2, 0, 1 ], [ -2, 0, -1, 3, 0, 1 ], [ -2, 1, -1, 2, 0, 1 ], [ -2, 0, 0, 2, 0, 1 ] ] +], +"cur_uid": "7fe1da13", +"ref_uid": "nil", +"order_seed": 962045, +"dur_seed": 299387, +"motifs_seed": 678579, +"entrances_probs_vals": [ 1, 0, 0, 3.8461538461538, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 3.8461538461538, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 3.8461538461538, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 1 ], [ 3, 2, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 1, 2, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 1, 2, 3 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_2/tmp/tmp_mus_model.json b/resources/string_quartet_2/tmp/tmp_mus_model.json new file mode 100644 index 0000000..d270a25 --- /dev/null +++ b/resources/string_quartet_2/tmp/tmp_mus_model.json @@ -0,0 +1,71 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 2, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -1, 3, -2, -1, 2, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 7 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ], [ -1, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -1, 3, -1, -1, 1, 0 ], [ -2, 4, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ -1, 3, -1, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ], 5.5 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 0, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ 0, 2, -2, -1, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 4 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ 0, 3, -2, -2, 1, 0 ], [ -1, 2, -2, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ 0, 3, -2, -2, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ 0, 3, -2, -2, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], 5.625 ] + ], + [ + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], 6.5 ], + [ [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ "Rest" ], [ -2, 3, -2, 0, 1, 0 ], [ "Rest" ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ -1, 3, -2, -1, 1, -1 ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 6.375 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 3, -2, -1, 1, 0 ], [ -1, 3, -3, -1, 1, 0 ], [ 0, 3, -2, -2, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ 0, 3, -2, -2, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 1, 0 ], [ -2, 3, -1, -1, 1, 0 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, -1, 1, 1 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ] ], + [ [ -2, 3, -2, -1, 1, 0 ], [ -2, 3, -2, 0, 1, 0 ], [ -1, 3, -2, -1, 1, 0 ], [ -1, 3, -2, -1, 1, -1 ] ] +], +"cur_uid": "tmp", +"ref_uid": 62300302, +"order_seed": 209649, +"dur_seed": 306774, +"motifs_seed": 821280, +"entrances_probs_vals": [ 1, 0, 2.9365079365079, 0.47, 2.8021978021978, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 2.7380952380952, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 2.7380952380952, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.35390946502058, 0, 0.5, 0.5, 0.62139917695473, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8.0714285714286, 10.091836734694 ], +"passages_size": [ 0, 10 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3.json b/resources/string_quartet_3.json new file mode 100644 index 0000000..f4e9aa1 --- /dev/null +++ b/resources/string_quartet_3.json @@ -0,0 +1,21 @@ +{ +"ledger": +[ + "5201b8af", + "781442dc", + "6f0f638f", + "577cf188", + "7c2de94c", + "5488f7e9", + "5ec14635", + "726a40c7", + "55f9b81e", + "45fa07e8", + "6a9928d6", + "55bd25a1", + "75316bf0", + "69c568c6", + "4ff624b0", + "7e230015" +] +} \ No newline at end of file diff --git a/resources/string_quartet_3.json_bak b/resources/string_quartet_3.json_bak new file mode 100644 index 0000000..4417271 --- /dev/null +++ b/resources/string_quartet_3.json_bak @@ -0,0 +1,20 @@ +{ +"ledger": +[ + "5201b8af", + "781442dc", + "6f0f638f", + "577cf188", + "7c2de94c", + "5488f7e9", + "5ec14635", + "726a40c7", + "55f9b81e", + "45fa07e8", + "6a9928d6", + "55bd25a1", + "75316bf0", + "69c568c6", + "4ff624b0" +] +} \ No newline at end of file diff --git a/resources/string_quartet_3/45fa07e8/45fa07e8_code.scd b/resources/string_quartet_3/45fa07e8/45fa07e8_code.scd new file mode 100644 index 0000000..92aa171 --- /dev/null +++ b/resources/string_quartet_3/45fa07e8/45fa07e8_code.scd @@ -0,0 +1,966 @@ +( +// 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) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + if(pDistance < 0, {stepFunc.value(abs(pDistance))}, {0.001}); + //stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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); + + + if(rangeScore.value(candidate, [0, 0, 0, 0, 0, 0], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + }, { + 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/string_quartet_3/45fa07e8/45fa07e8_mus_model.json b/resources/string_quartet_3/45fa07e8/45fa07e8_mus_model.json new file mode 100644 index 0000000..10b6bb0 --- /dev/null +++ b/resources/string_quartet_3/45fa07e8/45fa07e8_mus_model.json @@ -0,0 +1,443 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 2, -5, 0, 3, -1, -1 ], [ "Rest" ], [ "Rest" ] ], 1.5 ], + [ [ [ "Rest" ], [ 2, -5, 0, 3, -1, -1 ], [ "Rest" ], [ 4, -5, -1, 2, -1, -1 ] ], 1.75 ], + [ [ [ 2, -5, -1, 3, -1, -1 ], [ 2, -5, 0, 3, -1, -1 ], [ "Rest" ], [ 4, -5, -1, 2, -1, -1 ] ], 1 ], + [ [ [ 2, -5, -1, 3, -1, -1 ], [ 2, -5, 0, 3, -1, -1 ], [ 4, -5, -1, 2, 0, -1 ], [ 4, -5, -1, 2, -1, -1 ] ], 1.375 ] + ], + [ + [ [ [ 2, -5, -1, 3, -1, -1 ], [ 4, -5, -1, 1, -1, -1 ], [ 4, -5, -1, 2, 0, -1 ], [ 4, -5, -1, 2, -1, -1 ] ], 0.875 ] + ], + [ + [ [ [ 2, -5, -1, 3, -1, -1 ], [ 4, -5, -1, 1, -1, -1 ], [ 4, -5, -1, 2, 0, -1 ], [ 3, -4, -1, 2, 0, -1 ] ], 1.625 ] + ], + [ + [ [ [ 2, -5, -1, 3, -1, -1 ], [ 3, -5, -1, 3, -1, -1 ], [ 4, -5, -1, 2, 0, -1 ], [ 3, -4, -1, 2, 0, -1 ] ], 0.75 ] + ], + [ + [ [ [ 2, -5, -1, 3, -1, -1 ], [ 4, -6, -1, 2, 0, -1 ], [ 4, -5, -1, 2, 0, -1 ], [ 3, -4, -1, 2, 0, -1 ] ], 1.75 ] + ], + [ + [ [ [ 3, -6, -1, 2, 0, -1 ], [ 4, -6, -1, 2, 0, -1 ], [ 4, -5, -1, 2, 0, -1 ], [ 3, -4, -1, 2, 0, -1 ] ], 1.375 ] + ], + [ + [ [ [ 3, -6, -1, 2, 0, -1 ], [ 4, -5, -1, 2, -1, -1 ], [ 4, -5, -1, 2, 0, -1 ], [ 3, -4, -1, 2, 0, -1 ] ], 1.125 ] + ], + [ + [ [ [ 3, -6, -1, 2, 0, -1 ], [ 4, -5, -1, 2, -1, -1 ], [ 3, -4, -1, 2, 1, -1 ], [ 3, -4, -1, 2, 0, -1 ] ], 1.625 ] + ], + [ + [ [ [ 3, -6, -1, 2, 0, -1 ], [ 4, -5, -1, 2, -1, -1 ], [ 3, -4, -1, 2, 1, -1 ], [ 2, -3, -1, 2, 1, -1 ] ], 1.125 ] + ], + [ + [ [ [ 3, -6, -1, 2, 0, -1 ], [ 4, -5, -1, 2, -1, -1 ], [ 3, -4, -1, 2, 1, -1 ], [ 3, -4, -2, 2, 1, -1 ] ], 1.5 ] + ], + [ + [ [ [ 3, -6, -1, 2, 0, -1 ], [ 4, -5, -1, 2, -1, -1 ], [ 4, -5, -2, 2, 1, -1 ], [ 3, -4, -2, 2, 1, -1 ] ], 1.375 ] + ], + [ + [ [ [ 3, -5, -2, 2, 0, -1 ], [ 4, -5, -1, 2, -1, -1 ], [ 4, -5, -2, 2, 1, -1 ], [ 3, -4, -2, 2, 1, -1 ] ], 1.5 ] + ], + [ + [ [ [ 3, -5, -2, 2, 0, -1 ], [ 4, -5, -2, 2, 0, -1 ], [ 4, -5, -2, 2, 1, -1 ], [ 3, -4, -2, 2, 1, -1 ] ], 1.625 ] + ], + [ + [ [ [ 3, -5, -2, 2, 0, -1 ], [ 4, -5, -2, 2, 0, -1 ], [ 4, -5, -2, 2, 1, -1 ], [ 5, -5, -2, 1, 0, -1 ] ], 1.25 ] + ], + [ + [ [ [ 3, -5, -2, 2, 0, -1 ], [ 4, -5, -3, 2, 1, -1 ], [ 4, -5, -2, 2, 1, -1 ], [ 5, -5, -2, 1, 0, -1 ] ], 1.75 ] + ], + [ + [ [ [ 3, -5, -2, 2, 0, -1 ], [ 4, -5, -3, 2, 1, -1 ], [ 4, -5, -2, 2, 1, -1 ], [ 3, -5, -2, 3, 1, -1 ] ], 0.875 ] + ], + [ + [ [ [ 3, -5, -2, 2, 0, -1 ], [ 5, -5, -2, 1, 0, -1 ], [ 4, -5, -2, 2, 1, -1 ], [ 3, -5, -2, 3, 1, -1 ] ], 0.625 ] + ], + [ + [ [ [ 3, -5, -2, 2, 0, -1 ], [ 5, -6, -2, 2, 0, -1 ], [ 4, -5, -2, 2, 1, -1 ], [ 3, -5, -2, 3, 1, -1 ] ], 0.75 ] + ], + [ + [ [ [ 3, -5, -2, 2, 0, -1 ], [ 5, -6, -2, 2, 0, -1 ], [ 4, -5, -2, 2, 1, -1 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.125 ] + ], + [ + [ [ [ 3, -5, -2, 2, 0, -1 ], [ 6, -5, -2, 1, 0, -2 ], [ 4, -5, -2, 2, 1, -1 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.5 ] + ], + [ + [ [ [ 4, -4, -2, 1, 0, -2 ], [ 6, -5, -2, 1, 0, -2 ], [ 4, -5, -2, 2, 1, -1 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.375 ] + ], + [ + [ [ [ 4, -4, -2, 1, 0, -2 ], [ 6, -4, -2, 1, -1, -2 ], [ 4, -5, -2, 2, 1, -1 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.625 ] + ], + [ + [ [ [ 4, -4, -2, 1, 0, -2 ], [ 6, -4, -2, 1, -1, -2 ], [ 6, -5, -2, 2, 0, -3 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.375 ] + ], + [ + [ [ [ 5, -4, -2, 1, -2, -2 ], [ 6, -4, -2, 1, -1, -2 ], [ 6, -5, -2, 2, 0, -3 ], [ 5, -5, -2, 2, 0, -2 ] ], 0.75 ] + ], + [ + [ [ [ 5, -4, -2, 1, -2, -2 ], [ 6, -4, -2, 1, -1, -2 ], [ 5, -5, -1, 2, 0, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.375 ] + ], + [ + [ [ [ 5, -4, -2, 1, -2, -2 ], [ 6, -4, -2, 1, -1, -2 ], [ 5, -5, -2, 1, -1, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 1 ] + ], + [ + [ [ [ 5, -4, -2, 1, -2, -2 ], [ 6, -4, -2, 1, -1, -2 ], [ 4, -4, -2, 1, 0, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.625 ] + ], + [ + [ [ [ 5, -4, -2, 1, -2, -2 ], [ 6, -4, -2, 1, -1, -2 ], [ 4, -3, -2, 1, -1, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.625 ] + ], + [ + [ [ [ 5, -4, -2, 1, -2, -2 ], [ 5, -3, -2, 1, -2, -2 ], [ 4, -3, -2, 1, -1, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 1 ] + ], + [ + [ [ [ 4, -5, -2, 2, 0, -2 ], [ 5, -3, -2, 1, -2, -2 ], [ 4, -3, -2, 1, -1, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.125 ] + ], + [ + [ [ [ 4, -5, -2, 2, 0, -2 ], [ 5, -3, -2, 1, -2, -2 ], [ 5, -3, -2, 1, -3, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 0.75 ] + ], + [ + [ [ [ 4, -5, -2, 2, 0, -2 ], [ 5, -5, -2, 2, -1, -2 ], [ 5, -3, -2, 1, -3, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.5 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 5, -5, -2, 2, -1, -2 ], [ 5, -3, -2, 1, -3, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 0.875 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 4, -4, -2, 2, 0, -2 ], [ 5, -3, -2, 1, -3, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.75 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 4, -4, -2, 2, 0, -2 ], [ 5, -5, -2, 1, 0, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 0.875 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 4, -4, -2, 2, 0, -2 ], [ 5, -4, -2, 1, -1, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 0.875 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 4, -4, -2, 2, 0, -2 ], [ 4, -5, -1, 2, 0, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.5 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 4, -4, -2, 2, 0, -2 ], [ 5, -6, -2, 2, 0, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 0.875 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 4, -5, -2, 2, 0, -1 ], [ 5, -6, -2, 2, 0, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.625 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 4, -5, -2, 2, 0, -1 ], [ 5, -5, -2, 2, -1, -2 ], [ 5, -5, -2, 2, 0, -2 ] ], 1.125 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 4, -5, -2, 2, 0, -1 ], [ 5, -5, -2, 2, -1, -2 ], [ 5, -4, -2, 2, -1, -2 ] ], 1.5 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 5, -4, -3, 2, -1, -2 ], [ 5, -5, -2, 2, -1, -2 ], [ 5, -4, -2, 2, -1, -2 ] ], 0.875 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 5, -4, -3, 2, -1, -2 ], [ 5, -5, -2, 2, -1, -2 ], [ 6, -5, -3, 2, -1, -2 ] ], 1.5 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 5, -4, -3, 2, -1, -2 ], [ 6, -6, -3, 2, -1, -2 ], [ 6, -5, -3, 2, -1, -2 ] ], 1.25 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 5, -4, -3, 2, -1, -2 ], [ 6, -5, -2, 1, -1, -2 ], [ 6, -5, -3, 2, -1, -2 ] ], 1.5 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 7, -5, -2, 0, -1, -2 ], [ 6, -5, -2, 1, -1, -2 ], [ 6, -5, -3, 2, -1, -2 ] ], 0.75 ] + ], + [ + [ [ [ 4, -5, -2, 1, -1, -2 ], [ 7, -5, -2, 0, -1, -2 ], [ 6, -5, -2, 1, -1, -2 ], [ 6, -4, -2, 1, -1, -2 ] ], 0.75 ] + ], + [ + [ [ [ 5, -5, -2, 0, -1, -2 ], [ 7, -5, -2, 0, -1, -2 ], [ 6, -5, -2, 1, -1, -2 ], [ 6, -4, -2, 1, -1, -2 ] ], 1.125 ] + ], + [ + [ [ [ 5, -5, -2, 1, -1, -3 ], [ 7, -5, -2, 0, -1, -2 ], [ 6, -5, -2, 1, -1, -2 ], [ 6, -4, -2, 1, -1, -2 ] ], 1.625 ] + ], + [ + [ [ [ 5, -5, -2, 1, -1, -3 ], [ 6, -4, -3, 1, -1, -2 ], [ 6, -5, -2, 1, -1, -2 ], [ 6, -4, -2, 1, -1, -2 ] ], 1.75 ] + ], + [ + [ [ [ 5, -5, -2, 1, -1, -3 ], [ 6, -4, -3, 1, -1, -2 ], [ 5, -3, -2, 1, -1, -2 ], [ 6, -4, -2, 1, -1, -2 ] ], 0.875 ] + ], + [ + [ [ [ 4, -4, -2, 1, -1, -2 ], [ 6, -4, -3, 1, -1, -2 ], [ 5, -3, -2, 1, -1, -2 ], [ 6, -4, -2, 1, -1, -2 ] ], 1.25 ] + ], + [ + [ [ [ 4, -4, -2, 1, -1, -2 ], [ 6, -4, -3, 1, -1, -2 ], [ 5, -4, -2, 1, -1, -2 ], [ 6, -4, -2, 1, -1, -2 ] ], 1.625 ] + ], + [ + [ [ [ 4, -4, -2, 1, -1, -2 ], [ 5, -4, -2, 2, -1, -2 ], [ 5, -4, -2, 1, -1, -2 ], [ 6, -4, -2, 1, -1, -2 ] ], 1.875 ] + ], + [ + [ [ [ 4, -4, -2, 1, -1, -2 ], [ 3, -3, -2, 1, -1, -2 ], [ 5, -4, -2, 1, -1, -2 ], [ 6, -4, -2, 1, -1, -2 ] ], 1.625 ] + ], + [ + [ [ [ 4, -4, -2, 1, -1, -2 ], [ 4, -4, -3, 1, -1, -2 ], [ 5, -4, -2, 1, -1, -2 ], [ 6, -4, -2, 1, -1, -2 ] ], 1.5 ] + ], + [ + [ [ [ 4, -4, -2, 1, -1, -2 ], [ 4, -4, -3, 1, -1, -2 ], [ 5, -4, -2, 1, -1, -2 ], [ 5, -4, -3, 1, -1, -2 ] ], 0.625 ] + ], + [ + [ [ [ 4, -4, -2, 1, -1, -2 ], [ 3, -4, -2, 2, -1, -2 ], [ 5, -4, -2, 1, -1, -2 ], [ 5, -4, -3, 1, -1, -2 ] ], 1.625 ] + ], + [ + [ [ [ 4, -4, -2, 1, -1, -2 ], [ 5, -4, -3, 0, -1, -2 ], [ 5, -4, -2, 1, -1, -2 ], [ 5, -4, -3, 1, -1, -2 ] ], 1.625 ] + ], + [ + [ [ [ 5, -4, -3, 1, -2, -2 ], [ 5, -4, -3, 0, -1, -2 ], [ 5, -4, -2, 1, -1, -2 ], [ 5, -4, -3, 1, -1, -2 ] ], 0.875 ] + ], + [ + [ [ [ 5, -4, -3, 1, -2, -2 ], [ 5, -4, -3, 0, -1, -2 ], [ 6, -5, -3, 1, -1, -2 ], [ 5, -4, -3, 1, -1, -2 ] ], 1.125 ] + ], + [ + [ [ [ 4, -3, -3, 1, -1, -2 ], [ 5, -4, -3, 0, -1, -2 ], [ 6, -5, -3, 1, -1, -2 ], [ 5, -4, -3, 1, -1, -2 ] ], 1 ] + ], + [ + [ [ [ 4, -3, -3, 1, -1, -2 ], [ 5, -4, -3, 0, -1, -2 ], [ 6, -5, -3, 1, -1, -2 ], [ 6, -5, -4, 1, -1, -2 ] ], 1.5 ] + ], + [ + [ [ [ 5, -5, -2, 1, -1, -2 ], [ 5, -4, -3, 0, -1, -2 ], [ 6, -5, -3, 1, -1, -2 ], [ 6, -5, -4, 1, -1, -2 ] ], 1 ] + ], + [ + [ [ [ 5, -5, -2, 1, -1, -2 ], [ 5, -4, -3, 0, -1, -2 ], [ 6, -5, -3, 1, -1, -2 ], [ 5, -5, -3, 1, -1, -1 ] ], 1.25 ] + ], + [ + [ [ [ 5, -5, -2, 1, -1, -2 ], [ 5, -4, -3, 0, -1, -2 ], [ 5, -5, -2, 1, -1, -1 ], [ 5, -5, -3, 1, -1, -1 ] ], 1.375 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -4, -3, 0, -1, -2 ], [ 5, -5, -2, 1, -1, -1 ], [ 5, -5, -3, 1, -1, -1 ] ], 1.375 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -4, -3, 0, -1, -2 ], [ 5, -5, -2, 1, -1, -1 ], [ 4, -5, -2, 2, -1, -1 ] ], 1.5 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -4, -3, 0, -1, -2 ], [ 5, -5, -2, 1, -1, -1 ], [ 6, -7, -2, 1, -1, -1 ] ], 1.375 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -4, -3, 0, -1, -2 ], [ 5, -5, -2, 1, -1, -1 ], [ 6, -6, -2, 1, -2, -1 ] ], 1.625 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -4, -3, 0, -1, -2 ], [ 6, -6, -3, 1, -1, -1 ], [ 6, -6, -2, 1, -2, -1 ] ], 1.125 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -4, -3, 0, -1, -2 ], [ 5, -6, -2, 2, -1, -1 ], [ 6, -6, -2, 1, -2, -1 ] ], 1.125 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -4, -3, 0, -1, -2 ], [ 5, -6, -2, 2, -1, -1 ], [ 6, -4, -2, 0, -1, -2 ] ], 0.625 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -4, -3, 0, -1, -2 ], [ 7, -4, -2, -1, -1, -2 ], [ 6, -4, -2, 0, -1, -2 ] ], 1 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -7, -2, 1, -1, -1 ], [ 7, -4, -2, -1, -1, -2 ], [ 6, -4, -2, 0, -1, -2 ] ], 1.5 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 4, -4, -2, 1, -1, -2 ], [ 7, -4, -2, -1, -1, -2 ], [ 6, -4, -2, 0, -1, -2 ] ], 1.25 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 4, -5, -2, 1, -1, -1 ], [ 7, -4, -2, -1, -1, -2 ], [ 6, -4, -2, 0, -1, -2 ] ], 1.625 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 4, -5, -2, 1, -1, -1 ], [ 7, -4, -2, -1, -1, -2 ], [ 5, -6, -2, 1, -1, 0 ] ], 1 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 4, -5, -2, 1, -1, -1 ], [ 6, -6, -2, 1, -1, -1 ], [ 5, -6, -2, 1, -1, 0 ] ], 1.625 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 4, -5, -2, 1, -1, -1 ], [ 5, -6, -1, 1, -1, 0 ], [ 5, -6, -2, 1, -1, 0 ] ], 1.25 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 4, -5, -2, 1, -1, -1 ], [ 5, -6, -1, 1, -1, 0 ], [ 5, -6, -2, 2, -1, -1 ] ], 1 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 4, -5, -2, 1, -1, -1 ], [ 5, -6, -1, 1, -1, 0 ], [ 5, -5, -1, 1, -1, -1 ] ], 1.625 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 4, -6, -1, 1, -1, -1 ], [ 5, -6, -1, 1, -1, 0 ], [ 5, -5, -1, 1, -1, -1 ] ], 1.25 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -7, -2, 1, -1, -1 ], [ 5, -6, -1, 1, -1, 0 ], [ 5, -5, -1, 1, -1, -1 ] ], 0.875 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -7, -2, 1, -1, -1 ], [ 6, -5, -1, 0, -1, -1 ], [ 5, -5, -1, 1, -1, -1 ] ], 1.25 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -7, -2, 1, -1, -1 ], [ 6, -5, -1, 0, -1, -1 ], [ 6, -6, -2, 1, -1, -1 ] ], 0.75 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -7, -2, 1, -1, -1 ], [ 7, -7, -2, 1, -1, -1 ], [ 6, -6, -2, 1, -1, -1 ] ], 1.125 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -7, -2, 1, -1, -1 ], [ 4, -6, -2, 1, -1, -1 ], [ 6, -6, -2, 1, -1, -1 ] ], 1.625 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -7, -2, 1, -1, -1 ], [ 4, -6, -2, 1, -1, -1 ], [ 7, -7, -3, 1, -1, -1 ] ], 1.125 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -7, -2, 1, -1, -1 ], [ 4, -6, -2, 1, -1, -1 ], [ 6, -7, -2, 1, -1, 0 ] ], 1.375 ] + ], + [ + [ [ [ 5, -6, -2, 1, -1, -1 ], [ 5, -7, -2, 1, -1, -1 ], [ 4, -6, -2, 1, -1, -1 ], [ 6, -6, -1, 1, -1, -1 ] ], 1.75 ] + ], + [ + [ [ [ 4, -6, -1, 1, -1, 0 ], [ 5, -7, -2, 1, -1, -1 ], [ 4, -6, -2, 1, -1, -1 ], [ 6, -6, -1, 1, -1, -1 ] ], 1.125 ] + ], + [ + [ [ [ 4, -6, -1, 1, -1, 0 ], [ 5, -7, -2, 1, -1, -1 ], [ 4, -6, -2, 1, -1, -1 ], [ 7, -7, -2, 1, -1, -1 ] ], 1.25 ] + ], + [ + [ [ [ 4, -6, -1, 1, -1, 0 ], [ 5, -7, -2, 1, -1, -1 ], [ 4, -7, -2, 1, -1, 0 ], [ 7, -7, -2, 1, -1, -1 ] ], 0.75 ] + ], + [ + [ [ [ 4, -6, -1, 1, -1, 0 ], [ 5, -7, -2, 1, -1, -1 ], [ 4, -7, -2, 1, -1, 0 ], [ 6, -7, -1, 1, -1, 0 ] ], 1 ] + ], + [ + [ [ [ 4, -6, -1, 1, -1, 0 ], [ 5, -7, -2, 1, -1, -1 ], [ 4, -7, -2, 1, -1, 0 ], [ 5, -6, -1, 1, 0, 0 ] ], 1.125 ] + ], + [ + [ [ [ 4, -6, -1, 1, -1, 0 ], [ 4, -7, -1, 1, -1, 0 ], [ 4, -7, -2, 1, -1, 0 ], [ 5, -6, -1, 1, 0, 0 ] ], 0.625 ] + ], + [ + [ [ [ 4, -6, -1, 1, -1, 0 ], [ 4, -7, -1, 1, -1, 0 ], [ 3, -5, -1, 1, 0, 0 ], [ 5, -6, -1, 1, 0, 0 ] ], 1.25 ] + ], + [ + [ [ [ 4, -6, -1, 1, -1, 0 ], [ 4, -7, -1, 1, -1, 0 ], [ 3, -5, -1, 1, 0, 0 ], [ 5, -6, -2, 1, -1, 0 ] ], 1.25 ] + ], + [ + [ [ [ 4, -6, -1, 1, -1, 0 ], [ 4, -7, -1, 1, -1, 0 ], [ 3, -5, -1, 1, 0, 0 ], [ 6, -7, -1, 1, -1, -1 ] ], 1 ], + [ [ [ 4, -6, -1, 1, -1, 0 ], [ "Rest" ], [ 3, -5, -1, 1, 0, 0 ], [ 6, -7, -1, 1, -1, -1 ] ], 1.375 ], + [ [ [ 4, -6, -1, 1, -1, 0 ], [ "Rest" ], [ 3, -5, -1, 1, 0, 0 ], [ "Rest" ] ], 1.125 ], + [ [ [ "Rest" ], [ "Rest" ], [ 3, -5, -1, 1, 0, 0 ], [ "Rest" ] ], 1.5 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 6.375 ] + ] + ] +], +"last_changes": +[ + [ [ 4, -6, -1, 1, -1, 0 ], [ 5, -7, -2, 1, -1, -1 ], [ 4, -7, -2, 1, -1, 0 ], [ 5, -6, -1, 1, 0, 0 ] ], + [ [ 4, -6, -1, 1, -1, 0 ], [ 4, -7, -1, 1, -1, 0 ], [ 4, -7, -2, 1, -1, 0 ], [ 5, -6, -1, 1, 0, 0 ] ], + [ [ 4, -6, -1, 1, -1, 0 ], [ 4, -7, -1, 1, -1, 0 ], [ 3, -5, -1, 1, 0, 0 ], [ 5, -6, -1, 1, 0, 0 ] ], + [ [ 4, -6, -1, 1, -1, 0 ], [ 4, -7, -1, 1, -1, 0 ], [ 3, -5, -1, 1, 0, 0 ], [ 5, -6, -2, 1, -1, 0 ] ], + [ [ 4, -6, -1, 1, -1, 0 ], [ 4, -7, -1, 1, -1, 0 ], [ 3, -5, -1, 1, 0, 0 ], [ 6, -7, -1, 1, -1, -1 ] ] +], +"cur_uid": "45fa07e8", +"ref_uid": "55f9b81e", +"order_seed": 921383, +"dur_seed": 545846, +"motifs_seed": 212473, +"entrances_probs_vals": [ 0, 0, 0, 0.66, 1.8406593406593, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 0.66, 1.8406593406593, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 0.66, 1.8406593406593, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2467, 2400 ], [ -1167, 2400 ], [ -702, 2400 ], [ -702, 2400 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.090534979423868, 0.92613636363636, 0.17489711934156, 0.079545454545455, 0.37037037037037, 0, 0.7201646090535, 0, 1, 0 ], +"passages_weights": [ 0.63, 0.62, 1, 0.41, 1 ], +"hd_exp": 6, +"hd_invert": 0, +"order": +[ + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ] +], +"sus_weights": [ 0, 0, 0.61 ], +"order_size": [ 100, 100 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/4ff624b0/4ff624b0_code.scd b/resources/string_quartet_3/4ff624b0/4ff624b0_code.scd new file mode 100644 index 0000000..0d8c24b --- /dev/null +++ b/resources/string_quartet_3/4ff624b0/4ff624b0_code.scd @@ -0,0 +1,975 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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/string_quartet_3/4ff624b0/4ff624b0_mus_model.json b/resources/string_quartet_3/4ff624b0/4ff624b0_mus_model.json new file mode 100644 index 0000000..0ef2a04 --- /dev/null +++ b/resources/string_quartet_3/4ff624b0/4ff624b0_mus_model.json @@ -0,0 +1,552 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, -1, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, 0, -1, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, 0, -1, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, -1, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 0, 1, 0, -1, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, -1, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, -1, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ], [ 1, -1, -1, 0, 0, 0 ] ], 1 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 1, -1, -1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 1, -1, -1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -2, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ -2, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ -2, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ -2, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -1, 2, 0, 0, 0, 0 ], [ -2, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -1, 2, 0, 0, 0, 0 ], [ -1, 1, -1, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 2, 0, 0, 0, 0 ], [ -1, 1, -1, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 1 ], + [ [ [ 0, 1, -1, 0, 0, 0 ], [ -1, 1, -1, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 1, -1, 0, 0, 0 ], [ 0, 1, -1, -1, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 1 ], + [ [ [ 0, 1, -1, 0, 0, 0 ], [ 0, 1, -1, -1, 0, 0 ], [ -1, 2, -1, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 3, -1, 0, 0, 0 ], [ 0, 1, -1, -1, 0, 0 ], [ -1, 2, -1, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 3, -1, 0, 0, 0 ], [ 0, 1, -1, -1, 0, 0 ], [ -1, 2, -1, 0, 0, 0 ], [ -2, 3, -1, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 3, -1, 0, 0, 0 ], [ 0, 1, -1, -1, 0, 0 ], [ -1, 3, -1, 0, -1, 0 ], [ -2, 3, -1, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 3, -1, 0, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ], [ -1, 3, -1, 0, -1, 0 ], [ -2, 3, -1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 3, -1, 0, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ], [ -1, 3, -1, 0, -1, 0 ], [ -2, 4, -1, 0, -1, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 3, -1, 0, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ], [ -1, 3, -1, 0, -1, 0 ], [ 0, 3, 0, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 3, -1, -1, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ], [ -1, 3, -1, 0, -1, 0 ], [ 0, 3, 0, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 3, -1, -1, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ], [ 0, 2, -1, -1, 0, 0 ], [ 0, 3, 0, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 3, -1, -1, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ], [ -1, 4, -1, -1, 0, 0 ], [ 0, 3, 0, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 3, -1, -1, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ], [ -1, 4, -1, -1, 0, 0 ], [ 0, 2, -1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 3, -1, -1, 0, 0 ], [ -1, 2, -1, -1, 0, 0 ], [ -1, 4, -1, -1, 0, 0 ], [ 0, 2, -1, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 3, -1, -1, 0, 0 ], [ -2, 4, -1, -1, 0, 0 ], [ -1, 4, -1, -1, 0, 0 ], [ 0, 2, -1, -1, 0, 0 ] ], 1 ], + [ [ [ -2, 5, -1, -1, 0, 0 ], [ -2, 4, -1, -1, 0, 0 ], [ -1, 4, -1, -1, 0, 0 ], [ 0, 2, -1, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 5, -1, -1, 0, 0 ], [ -2, 4, -1, -1, 0, 0 ], [ -1, 4, -1, -1, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ] ], 1 ], + [ [ [ -2, 5, -1, -1, 0, 0 ], [ -3, 6, -1, -1, 0, 0 ], [ -1, 4, -1, -1, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 5, -1, -1, 0, 0 ], [ -3, 6, -1, -1, 0, 0 ], [ -1, 4, -1, -1, 0, 0 ], [ -3, 7, -1, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 5, -1, -1, 0, 0 ], [ -3, 6, -1, -1, 0, 0 ], [ -2, 6, -1, -1, 0, 0 ], [ -3, 7, -1, -1, 0, 0 ] ], 1 ], + [ [ [ -2, 5, -1, -1, 0, 0 ], [ -4, 8, -1, -1, 0, 0 ], [ -2, 6, -1, -1, 0, 0 ], [ -3, 7, -1, -1, 0, 0 ] ], 1 ], + [ [ [ -3, 6, -1, -1, 0, 0 ], [ -4, 8, -1, -1, 0, 0 ], [ -2, 6, -1, -1, 0, 0 ], [ -3, 7, -1, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 7, -1, -1, 0, 0 ], [ -4, 8, -1, -1, 0, 0 ], [ -2, 6, -1, -1, 0, 0 ], [ -3, 7, -1, -1, 0, 0 ] ], 1 ], + [ [ [ -4, 7, -1, -1, 0, 0 ], [ -2, 7, -1, -1, 0, 0 ], [ -2, 6, -1, -1, 0, 0 ], [ -3, 7, -1, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 6, -1, -1, 0, 0 ], [ -2, 7, -1, -1, 0, 0 ], [ -2, 6, -1, -1, 0, 0 ], [ -3, 7, -1, -1, 0, 0 ] ], 1 ], + [ [ [ -4, 6, -1, -1, 0, 0 ], [ -2, 7, -1, -1, 0, 0 ], [ -3, 8, -1, -1, 0, 0 ], [ -3, 7, -1, -1, 0, 0 ] ], 1 ], + [ [ [ -4, 6, -1, -1, 0, 0 ], [ -2, 6, -1, -1, 0, 0 ], [ -3, 8, -1, -1, 0, 0 ], [ -3, 7, -1, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 6, -1, -1, 0, 0 ], [ -2, 6, -1, -1, 0, 0 ], [ -3, 8, -1, -1, 0, 0 ], [ -3, 7, -1, -1, 0, 0 ] ], 1 ], + [ [ [ -3, 6, -1, -1, 0, 0 ], [ -2, 6, -1, -1, 0, 0 ], [ -3, 7, -1, -1, 0, 1 ], [ -3, 7, -1, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 6, -1, -1, 0, 0 ], [ -2, 6, -1, -1, 0, 0 ], [ -3, 7, -1, -1, 0, 1 ], [ -3, 6, -1, -1, 0, 1 ] ], 1 ], + [ [ [ -3, 6, -1, -1, 0, 0 ], [ -4, 7, -1, 0, 0, 1 ], [ -3, 7, -1, -1, 0, 1 ], [ -3, 6, -1, -1, 0, 1 ] ], 1 ], + [ [ [ -5, 7, -1, 0, 0, 1 ], [ -4, 7, -1, 0, 0, 1 ], [ -3, 7, -1, -1, 0, 1 ], [ -3, 6, -1, -1, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -4, 7, -1, -1, 0, 1 ], [ -4, 7, -1, 0, 0, 1 ], [ -3, 7, -1, -1, 0, 1 ], [ -3, 6, -1, -1, 0, 1 ] ], 1 ], + [ [ [ -4, 7, -1, -1, 0, 1 ], [ -4, 7, -1, 0, 0, 1 ], [ -4, 7, 0, 0, 0, 1 ], [ -3, 6, -1, -1, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -4, 7, -1, -1, 0, 1 ], [ -4, 7, -1, 0, 0, 1 ], [ -3, 6, -1, 0, 0, 1 ], [ -3, 6, -1, -1, 0, 1 ] ], 1 ], + [ [ [ -4, 7, -1, -1, 0, 1 ], [ -3, 7, -1, -1, 0, 1 ], [ -3, 6, -1, 0, 0, 1 ], [ -3, 6, -1, -1, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -3, 5, -1, -1, 0, 1 ], [ -3, 7, -1, -1, 0, 1 ], [ -3, 6, -1, 0, 0, 1 ], [ -3, 6, -1, -1, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -3, 5, -1, -1, 0, 1 ], [ -3, 7, -1, -1, 0, 1 ], [ -3, 6, -1, 0, 0, 1 ], [ -3, 5, -1, 0, 0, 1 ] ], 1 ], + [ [ [ -3, 5, -1, -1, 0, 1 ], [ -2, 5, -1, -1, 0, 1 ], [ -3, 6, -1, 0, 0, 1 ], [ -3, 5, -1, 0, 0, 1 ] ], 1 ], + [ [ [ -3, 5, -1, -1, 0, 1 ], [ -2, 5, -1, -1, 0, 1 ], [ -2, 5, 0, -1, 0, 1 ], [ -3, 5, -1, 0, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -3, 5, -1, -1, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -2, 5, 0, -1, 0, 1 ], [ -3, 5, -1, 0, 0, 1 ] ], 1 ], + [ [ [ -3, 5, -1, -1, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -1, 4, -1, -1, 0, 1 ], [ -3, 5, -1, 0, 0, 1 ] ], 1 ], + [ [ [ -3, 5, -1, -1, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -1, 4, -1, -1, 0, 1 ], [ -2, 5, -1, -1, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -2, 5, -1, -2, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -1, 4, -1, -1, 0, 1 ], [ -2, 5, -1, -1, 0, 1 ] ], 1 ], + [ [ [ -2, 5, -1, -2, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -1, 4, -1, -2, 0, 1 ], [ -2, 5, -1, -1, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -2, 5, -1, -2, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -2, 6, -1, -2, 0, 1 ], [ -2, 5, -1, -1, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -2, 5, -1, -2, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -1, 5, -2, -2, 0, 1 ], [ -2, 5, -1, -1, 0, 1 ] ], 1 ], + [ [ [ -2, 4, -1, -2, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -1, 5, -2, -2, 0, 1 ], [ -2, 5, -1, -1, 0, 1 ] ], 1 ], + [ [ [ -2, 4, -1, -2, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -1, 5, -2, -2, 0, 1 ], [ -1, 4, -1, -2, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -3, 6, -1, -2, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -1, 5, -2, -2, 0, 1 ], [ -1, 4, -1, -2, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -3, 6, -1, -2, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -1, 5, -2, -2, 0, 1 ], [ -2, 6, -1, -2, 0, 1 ] ], 1 ], + [ [ [ -3, 6, -1, -2, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -2, 5, -1, -2, 0, 1 ], [ -2, 6, -1, -2, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -3, 6, -1, -2, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -2, 5, -1, -2, 0, 1 ], [ -1, 5, -2, -2, 0, 1 ] ], 1 ], + [ [ [ -3, 6, -1, -2, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -1, 5, -1, -3, 0, 1 ], [ -1, 5, -2, -2, 0, 1 ] ], 1 ], + [ [ [ -2, 5, -2, -2, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -1, 5, -1, -3, 0, 1 ], [ -1, 5, -2, -2, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -2, 5, -2, -3, 0, 1 ], [ -1, 5, -1, -2, 0, 1 ], [ -1, 5, -1, -3, 0, 1 ], [ -1, 5, -2, -2, 0, 1 ] ], 1 ], + [ [ [ -2, 5, -2, -3, 0, 1 ], [ 0, 5, -1, -3, 0, 1 ], [ -1, 5, -1, -3, 0, 1 ], [ -1, 5, -2, -2, 0, 1 ] ], 1 ], + [ [ [ -2, 5, -2, -3, 0, 1 ], [ 0, 5, -1, -3, 0, 1 ], [ -1, 5, -1, -3, 0, 1 ], [ -1, 6, -1, -3, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -2, 5, -2, -3, 0, 1 ], [ 0, 5, -1, -3, 0, 1 ], [ -1, 5, -1, -3, 0, 1 ], [ -1, 5, -2, -3, 0, 1 ] ], 1 ], + [ [ [ -2, 5, -2, -3, 0, 1 ], [ 1, 4, -2, -3, 0, 1 ], [ -1, 5, -1, -3, 0, 1 ], [ -1, 5, -2, -3, 0, 1 ] ], 1 ], + [ [ [ -2, 5, -2, -3, 0, 1 ], [ 1, 4, -2, -3, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ -1, 5, -2, -3, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -2, 5, -2, -3, 0, 1 ], [ 0, 5, -2, -3, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ -1, 5, -2, -3, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -2, 5, -2, -3, 0, 1 ], [ 1, 5, -2, -4, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ -1, 5, -2, -3, 0, 1 ] ], 1 ], + [ [ [ -1, 5, -2, -4, 0, 1 ], [ 1, 5, -2, -4, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ -1, 5, -2, -3, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 5, -2, -4, 0, 1 ], [ 1, 5, -2, -4, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 0, 5, -2, -4, 0, 1 ] ], 1 ], + [ [ [ -1, 5, -2, -4, 0, 1 ], [ 0, 6, -2, -4, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 0, 5, -2, -4, 0, 1 ] ], 1 ], + [ [ [ -1, 5, -2, -4, 0, 1 ], [ 0, 6, -2, -4, 0, 1 ], [ 0, 5, -1, -4, 0, 1 ], [ 0, 5, -2, -4, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -2, 5, -1, -4, 0, 1 ], [ 0, 6, -2, -4, 0, 1 ], [ 0, 5, -1, -4, 0, 1 ], [ 0, 5, -2, -4, 0, 1 ] ], 1 ], + [ [ [ -2, 5, -1, -4, 0, 1 ], [ 0, 5, -1, -4, 1, 1 ], [ 0, 5, -1, -4, 0, 1 ], [ 0, 5, -2, -4, 0, 1 ] ], 1 ], + [ [ [ -2, 5, -1, -4, 0, 1 ], [ 0, 5, -1, -4, 1, 1 ], [ 0, 5, -1, -4, 0, 1 ], [ -1, 5, -1, -4, 1, 1 ] ], 1 ] + ], + [ + [ [ [ -2, 5, -1, -4, 0, 1 ], [ -1, 6, -1, -4, 0, 1 ], [ 0, 5, -1, -4, 0, 1 ], [ -1, 5, -1, -4, 1, 1 ] ], 1 ], + [ [ [ -1, 5, -1, -5, 0, 1 ], [ -1, 6, -1, -4, 0, 1 ], [ 0, 5, -1, -4, 0, 1 ], [ -1, 5, -1, -4, 1, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 5, -1, -5, 0, 1 ], [ -1, 6, -1, -4, 0, 1 ], [ 0, 5, -1, -4, 0, 1 ], [ 1, 4, -1, -5, 0, 1 ] ], 1 ], + [ [ [ -1, 5, -1, -5, 0, 1 ], [ 0, 6, -1, -5, 0, 1 ], [ 0, 5, -1, -4, 0, 1 ], [ 1, 4, -1, -5, 0, 1 ] ], 1 ], + [ [ [ -1, 5, -1, -5, 0, 1 ], [ 0, 6, -1, -5, 0, 1 ], [ 1, 5, -1, -5, 0, 1 ], [ 1, 4, -1, -5, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 5, -1, -5, 0, 1 ], [ 0, 5, -1, -5, 0, 1 ], [ 1, 5, -1, -5, 0, 1 ], [ 1, 4, -1, -5, 0, 1 ] ], 1 ], + [ [ [ 0, 4, -1, -5, 0, 1 ], [ 0, 5, -1, -5, 0, 1 ], [ 1, 5, -1, -5, 0, 1 ], [ 1, 4, -1, -5, 0, 1 ] ], 1 ], + [ [ [ 0, 4, -1, -5, 0, 1 ], [ 0, 5, -1, -5, 0, 1 ], [ 2, 4, -1, -5, 0, 1 ], [ 1, 4, -1, -5, 0, 1 ] ], 1 ] + ], + [ + [ [ [ 0, 4, -1, -5, 0, 1 ], [ 0, 5, -1, -5, 0, 1 ], [ 2, 4, -1, -5, 0, 1 ], [ 0, 6, -1, -5, 0, 1 ] ], 1 ], + [ [ [ 0, 4, -1, -5, 0, 1 ], [ 0, 5, -1, -5, 0, 1 ], [ 1, 6, -1, -5, 0, 1 ], [ 0, 6, -1, -5, 0, 1 ] ], 1 ] + ], + [ + [ [ [ 0, 4, -1, -5, 0, 1 ], [ 0, 5, -1, -5, 0, 1 ], [ 1, 6, -1, -5, 0, 1 ], [ 2, 4, -1, -5, 0, 0 ] ], 1 ], + [ [ [ 0, 4, -1, -5, 0, 1 ], [ 1, 4, -2, -5, 0, 1 ], [ 1, 6, -1, -5, 0, 1 ], [ 2, 4, -1, -5, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 4, -1, -5, 0, 1 ], [ 1, 4, -2, -5, 0, 1 ], [ 1, 6, -1, -5, 0, 1 ], [ 1, 6, -1, -5, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 4, -1, -5, 0, 1 ], [ 1, 5, -1, -5, 0, 0 ], [ 1, 6, -1, -5, 0, 1 ], [ 1, 6, -1, -5, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 4, -1, -5, 0, 1 ], [ 1, 4, -1, -5, 0, 1 ], [ 1, 6, -1, -5, 0, 1 ], [ 1, 6, -1, -5, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 5, -1, -5, 0, 1 ], [ 1, 4, -1, -5, 0, 1 ], [ 1, 6, -1, -5, 0, 1 ], [ 1, 6, -1, -5, 0, 0 ] ], 1 ], + [ [ [ -1, 5, -1, -5, 0, 1 ], [ 1, 4, -1, -5, 0, 1 ], [ 3, 4, -1, -5, 0, 0 ], [ 1, 6, -1, -5, 0, 0 ] ], 1 ], + [ [ [ -1, 5, -1, -5, 0, 1 ], [ 1, 4, -1, -5, 0, 1 ], [ 3, 4, -1, -5, 0, 0 ], [ 1, 5, -1, -5, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 5, -1, -5, 0, 1 ], [ 1, 5, -1, -5, -1, 1 ], [ 3, 4, -1, -5, 0, 0 ], [ 1, 5, -1, -5, 0, 1 ] ], 1 ], + [ [ [ 0, 4, -1, -5, 1, 0 ], [ 1, 5, -1, -5, -1, 1 ], [ 3, 4, -1, -5, 0, 0 ], [ 1, 5, -1, -5, 0, 1 ] ], 1 ] + ], + [ + [ [ [ 0, 4, -1, -5, 1, 0 ], [ 1, 5, -1, -5, -1, 1 ], [ 2, 4, -1, -5, -1, 1 ], [ 1, 5, -1, -5, 0, 1 ] ], 1 ], + [ [ [ 0, 4, -1, -5, 1, 0 ], [ 1, 5, -1, -5, -1, 1 ], [ 2, 4, -1, -5, -1, 1 ], [ 0, 5, -1, -4, -1, 1 ] ], 1 ], + [ [ [ -1, 5, -1, -4, -1, 1 ], [ 1, 5, -1, -5, -1, 1 ], [ 2, 4, -1, -5, -1, 1 ], [ 0, 5, -1, -4, -1, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 5, -1, -4, -1, 1 ], [ 1, 5, -1, -5, -1, 1 ], [ 2, 4, -1, -5, -1, 1 ], [ -1, 5, -1, -3, -1, 1 ] ], 1 ], + [ [ [ -1, 5, -1, -4, -1, 1 ], [ 1, 5, -1, -5, -1, 1 ], [ 0, 5, -1, -3, -1, 1 ], [ -1, 5, -1, -3, -1, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 5, -1, -4, -1, 1 ], [ 1, 5, -1, -5, -1, 1 ], [ 1, 5, -1, -4, -1, 1 ], [ -1, 5, -1, -3, -1, 1 ] ], 1 ], + [ [ [ -1, 5, -1, -4, -1, 1 ], [ 1, 5, -1, -5, -1, 1 ], [ 1, 5, -1, -4, -1, 1 ], [ 0, 5, -1, -4, -1, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 5, -1, -4, -1, 1 ], [ 1, 5, -1, -5, -1, 1 ], [ 2, 5, -1, -5, -1, 1 ], [ 0, 5, -1, -4, -1, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 5, -1, -4, -1, 1 ], [ 1, 5, -1, -5, -1, 1 ], [ 2, 5, -1, -5, -1, 1 ], [ 1, 6, -1, -5, -1, 1 ] ], 1 ], + [ [ [ 0, 5, -1, -5, -1, 1 ], [ 1, 5, -1, -5, -1, 1 ], [ 2, 5, -1, -5, -1, 1 ], [ 1, 6, -1, -5, -1, 1 ] ], 1 ], + [ [ [ 0, 5, -1, -5, -1, 1 ], [ 1, 5, -1, -5, -1, 1 ], [ 2, 5, -1, -5, -2, 1 ], [ 1, 6, -1, -5, -1, 1 ] ], 1 ] + ], + [ + [ [ [ 0, 5, -1, -5, -1, 1 ], [ 1, 6, -1, -5, -2, 1 ], [ 2, 5, -1, -5, -2, 1 ], [ 1, 6, -1, -5, -1, 1 ] ], 1 ] + ], + [ + [ [ [ 0, 6, -1, -5, -2, 1 ], [ 1, 6, -1, -5, -2, 1 ], [ 2, 5, -1, -5, -2, 1 ], [ 1, 6, -1, -5, -1, 1 ] ], 1 ] + ], + [ + [ [ [ 0, 6, -1, -5, -2, 1 ], [ 0, 6, -1, -5, -1, 1 ], [ 2, 5, -1, -5, -2, 1 ], [ 1, 6, -1, -5, -1, 1 ] ], 1 ], + [ [ [ 0, 6, -2, -5, -1, 1 ], [ 0, 6, -1, -5, -1, 1 ], [ 2, 5, -1, -5, -2, 1 ], [ 1, 6, -1, -5, -1, 1 ] ], 1 ], + [ [ [ 0, 6, -2, -5, -1, 1 ], [ 0, 6, -1, -5, -1, 1 ], [ 1, 6, -2, -5, -1, 1 ], [ 1, 6, -1, -5, -1, 1 ] ], 1 ] + ], + [ + [ [ [ 0, 6, -2, -5, -1, 1 ], [ 0, 6, -1, -5, -1, 1 ], [ 1, 6, -2, -5, -1, 1 ], [ 2, 5, -2, -5, -1, 1 ] ], 1 ], + [ [ [ -1, 7, -2, -5, -1, 1 ], [ 0, 6, -1, -5, -1, 1 ], [ 1, 6, -2, -5, -1, 1 ], [ 2, 5, -2, -5, -1, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 7, -2, -5, -1, 1 ], [ 0, 6, -1, -5, -1, 1 ], [ 1, 6, -2, -5, -1, 1 ], [ 0, 7, -2, -5, -1, 1 ] ], 1 ], + [ [ [ -1, 7, -2, -5, -1, 1 ], [ 0, 6, -1, -5, -1, 1 ], [ 1, 5, -1, -5, -1, 1 ], [ 0, 7, -2, -5, -1, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 7, -2, -5, -1, 1 ], [ -1, 8, -2, -5, -1, 1 ], [ 1, 5, -1, -5, -1, 1 ], [ 0, 7, -2, -5, -1, 1 ] ], 1 ], + [ [ [ -1, 7, -2, -5, -1, 1 ], [ -1, 8, -2, -5, -1, 1 ], [ 0, 8, -2, -5, -1, 1 ], [ 0, 7, -2, -5, -1, 1 ] ], 1 ] + ], + [ + [ [ [ -2, 8, -2, -5, -1, 1 ], [ -1, 8, -2, -5, -1, 1 ], [ 0, 8, -2, -5, -1, 1 ], [ 0, 7, -2, -5, -1, 1 ] ], 1 ], + [ [ [ -2, 8, -2, -5, -1, 1 ], [ 0, 8, -2, -6, -1, 1 ], [ 0, 8, -2, -5, -1, 1 ], [ 0, 7, -2, -5, -1, 1 ] ], 1 ], + [ [ [ -2, 8, -2, -5, -1, 1 ], [ 0, 8, -2, -6, -1, 1 ], [ 0, 8, -2, -5, -1, 1 ], [ -1, 9, -2, -5, -1, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 8, -2, -6, -1, 1 ], [ 0, 8, -2, -6, -1, 1 ], [ 0, 8, -2, -5, -1, 1 ], [ -1, 9, -2, -5, -1, 1 ] ], 1 ], + [ [ [ -1, 8, -2, -6, -1, 1 ], [ 0, 8, -2, -6, -1, 1 ], [ 0, 8, -2, -5, -1, 1 ], [ 0, 8, -3, -5, -1, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 8, -2, -6, -1, 1 ], [ 0, 8, -2, -6, -1, 1 ], [ 1, 8, -2, -6, -1, 1 ], [ 0, 8, -3, -5, -1, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 8, -2, -6, -1, 1 ], [ 0, 8, -2, -6, -1, 1 ], [ 0, 9, -3, -5, -1, 1 ], [ 0, 8, -3, -5, -1, 1 ] ], 1 ], + [ [ [ -2, 8, -3, -5, -1, 2 ], [ 0, 8, -2, -6, -1, 1 ], [ 0, 9, -3, -5, -1, 1 ], [ 0, 8, -3, -5, -1, 1 ] ], 1 ], + [ [ [ -2, 8, -3, -5, -1, 2 ], [ -1, 8, -3, -5, -1, 2 ], [ 0, 9, -3, -5, -1, 1 ], [ 0, 8, -3, -5, -1, 1 ] ], 1 ] + ], + [ + [ [ [ -2, 8, -3, -5, -1, 2 ], [ -1, 8, -3, -5, -1, 2 ], [ 0, 8, -3, -5, -1, 2 ], [ 0, 8, -3, -5, -1, 1 ] ], 1 ], + [ [ [ -2, 8, -3, -5, -1, 2 ], [ -1, 8, -3, -5, -1, 2 ], [ 0, 8, -3, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -2, 8, -3, -5, -1, 2 ], [ -1, 8, -3, -5, -1, 2 ], [ 1, 8, -3, -6, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ], + [ [ [ -1, 8, -3, -6, -1, 2 ], [ -1, 8, -3, -5, -1, 2 ], [ 1, 8, -3, -6, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 7, -3, -6, -1, 2 ], [ -1, 8, -3, -5, -1, 2 ], [ 1, 8, -3, -6, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ], + [ [ [ -1, 7, -3, -6, -1, 2 ], [ -1, 8, -3, -5, -1, 2 ], [ -1, 8, -2, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 7, -3, -6, -1, 2 ], [ -2, 8, -2, -4, -1, 2 ], [ -1, 8, -2, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ], + [ [ [ -2, 7, -2, -5, -1, 2 ], [ -2, 8, -2, -4, -1, 2 ], [ -1, 8, -2, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -2, 7, -2, -5, -1, 2 ], [ -2, 8, -1, -5, -1, 2 ], [ -1, 8, -2, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -2, 7, -2, -5, -1, 2 ], [ -1, 7, -2, -5, -1, 2 ], [ -1, 8, -2, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ], + [ [ [ -3, 9, -2, -5, -1, 2 ], [ -1, 7, -2, -5, -1, 2 ], [ -1, 8, -2, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -3, 9, -2, -5, -1, 2 ], [ 0, 6, -3, -5, -1, 2 ], [ -1, 8, -2, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ], + [ [ [ -3, 9, -2, -5, -1, 2 ], [ 0, 6, -3, -5, -1, 2 ], [ -1, 8, -3, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ], + [ [ [ -2, 8, -3, -5, -1, 2 ], [ 0, 6, -3, -5, -1, 2 ], [ -1, 8, -3, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -2, 7, -3, -5, -1, 2 ], [ 0, 6, -3, -5, -1, 2 ], [ -1, 8, -3, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ], + [ [ [ -2, 7, -3, -5, -1, 2 ], [ 0, 6, -3, -5, -1, 2 ], [ 0, 7, -4, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 7, -3, -6, -1, 2 ], [ 0, 6, -3, -5, -1, 2 ], [ 0, 7, -4, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ], + [ [ [ -1, 7, -3, -6, -1, 2 ], [ -1, 8, -3, -5, -1, 2 ], [ 0, 7, -4, -5, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ], + [ [ [ -1, 7, -3, -6, -1, 2 ], [ -1, 8, -3, -5, -1, 2 ], [ -1, 7, -3, -4, -1, 2 ], [ 0, 7, -3, -5, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 7, -3, -6, -1, 2 ], [ -1, 8, -3, -5, -1, 2 ], [ -1, 7, -3, -4, -1, 2 ], [ -1, 7, -2, -4, -1, 2 ] ], 1 ], + [ [ [ -1, 7, -3, -6, -1, 2 ], [ -1, 6, -3, -4, -1, 2 ], [ -1, 7, -3, -4, -1, 2 ], [ -1, 7, -2, -4, -1, 2 ] ], 1 ], + [ [ [ -2, 6, -3, -4, -1, 2 ], [ -1, 6, -3, -4, -1, 2 ], [ -1, 7, -3, -4, -1, 2 ], [ -1, 7, -2, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -2, 6, -3, -4, -1, 2 ], [ -1, 6, -3, -4, -1, 2 ], [ 0, 6, -4, -4, -1, 2 ], [ -1, 7, -2, -4, -1, 2 ] ], 1 ], + [ [ [ -1, 6, -3, -5, -1, 2 ], [ -1, 6, -3, -4, -1, 2 ], [ 0, 6, -4, -4, -1, 2 ], [ -1, 7, -2, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 6, -3, -5, -1, 2 ], [ -1, 6, -3, -4, -1, 2 ], [ -1, 6, -3, -3, -1, 2 ], [ -1, 7, -2, -4, -1, 2 ] ], 1 ], + [ [ [ -1, 5, -3, -4, -1, 2 ], [ -1, 6, -3, -4, -1, 2 ], [ -1, 6, -3, -3, -1, 2 ], [ -1, 7, -2, -4, -1, 2 ] ], 1 ], + [ [ [ -1, 5, -3, -4, -1, 2 ], [ -1, 6, -3, -4, -1, 2 ], [ -1, 6, -3, -3, -1, 2 ], [ 0, 6, -3, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 5, -3, -4, -1, 2 ], [ 0, 5, -4, -4, -1, 2 ], [ -1, 6, -3, -3, -1, 2 ], [ 0, 6, -3, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 5, -3, -4, -1, 2 ], [ 0, 5, -4, -4, -1, 2 ], [ 0, 5, -3, -4, -1, 2 ], [ 0, 6, -3, -4, -1, 2 ] ], 1 ], + [ [ [ -1, 5, -3, -4, -1, 2 ], [ 0, 5, -4, -4, -1, 2 ], [ 0, 5, -3, -4, -1, 2 ], [ 1, 5, -4, -4, -1, 2 ] ], 1 ], + [ [ [ -1, 5, -3, -4, -1, 2 ], [ -1, 5, -3, -3, -1, 2 ], [ 0, 5, -3, -4, -1, 2 ], [ 1, 5, -4, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 5, -3, -4, -1, 2 ], [ 1, 5, -3, -4, -1, 2 ], [ 0, 5, -3, -4, -1, 2 ], [ 1, 5, -4, -4, -1, 2 ] ], 1 ], + [ [ [ -1, 5, -3, -4, -1, 2 ], [ 1, 5, -3, -4, -1, 2 ], [ 0, 5, -3, -4, -1, 2 ], [ 0, 5, -2, -4, -1, 2 ] ], 1 ], + [ [ [ -1, 5, -3, -4, -1, 2 ], [ 1, 5, -3, -4, -1, 2 ], [ 1, 5, -3, -5, -1, 2 ], [ 0, 5, -2, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 5, -3, -4, -1, 2 ], [ 1, 5, -3, -4, -1, 2 ], [ 1, 4, -3, -4, -1, 2 ], [ 0, 5, -2, -4, -1, 2 ] ], 1 ], + [ [ [ -1, 5, -3, -4, -1, 2 ], [ 1, 5, -3, -4, -1, 2 ], [ 1, 4, -3, -4, -1, 2 ], [ 0, 5, -3, -4, -1, 2 ] ], 1 ], + [ [ [ -1, 5, -3, -4, -1, 2 ], [ 0, 6, -3, -4, -1, 2 ], [ 1, 4, -3, -4, -1, 2 ], [ 0, 5, -3, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ 0, 4, -4, -4, -1, 2 ], [ 0, 6, -3, -4, -1, 2 ], [ 1, 4, -3, -4, -1, 2 ], [ 0, 5, -3, -4, -1, 2 ] ], 1 ], + [ [ [ 0, 4, -4, -4, -1, 2 ], [ 1, 4, -4, -4, -1, 2 ], [ 1, 4, -3, -4, -1, 2 ], [ 0, 5, -3, -4, -1, 2 ] ], 1 ], + [ [ [ 0, 4, -4, -4, -1, 2 ], [ 1, 4, -4, -4, -1, 2 ], [ 1, 4, -3, -4, -1, 2 ], [ 2, 4, -4, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ 0, 3, -3, -4, -1, 2 ], [ 1, 4, -4, -4, -1, 2 ], [ 1, 4, -3, -4, -1, 2 ], [ 2, 4, -4, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ 0, 3, -4, -4, -1, 2 ], [ 1, 4, -4, -4, -1, 2 ], [ 1, 4, -3, -4, -1, 2 ], [ 2, 4, -4, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ 0, 4, -3, -5, -1, 2 ], [ 1, 4, -4, -4, -1, 2 ], [ 1, 4, -3, -4, -1, 2 ], [ 2, 4, -4, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ 0, 4, -3, -5, -1, 2 ], [ 1, 4, -4, -4, -1, 2 ], [ 1, 4, -3, -4, -1, 2 ], [ 1, 4, -3, -3, -1, 2 ] ], 1 ], + [ [ [ -1, 4, -2, -4, -1, 2 ], [ 1, 4, -4, -4, -1, 2 ], [ 1, 4, -3, -4, -1, 2 ], [ 1, 4, -3, -3, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 4, -2, -4, -1, 2 ], [ 1, 4, -4, -4, -1, 2 ], [ 1, 4, -3, -4, -1, 2 ], [ 1, 5, -2, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 4, -2, -4, -1, 2 ], [ 1, 4, -4, -4, -1, 2 ], [ 1, 4, -3, -4, -1, 2 ], [ 1, 4, -2, -4, -1, 2 ] ], 1 ], + [ [ [ -1, 4, -2, -4, -1, 2 ], [ 1, 4, -4, -4, -1, 2 ], [ 2, 3, -4, -4, -1, 2 ], [ 1, 4, -2, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 4, -2, -4, -1, 2 ], [ 1, 4, -4, -4, -1, 2 ], [ 2, 3, -4, -4, -1, 2 ], [ 1, 3, -2, -4, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 4, -2, -4, -1, 2 ], [ 1, 4, -4, -4, -1, 2 ], [ 2, 3, -4, -4, -1, 2 ], [ 1, 4, -2, -5, -1, 2 ] ], 1 ], + [ [ [ -1, 4, -2, -4, -1, 2 ], [ 0, 4, -2, -4, 0, 2 ], [ 2, 3, -4, -4, -1, 2 ], [ 1, 4, -2, -5, -1, 2 ] ], 1 ], + [ [ [ -1, 4, -2, -4, -1, 2 ], [ 0, 4, -2, -4, 0, 2 ], [ 0, 5, -2, -4, -1, 2 ], [ 1, 4, -2, -5, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 4, -2, -4, -1, 2 ], [ 0, 4, -2, -4, 0, 2 ], [ 1, 4, -2, -4, 0, 1 ], [ 1, 4, -2, -5, -1, 2 ] ], 1 ], + [ [ [ -1, 4, -2, -4, -1, 2 ], [ 0, 4, -2, -4, 0, 2 ], [ 1, 4, -2, -4, 0, 1 ], [ 1, 4, -2, -4, -1, 1 ] ], 1 ] + ], + [ + [ [ [ 0, 3, -2, -4, 0, 1 ], [ 0, 4, -2, -4, 0, 2 ], [ 1, 4, -2, -4, 0, 1 ], [ 1, 4, -2, -4, -1, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 4, -2, -4, 0, 1 ], [ 0, 4, -2, -4, 0, 2 ], [ 1, 4, -2, -4, 0, 1 ], [ 1, 4, -2, -4, -1, 1 ] ], 1 ], + [ [ [ -1, 4, -2, -4, 0, 1 ], [ 0, 4, -2, -4, 0, 2 ], [ 1, 4, -2, -4, 0, 1 ], [ 0, 4, -3, -4, 0, 2 ] ], 1 ], + [ [ [ -1, 4, -2, -4, 0, 1 ], [ 0, 4, -2, -4, 0, 2 ], [ 1, 3, -2, -4, 0, 2 ], [ 0, 4, -3, -4, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 4, -2, -4, 0, 1 ], [ 0, 4, -2, -4, 0, 2 ], [ 0, 5, -2, -4, 0, 2 ], [ 0, 4, -3, -4, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -1, 3, -2, -4, 0, 2 ], [ 0, 4, -2, -4, 0, 2 ], [ 0, 5, -2, -4, 0, 2 ], [ 0, 4, -3, -4, 0, 2 ] ], 1 ], + [ [ [ -1, 3, -2, -4, 0, 2 ], [ "Rest" ], [ 0, 5, -2, -4, 0, 2 ], [ 0, 4, -3, -4, 0, 2 ] ], 1 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 5, -2, -4, 0, 2 ], [ 0, 4, -3, -4, 0, 2 ] ], 1 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 0, 4, -3, -4, 0, 2 ] ], 1 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -1, 4, -2, -4, 0, 1 ], [ 0, 4, -2, -4, 0, 2 ], [ 1, 4, -2, -4, 0, 1 ], [ 1, 4, -2, -4, -1, 1 ] ], + [ [ -1, 4, -2, -4, 0, 1 ], [ 0, 4, -2, -4, 0, 2 ], [ 1, 4, -2, -4, 0, 1 ], [ 0, 4, -3, -4, 0, 2 ] ], + [ [ -1, 4, -2, -4, 0, 1 ], [ 0, 4, -2, -4, 0, 2 ], [ 1, 3, -2, -4, 0, 2 ], [ 0, 4, -3, -4, 0, 2 ] ], + [ [ -1, 4, -2, -4, 0, 1 ], [ 0, 4, -2, -4, 0, 2 ], [ 0, 5, -2, -4, 0, 2 ], [ 0, 4, -3, -4, 0, 2 ] ], + [ [ -1, 3, -2, -4, 0, 2 ], [ 0, 4, -2, -4, 0, 2 ], [ 0, 5, -2, -4, 0, 2 ], [ 0, 4, -3, -4, 0, 2 ] ] +], +"cur_uid": "4ff624b0", +"ref_uid": "nil", +"order_seed": 492048, +"dur_seed": 518010, +"motifs_seed": 626337, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ -1200, 1200, 0.0020576131687243, 0.068181818181818, 0.074074074074074, 0.0625, 0.20576131687243, 0.0625, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61316872427983, 0, 0.98971193415638, 0 ], +"passages_weights": [ 0.48, 0.46, 0.48, 1, 1 ], +"hd_exp": 9, +"hd_invert": 0, +"order": +[ + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 1, 2 ], [ 0, 3 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 1 ], [ 0, 2, 3 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ], + [ [ 3, 0 ], [ 1, 2 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 2, 0 ], [ 3, 1 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 3 ], [ 0, 2, 1 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 2 ], [ 3, 1, 0 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 0 ], [ 1, 2, 3 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 1 ], [ 3, 2, 0 ], [ ] ], + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 0, 2 ], [ 3, 1 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 1 ], [ 0, 2, 3 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 1 ], [ 2, 3, 0 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2 ], [ ] ], + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 1, 2 ], [ 0, 3 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 3 ], [ 2, 0, 1 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 2 ], [ 3, 1, 0 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 100, 100 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/5201b8af/5201b8af_code.scd b/resources/string_quartet_3/5201b8af/5201b8af_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_3/5201b8af/5201b8af_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/string_quartet_3/5201b8af/5201b8af_mus_model.json b/resources/string_quartet_3/5201b8af/5201b8af_mus_model.json new file mode 100644 index 0000000..aa90760 --- /dev/null +++ b/resources/string_quartet_3/5201b8af/5201b8af_mus_model.json @@ -0,0 +1,58 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 4.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], 4.75 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 4.125 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ "Rest" ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ "Rest" ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, -1, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ] +], +"cur_uid": "5201b8af", +"ref_uid": "77b0d2dc", +"order_seed": 921767, +"dur_seed": 954688, +"motifs_seed": 995213, +"entrances_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 3.68, 5, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315.1702786378, 338.08049535604 ], [ -200.61919504644, 1452.6315789474 ], [ -386, 1675.5417956656 ], [ -219, 1694.1176470588 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 0.7, 0.29, 0.66, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ] +], +"sus_weights": [ 1, 0, 0 ], +"order_size": [ 8, 10 ], +"passages_size": [ 0, 10 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/5201b8af/lilypond/part_I.ly b/resources/string_quartet_3/5201b8af/lilypond/part_I.ly new file mode 100644 index 0000000..01ab702 --- /dev/null +++ b/resources/string_quartet_3/5201b8af/lilypond/part_I.ly @@ -0,0 +1,16 @@ +{ + { r2 c''2^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''2. ~ c''8.[ e'16^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}] ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'4 ~ e'16[ e'8.^\markup { \pad-markup #0.2 "-41"}] ~ e'2 ~ } + \bar "|" + { e'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3/5201b8af/lilypond/part_II.ly b/resources/string_quartet_3/5201b8af/lilypond/part_II.ly new file mode 100644 index 0000000..f176e75 --- /dev/null +++ b/resources/string_quartet_3/5201b8af/lilypond/part_II.ly @@ -0,0 +1,16 @@ +{ + { r2 e'2^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'2. ~ e'8.[ e'16^\markup { \pad-markup #0.2 "-41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}] ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'4 ~ e'16[ f'8.^\markup { \pad-markup #0.2 "-2"}] ~ f'2 ~ } + \bar "|" + { f'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3/5201b8af/lilypond/part_III.ly b/resources/string_quartet_3/5201b8af/lilypond/part_III.ly new file mode 100644 index 0000000..5f83063 --- /dev/null +++ b/resources/string_quartet_3/5201b8af/lilypond/part_III.ly @@ -0,0 +1,16 @@ +{ + { r2 fis'2^\markup { \pad-markup #0.2 "+49"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'2. ~ fis'8.[ g'16^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }}] ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'4 ~ g'16[ gis'8.^\markup { \pad-markup #0.2 "+14"}] ~ gis'2 ~ } + \bar "|" + { gis'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3/5201b8af/lilypond/part_IV.ly b/resources/string_quartet_3/5201b8af/lilypond/part_IV.ly new file mode 100644 index 0000000..67fc5d0 --- /dev/null +++ b/resources/string_quartet_3/5201b8af/lilypond/part_IV.ly @@ -0,0 +1,16 @@ +{ + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3/5488f7e9/5488f7e9_code.scd b/resources/string_quartet_3/5488f7e9/5488f7e9_code.scd new file mode 100644 index 0000000..e5197a1 --- /dev/null +++ b/resources/string_quartet_3/5488f7e9/5488f7e9_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 = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + if(pDistance < 0, {stepFunc.value(pDistance)}, {0.001}); +}; + +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/string_quartet_3/5488f7e9/5488f7e9_mus_model.json b/resources/string_quartet_3/5488f7e9/5488f7e9_mus_model.json new file mode 100644 index 0000000..cf2a424 --- /dev/null +++ b/resources/string_quartet_3/5488f7e9/5488f7e9_mus_model.json @@ -0,0 +1,163 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ -3, -2, 0, 1, 0, -2 ], [ "Rest" ], [ "Rest" ] ], 0.75 ], + [ [ [ "Rest" ], [ -3, -2, 0, 1, 0, -2 ], [ -3, -1, 0, 0, 0, -1 ], [ "Rest" ] ], 0.75 ], + [ [ [ -3, -1, 0, 1, 0, -2 ], [ -3, -2, 0, 1, 0, -2 ], [ -3, -1, 0, 0, 0, -1 ], [ "Rest" ] ], 0.5 ], + [ [ [ -3, -1, 0, 1, 0, -2 ], [ -3, -2, 0, 1, 0, -2 ], [ -3, -1, 0, 0, 0, -1 ], [ 0, -1, 0, 2, 0, -2 ] ], 0.5 ] + ], + [ + [ [ [ -3, -1, 0, 1, 0, -2 ], [ 0, -1, 0, 1, 0, -1 ], [ -3, -1, 0, 0, 0, -1 ], [ 0, -1, 0, 2, 0, -2 ] ], 0.75 ] + ], + [ + [ [ [ -3, -1, 0, 1, 0, -2 ], [ 2, -1, 0, 2, -1, -2 ], [ -3, -1, 0, 0, 0, -1 ], [ 0, -1, 0, 2, 0, -2 ] ], 0.5 ] + ], + [ + [ [ [ -3, -1, 0, 1, 0, -2 ], [ 2, -1, 0, 2, -1, -2 ], [ 2, -1, 0, 1, 0, -2 ], [ 0, -1, 0, 2, 0, -2 ] ], 0.875 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, -2 ], [ 2, -1, 0, 2, -1, -2 ], [ 2, -1, 0, 1, 0, -2 ], [ 0, -1, 0, 2, 0, -2 ] ], 0.5 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, -2 ], [ 2, -1, 0, 2, -1, -2 ], [ 2, -1, 0, 1, 0, -2 ], [ 1, 0, 0, 1, 0, -2 ] ], 0.5 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, -2 ], [ 2, -1, 0, 2, -1, -2 ], [ 2, -1, 0, 1, 0, -2 ], [ 0, 0, 0, 2, 0, -2 ] ], 1.25 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, -2 ], [ 2, -1, 0, 2, -1, -2 ], [ 2, -1, 0, 1, 0, -2 ], [ 1, 1, 0, 1, 0, -2 ] ], 0.875 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, -2 ], [ 2, 0, 0, 1, -1, -2 ], [ 2, -1, 0, 1, 0, -2 ], [ 1, 1, 0, 1, 0, -2 ] ], 1.125 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, -2 ], [ 2, 0, 0, 1, -1, -2 ], [ 1, 1, 0, 1, 1, -2 ], [ 1, 1, 0, 1, 0, -2 ] ], 0.125 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, -2 ], [ 0, 1, 0, 1, -1, -2 ], [ 1, 1, 0, 1, 1, -2 ], [ 1, 1, 0, 1, 0, -2 ] ], 0.625 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, -2 ], [ 0, 1, 0, 1, -1, -2 ], [ 0, 1, -1, 1, 0, -2 ], [ 1, 1, 0, 1, 0, -2 ] ], 1.125 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, -2 ], [ 0, 1, 0, 1, -1, -2 ], [ 1, 0, 0, 0, 0, -2 ], [ 1, 1, 0, 1, 0, -2 ] ], 0.875 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, -2 ], [ 0, 1, 0, 1, 0, -3 ], [ 1, 0, 0, 0, 0, -2 ], [ 1, 1, 0, 1, 0, -2 ] ], 0.25 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, -2 ], [ 2, -1, 0, 1, 0, -2 ], [ 1, 0, 0, 0, 0, -2 ], [ 1, 1, 0, 1, 0, -2 ] ], 1.125 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, -2 ], [ 2, -1, 0, 1, 0, -2 ], [ 1, -1, 0, 1, -1, -2 ], [ 1, 1, 0, 1, 0, -2 ] ], 0.25 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, -2 ], [ 1, -1, 0, 2, -1, -2 ], [ 1, -1, 0, 1, -1, -2 ], [ 1, 1, 0, 1, 0, -2 ] ], 0.875 ] + ], + [ + [ [ [ 0, 1, 0, 1, 0, -1 ], [ 1, -1, 0, 2, -1, -2 ], [ 1, -1, 0, 1, -1, -2 ], [ 1, 1, 0, 1, 0, -2 ] ], 0.75 ] + ], + [ + [ [ [ 0, 1, 0, 1, 0, -1 ], [ 1, -1, 0, 2, -1, -2 ], [ -1, 1, 1, 1, 0, -2 ], [ 1, 1, 0, 1, 0, -2 ] ], 0.75 ] + ], + [ + [ [ [ 0, 1, 1, 1, 0, -3 ], [ 1, -1, 0, 2, -1, -2 ], [ -1, 1, 1, 1, 0, -2 ], [ 1, 1, 0, 1, 0, -2 ] ], 1 ] + ], + [ + [ [ [ 0, 1, 1, 1, 0, -3 ], [ 1, -1, 0, 2, -1, -2 ], [ 1, 1, 1, 1, -1, -3 ], [ 1, 1, 0, 1, 0, -2 ] ], 1.125 ] + ], + [ + [ [ [ 0, 1, 1, 1, 0, -3 ], [ 1, -1, 0, 2, -1, -2 ], [ 2, -1, 0, 2, -1, -3 ], [ 1, 1, 0, 1, 0, -2 ] ], 0.375 ] + ], + [ + [ [ [ 2, -1, 0, 3, -1, -3 ], [ 1, -1, 0, 2, -1, -2 ], [ 2, -1, 0, 2, -1, -3 ], [ 1, 1, 0, 1, 0, -2 ] ], 0.75 ] + ], + [ + [ [ [ 2, -1, 0, 3, -1, -3 ], [ 1, -1, 0, 2, -1, -2 ], [ 2, -1, 0, 2, -1, -3 ], [ 3, -1, 0, 1, -1, -2 ] ], 1.125 ] + ], + [ + [ [ [ 2, -1, 0, 3, -1, -3 ], [ 2, -2, 0, 3, -1, -3 ], [ 2, -1, 0, 2, -1, -3 ], [ 3, -1, 0, 1, -1, -2 ] ], 0.375 ] + ], + [ + [ [ [ 2, -1, 0, 3, -1, -3 ], [ 2, -2, 0, 3, -1, -3 ], [ 2, -1, 0, 2, -1, -3 ], [ 2, -2, 1, 3, -1, -3 ] ], 0.625 ] + ], + [ + [ [ [ 2, -1, 0, 3, -1, -3 ], [ 2, -2, 0, 3, -1, -3 ], [ 2, -1, 0, 2, -1, -3 ], [ 2, -2, 0, 2, -1, -3 ] ], 0.25 ] + ], + [ + [ [ [ 1, -1, -1, 2, -1, -3 ], [ 2, -2, 0, 3, -1, -3 ], [ 2, -1, 0, 2, -1, -3 ], [ 2, -2, 0, 2, -1, -3 ] ], 1.125 ] + ], + [ + [ [ [ 1, -1, -1, 2, -1, -3 ], [ 2, -2, 0, 3, -1, -3 ], [ 2, -1, 0, 2, -1, -3 ], [ 2, -1, -1, 2, -1, -2 ] ], 0.25 ] + ], + [ + [ [ [ 1, -1, -1, 2, -1, -3 ], [ 2, -1, -1, 3, -1, -3 ], [ 2, -1, 0, 2, -1, -3 ], [ 2, -1, -1, 2, -1, -2 ] ], 0.875 ], + [ [ [ "Rest" ], [ 2, -1, -1, 3, -1, -3 ], [ 2, -1, 0, 2, -1, -3 ], [ 2, -1, -1, 2, -1, -2 ] ], 0 ], + [ [ [ "Rest" ], [ 2, -1, -1, 3, -1, -3 ], [ 2, -1, 0, 2, -1, -3 ], [ "Rest" ] ], 0.5 ], + [ [ [ "Rest" ], [ "Rest" ], [ 2, -1, 0, 2, -1, -3 ], [ "Rest" ] ], 0.375 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 6.625 ] + ] + ] +], +"last_changes": +[ + [ [ 2, -1, 0, 3, -1, -3 ], [ 2, -2, 0, 3, -1, -3 ], [ 2, -1, 0, 2, -1, -3 ], [ 2, -2, 1, 3, -1, -3 ] ], + [ [ 2, -1, 0, 3, -1, -3 ], [ 2, -2, 0, 3, -1, -3 ], [ 2, -1, 0, 2, -1, -3 ], [ 2, -2, 0, 2, -1, -3 ] ], + [ [ 1, -1, -1, 2, -1, -3 ], [ 2, -2, 0, 3, -1, -3 ], [ 2, -1, 0, 2, -1, -3 ], [ 2, -2, 0, 2, -1, -3 ] ], + [ [ 1, -1, -1, 2, -1, -3 ], [ 2, -2, 0, 3, -1, -3 ], [ 2, -1, 0, 2, -1, -3 ], [ 2, -1, -1, 2, -1, -2 ] ], + [ [ 1, -1, -1, 2, -1, -3 ], [ 2, -1, -1, 3, -1, -3 ], [ 2, -1, 0, 2, -1, -3 ], [ 2, -1, -1, 2, -1, -2 ] ] +], +"cur_uid": "5488f7e9", +"ref_uid": "7c2de94c", +"order_seed": 199804, +"dur_seed": 927098, +"motifs_seed": 741655, +"entrances_probs_vals": [ 0, 0, 0, 0, 1.2087912087912, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 0, 1.2087912087912, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 0, 1.2087912087912, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1147.9876160991, 1638 ], [ -1240.866873065, 1453 ], [ -980.80495356037, 1768 ], [ -850.77399380805, 1843 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 1, 0.34, 0.98, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ] +], +"sus_weights": [ 0, 0, 0.61 ], +"order_size": [ 30, 30 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/55bd25a1/55bd25a1_code.scd b/resources/string_quartet_3/55bd25a1/55bd25a1_code.scd new file mode 100644 index 0000000..49436ca --- /dev/null +++ b/resources/string_quartet_3/55bd25a1/55bd25a1_code.scd @@ -0,0 +1,973 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + + + //isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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/string_quartet_3/55bd25a1/55bd25a1_mus_model.json b/resources/string_quartet_3/55bd25a1/55bd25a1_mus_model.json new file mode 100644 index 0000000..b0cec78 --- /dev/null +++ b/resources/string_quartet_3/55bd25a1/55bd25a1_mus_model.json @@ -0,0 +1,548 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 7, -5, -2, -1, -3, -2 ], [ "Rest" ], [ "Rest" ] ], 1.125 ], + [ [ [ "Rest" ], [ 7, -5, -2, -1, -3, -2 ], [ "Rest" ], [ 6, -6, -2, 0, -1, -2 ] ], 1.25 ], + [ [ [ 5, -5, -2, 0, -2, -2 ], [ 7, -5, -2, -1, -3, -2 ], [ "Rest" ], [ 6, -6, -2, 0, -1, -2 ] ], 1.25 ], + [ [ [ 5, -5, -2, 0, -2, -2 ], [ 7, -5, -2, -1, -3, -2 ], [ 6, -5, -2, 0, -1, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 1.5 ] + ], + [ + [ [ [ 5, -5, -2, 0, -2, -2 ], [ 6, -6, -2, 0, -2, -2 ], [ 6, -5, -2, 0, -1, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 0.625 ], + [ [ [ 5, -5, -2, 0, -2, -2 ], [ 6, -6, -2, 0, -2, -2 ], [ 6, -4, -2, 0, -2, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 0.5 ] + ], + [ + [ [ [ 4, -3, -2, 0, -2, -2 ], [ 6, -6, -2, 0, -2, -2 ], [ 6, -4, -2, 0, -2, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 0.625 ], + [ [ [ 4, -3, -2, 0, -2, -2 ], [ 5, -4, -2, 0, -2, -2 ], [ 6, -4, -2, 0, -2, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 1 ] + ], + [ + [ [ [ 4, -3, -2, 0, -2, -2 ], [ 5, -4, -2, 0, -2, -2 ], [ 7, -4, -2, -1, -2, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 1 ], + [ [ [ 5, -4, -3, 0, -2, -2 ], [ 5, -4, -2, 0, -2, -2 ], [ 7, -4, -2, -1, -2, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 0.75 ], + [ [ [ 5, -4, -3, 0, -2, -2 ], [ 5, -4, -2, 0, -2, -2 ], [ 7, -4, -2, -1, -2, -2 ], [ 6, -5, -2, 0, -2, -2 ] ], 0.5 ] + ], + [ + [ [ [ 6, -6, -2, 0, -2, -2 ], [ 5, -4, -2, 0, -2, -2 ], [ 7, -4, -2, -1, -2, -2 ], [ 6, -5, -2, 0, -2, -2 ] ], 0.625 ], + [ [ [ 6, -6, -2, 0, -2, -2 ], [ 6, -5, -3, 0, -2, -2 ], [ 7, -4, -2, -1, -2, -2 ], [ 6, -5, -2, 0, -2, -2 ] ], 1.25 ], + [ [ [ 6, -6, -2, 0, -2, -2 ], [ 6, -5, -3, 0, -2, -2 ], [ 7, -5, -3, 0, -2, -2 ], [ 6, -5, -2, 0, -2, -2 ] ], 1.5 ] + ], + [ + [ [ [ 6, -6, -2, 0, -2, -2 ], [ 6, -5, -3, 0, -2, -2 ], [ 6, -5, -2, 1, -2, -2 ], [ 6, -5, -2, 0, -2, -2 ] ], 0.625 ] + ], + [ + [ [ [ 6, -5, -2, 0, -3, -2 ], [ 6, -5, -3, 0, -2, -2 ], [ 6, -5, -2, 1, -2, -2 ], [ 6, -5, -2, 0, -2, -2 ] ], 0.5 ], + [ [ [ 6, -5, -2, 0, -3, -2 ], [ 5, -5, -2, 1, -2, -2 ], [ 6, -5, -2, 1, -2, -2 ], [ 6, -5, -2, 0, -2, -2 ] ], 0.75 ], + [ [ [ 6, -5, -2, 0, -3, -2 ], [ 5, -5, -2, 1, -2, -2 ], [ 6, -4, -2, 0, -2, -2 ], [ 6, -5, -2, 0, -2, -2 ] ], 0.75 ] + ], + [ + [ [ [ 5, -5, -1, 0, -2, -2 ], [ 5, -5, -2, 1, -2, -2 ], [ 6, -4, -2, 0, -2, -2 ], [ 6, -5, -2, 0, -2, -2 ] ], 0.875 ], + [ [ [ 5, -5, -1, 0, -2, -2 ], [ 5, -4, -2, 0, -2, -2 ], [ 6, -4, -2, 0, -2, -2 ], [ 6, -5, -2, 0, -2, -2 ] ], 0.875 ], + [ [ [ 5, -5, -1, 0, -2, -2 ], [ 5, -4, -2, 0, -2, -2 ], [ 6, -5, -2, 0, -2, -1 ], [ 6, -5, -2, 0, -2, -2 ] ], 0.875 ] + ], + [ + [ [ [ 5, -5, -1, 0, -2, -2 ], [ 5, -4, -2, 0, -2, -2 ], [ 6, -5, -2, 0, -2, -1 ], [ 5, -5, -1, 1, -2, -2 ] ], 1.625 ], + [ [ [ 5, -5, -1, 0, -2, -2 ], [ 6, -6, -1, 0, -2, -2 ], [ 6, -5, -2, 0, -2, -1 ], [ 5, -5, -1, 1, -2, -2 ] ], 0.75 ] + ], + [ + [ [ [ 5, -5, -1, 0, -2, -2 ], [ 6, -6, -1, 0, -2, -2 ], [ 6, -5, -2, 0, -2, -1 ], [ 6, -5, -1, 0, -2, -2 ] ], 1.75 ] + ], + [ + [ [ [ 5, -5, -1, 0, -2, -2 ], [ 5, -4, -1, 0, -2, -2 ], [ 6, -5, -2, 0, -2, -1 ], [ 6, -5, -1, 0, -2, -2 ] ], 1.75 ], + [ [ [ 6, -5, -1, -1, -2, -2 ], [ 5, -4, -1, 0, -2, -2 ], [ 6, -5, -2, 0, -2, -1 ], [ 6, -5, -1, 0, -2, -2 ] ], 1.125 ], + [ [ [ 6, -5, -1, -1, -2, -2 ], [ 5, -4, -1, 0, -2, -2 ], [ 6, -4, -1, 0, -2, -2 ], [ 6, -5, -1, 0, -2, -2 ] ], 1.75 ] + ], + [ + [ [ [ 5, -4, -2, 0, -2, -2 ], [ 5, -4, -1, 0, -2, -2 ], [ 6, -4, -1, 0, -2, -2 ], [ 6, -5, -1, 0, -2, -2 ] ], 0.875 ], + [ [ [ 5, -4, -2, 0, -2, -2 ], [ 5, -4, -1, 0, -2, -2 ], [ 6, -4, -1, 0, -2, -2 ], [ 5, -3, -1, 0, -2, -2 ] ], 1.25 ], + [ [ [ 5, -4, -2, 0, -2, -2 ], [ 6, -4, -1, -1, -2, -2 ], [ 6, -4, -1, 0, -2, -2 ], [ 5, -3, -1, 0, -2, -2 ] ], 1 ] + ], + [ + [ [ [ 5, -5, -1, 0, -2, -2 ], [ 6, -4, -1, -1, -2, -2 ], [ 6, -4, -1, 0, -2, -2 ], [ 5, -3, -1, 0, -2, -2 ] ], 1.25 ] + ], + [ + [ [ [ 4, -3, -1, 0, -2, -2 ], [ 6, -4, -1, -1, -2, -2 ], [ 6, -4, -1, 0, -2, -2 ], [ 5, -3, -1, 0, -2, -2 ] ], 1.5 ], + [ [ [ 4, -3, -1, 0, -2, -2 ], [ 6, -4, -1, -1, -2, -2 ], [ 6, -4, -1, 0, -2, -2 ], [ 6, -4, -2, 0, -2, -2 ] ], 0.75 ] + ], + [ + [ [ [ 4, -3, -1, 0, -2, -2 ], [ 6, -4, -1, 0, -2, -3 ], [ 6, -4, -1, 0, -2, -2 ], [ 6, -4, -2, 0, -2, -2 ] ], 1.375 ] + ], + [ + [ [ [ 4, -3, -1, 0, -2, -2 ], [ 6, -4, -1, 0, -2, -3 ], [ 6, -4, -1, 0, -2, -2 ], [ 4, -4, -1, 1, -2, -2 ] ], 0.75 ], + [ [ [ 5, -4, -2, 0, -2, -2 ], [ 6, -4, -1, 0, -2, -3 ], [ 6, -4, -1, 0, -2, -2 ], [ 4, -4, -1, 1, -2, -2 ] ], 1.375 ], + [ [ [ 5, -4, -2, 0, -2, -2 ], [ 6, -5, -1, 0, -2, -2 ], [ 6, -4, -1, 0, -2, -2 ], [ 4, -4, -1, 1, -2, -2 ] ], 0.375 ] + ], + [ + [ [ [ 4, -4, -1, 1, -3, -2 ], [ 6, -5, -1, 0, -2, -2 ], [ 6, -4, -1, 0, -2, -2 ], [ 4, -4, -1, 1, -2, -2 ] ], 0.5 ], + [ [ [ 4, -4, -1, 1, -3, -2 ], [ 4, -4, -1, 1, -2, -1 ], [ 6, -4, -1, 0, -2, -2 ], [ 4, -4, -1, 1, -2, -2 ] ], 1.625 ], + [ [ [ 4, -4, -1, 1, -3, -2 ], [ 4, -4, -1, 1, -2, -1 ], [ 5, -4, -1, 1, -2, -2 ], [ 4, -4, -1, 1, -2, -2 ] ], 1.5 ] + ], + [ + [ [ [ 4, -4, -1, 1, -3, -2 ], [ 4, -4, -1, 1, -2, -1 ], [ 5, -4, -1, 1, -2, -2 ], [ 5, -4, -1, 0, -2, -2 ] ], 1.25 ], + [ [ [ 4, -4, -1, 1, -3, -2 ], [ 4, -4, -1, 1, -2, -2 ], [ 5, -4, -1, 1, -2, -2 ], [ 5, -4, -1, 0, -2, -2 ] ], 1.625 ], + [ [ [ 4, -4, -2, 1, -2, -2 ], [ 4, -4, -1, 1, -2, -2 ], [ 5, -4, -1, 1, -2, -2 ], [ 5, -4, -1, 0, -2, -2 ] ], 0.5 ] + ], + [ + [ [ [ 4, -4, -2, 1, -2, -2 ], [ 4, -4, -1, 1, -2, -2 ], [ 6, -4, -2, 0, -2, -2 ], [ 5, -4, -1, 0, -2, -2 ] ], 0.875 ], + [ [ [ 4, -4, -2, 1, -2, -2 ], [ 4, -4, -1, 1, -2, -2 ], [ 6, -4, -2, 0, -2, -2 ], [ 4, -4, 0, 1, -2, -2 ] ], 0.875 ] + ], + [ + [ [ [ 4, -4, -2, 1, -2, -2 ], [ 4, -4, -1, 1, -2, -2 ], [ 4, -4, 0, 1, -2, -1 ], [ 4, -4, 0, 1, -2, -2 ] ], 1.75 ], + [ [ [ 3, -4, -1, 2, -2, -2 ], [ 4, -4, -1, 1, -2, -2 ], [ 4, -4, 0, 1, -2, -1 ], [ 4, -4, 0, 1, -2, -2 ] ], 1.625 ] + ], + [ + [ [ [ 3, -4, -1, 2, -2, -2 ], [ 4, -4, -1, 1, -2, -2 ], [ 4, -4, 0, 1, -2, -1 ], [ 3, -4, -1, 2, -2, -1 ] ], 1.375 ], + [ [ [ 3, -4, -1, 2, -2, -2 ], [ 3, -4, 0, 2, -2, -2 ], [ 4, -4, 0, 1, -2, -1 ], [ 3, -4, -1, 2, -2, -1 ] ], 1.625 ], + [ [ [ 3, -4, -1, 2, -2, -2 ], [ 3, -4, 0, 2, -2, -2 ], [ 4, -4, 0, 2, -2, -2 ], [ 3, -4, -1, 2, -2, -1 ] ], 1.5 ] + ], + [ + [ [ [ 3, -5, -1, 2, -2, -1 ], [ 3, -4, 0, 2, -2, -2 ], [ 4, -4, 0, 2, -2, -2 ], [ 3, -4, -1, 2, -2, -1 ] ], 1 ], + [ [ [ 3, -5, -1, 2, -2, -1 ], [ 2, -4, -1, 2, -2, 0 ], [ 4, -4, 0, 2, -2, -2 ], [ 3, -4, -1, 2, -2, -1 ] ], 1.25 ] + ], + [ + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 2, -4, -1, 2, -2, 0 ], [ 4, -4, 0, 2, -2, -2 ], [ 3, -4, -1, 2, -2, -1 ] ], 1.25 ], + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 2, -4, 0, 2, -2, -1 ], [ 4, -4, 0, 2, -2, -2 ], [ 3, -4, -1, 2, -2, -1 ] ], 0.875 ] + ], + [ + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 2, -4, 0, 2, -2, -1 ], [ 4, -5, 0, 2, -2, -1 ], [ 3, -4, -1, 2, -2, -1 ] ], 1.75 ], + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 2, -4, 0, 2, -2, -1 ], [ 4, -5, 0, 2, -2, -1 ], [ 2, -4, 0, 3, -2, -1 ] ], 1.5 ] + ], + [ + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 2, -5, 0, 2, -2, 0 ], [ 4, -5, 0, 2, -2, -1 ], [ 2, -4, 0, 3, -2, -1 ] ], 0.625 ], + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 2, -5, 0, 2, -2, 0 ], [ 4, -5, 0, 2, -2, -1 ], [ 3, -4, 0, 2, -2, -1 ] ], 1.25 ] + ], + [ + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 3, -4, 0, 2, -2, -2 ], [ 4, -5, 0, 2, -2, -1 ], [ 3, -4, 0, 2, -2, -1 ] ], 0.625 ], + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 3, -4, 0, 2, -2, -2 ], [ 4, -5, 0, 2, -2, -1 ], [ 4, -5, 0, 2, -1, -2 ] ], 1.625 ] + ], + [ + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 3, -4, 0, 2, -2, -2 ], [ 4, -5, 0, 2, -2, -1 ], [ 4, -4, 0, 2, -2, -2 ] ], 1.5 ], + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 3, -4, 0, 2, -2, -2 ], [ 3, -4, 0, 2, -2, -1 ], [ 4, -4, 0, 2, -2, -2 ] ], 1.5 ] + ], + [ + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 3, -4, -1, 2, -1, -2 ], [ 3, -4, 0, 2, -2, -1 ], [ 4, -4, 0, 2, -2, -2 ] ], 1.625 ], + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 3, -4, -1, 2, -1, -2 ], [ 4, -5, 0, 2, -1, -2 ], [ 4, -4, 0, 2, -2, -2 ] ], 1.25 ], + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 3, -4, -1, 2, -1, -2 ], [ 4, -5, 0, 2, -1, -2 ], [ 4, -4, 0, 1, -1, -2 ] ], 1.75 ] + ], + [ + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 3, -4, -1, 2, -1, -2 ], [ 3, -3, 0, 2, -1, -2 ], [ 4, -4, 0, 1, -1, -2 ] ], 0.75 ], + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 3, -4, -1, 2, -1, -2 ], [ 3, -3, 0, 2, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ] ], 1.5 ], + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 2, -4, 0, 3, -1, -2 ], [ 3, -3, 0, 2, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ] ], 1 ] + ], + [ + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 2, -4, 0, 3, -1, -2 ], [ 2, -3, 0, 3, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ] ], 1.625 ] + ], + [ + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 2, -4, 0, 3, -1, -2 ], [ 2, -3, 0, 3, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ] ], 1.25 ], + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 2, -4, 0, 3, -1, -2 ], [ 4, -4, 1, 1, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ] ], 0.5 ], + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 2, -3, 1, 2, -1, -2 ], [ 4, -4, 1, 1, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ] ], 1 ] + ], + [ + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 2, -3, 1, 2, -1, -2 ], [ 4, -4, 1, 1, -1, -2 ], [ 2, -2, 1, 2, -1, -2 ] ], 1.5 ], + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 2, -3, 1, 2, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ], [ 2, -2, 1, 2, -1, -2 ] ], 1.125 ] + ], + [ + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 2, -3, 1, 2, -1, -2 ], [ 3, -5, 1, 3, -1, -2 ], [ 2, -2, 1, 2, -1, -2 ] ], 1.125 ] + ], + [ + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 2, -3, 1, 2, -1, -2 ], [ 1, -2, 1, 3, -1, -2 ], [ 2, -2, 1, 2, -1, -2 ] ], 1 ] + ], + [ + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 2, -3, 1, 2, -1, -2 ], [ 2, -4, 2, 3, -1, -2 ], [ 2, -2, 1, 2, -1, -2 ] ], 1.25 ], + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 2, -3, 1, 2, -1, -2 ], [ 2, -4, 2, 3, -1, -2 ], [ 3, -3, 0, 2, -1, -2 ] ], 0.875 ] + ], + [ + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 2, -4, 1, 2, -1, -2 ], [ 2, -4, 2, 3, -1, -2 ], [ 3, -3, 0, 2, -1, -2 ] ], 0.875 ], + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 2, -4, 1, 2, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ], [ 3, -3, 0, 2, -1, -2 ] ], 0.5 ], + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 2, -4, 1, 2, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ], [ 2, -4, 2, 3, -1, -2 ] ], 1.25 ] + ], + [ + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 2, -4, 1, 2, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ], [ 4, -4, 1, 1, -1, -2 ] ], 0.5 ] + ], + [ + [ [ [ 1, -4, 1, 3, -1, -2 ], [ 3, -4, 1, 1, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ], [ 4, -4, 1, 1, -1, -2 ] ], 1.625 ], + [ [ [ 2, -4, 1, 2, -1, -2 ], [ 3, -4, 1, 1, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ], [ 4, -4, 1, 1, -1, -2 ] ], 1.25 ], + [ [ [ 2, -4, 1, 2, -1, -2 ], [ 3, -4, 1, 1, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ], [ 1, -4, 1, 2, -1, -1 ] ], 0.625 ] + ], + [ + [ [ [ 2, -4, 1, 2, -1, -2 ], [ 3, -4, 1, 1, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ], [ 3, -4, 0, 1, -1, -2 ] ], 1 ] + ], + [ + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 3, -4, 1, 1, -1, -2 ], [ 3, -4, 1, 2, -1, -2 ], [ 3, -4, 0, 1, -1, -2 ] ], 1.125 ] + ], + [ + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 3, -4, 1, 1, -1, -2 ], [ 4, -4, 1, 1, -1, -2 ], [ 3, -4, 0, 1, -1, -2 ] ], 1 ] + ], + [ + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 3, -4, 1, 1, -1, -2 ], [ 4, -4, 1, 1, -1, -2 ], [ 2, -4, 1, 2, -1, -2 ] ], 1.625 ], + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 2, -3, 0, 2, -1, -2 ], [ 4, -4, 1, 1, -1, -2 ], [ 2, -4, 1, 2, -1, -2 ] ], 1.625 ], + [ [ [ 2, -4, 0, 2, -1, -2 ], [ 2, -3, 0, 2, -1, -2 ], [ 3, -3, 0, 2, -1, -2 ], [ 2, -4, 1, 2, -1, -2 ] ], 1.375 ] + ], + [ + [ [ [ 1, -2, 0, 2, -1, -2 ], [ 2, -3, 0, 2, -1, -2 ], [ 3, -3, 0, 2, -1, -2 ], [ 2, -4, 1, 2, -1, -2 ] ], 0.625 ], + [ [ [ 1, -2, 0, 2, -1, -2 ], [ 2, -3, 0, 2, -1, -2 ], [ 2, -3, 0, 2, -1, -1 ], [ 2, -4, 1, 2, -1, -2 ] ], 1.375 ] + ], + [ + [ [ [ 1, -2, 0, 2, -1, -2 ], [ 2, -3, 0, 2, -1, -2 ], [ 2, -3, 0, 2, -1, -1 ], [ 2, -2, 0, 2, -1, -3 ] ], 1.375 ], + [ [ [ 1, -2, 0, 2, -1, -2 ], [ 1, -1, 0, 2, -1, -2 ], [ 2, -3, 0, 2, -1, -1 ], [ 2, -2, 0, 2, -1, -3 ] ], 1.375 ] + ], + [ + [ [ [ 0, 0, 0, 2, -1, -2 ], [ 1, -1, 0, 2, -1, -2 ], [ 2, -3, 0, 2, -1, -1 ], [ 2, -2, 0, 2, -1, -3 ] ], 1.25 ], + [ [ [ 0, 0, 0, 2, -1, -2 ], [ 1, -1, 0, 2, -1, -2 ], [ 2, -1, -1, 2, -1, -2 ], [ 2, -2, 0, 2, -1, -3 ] ], 1 ] + ], + [ + [ [ [ 2, -2, -1, 2, -1, -3 ], [ 1, -1, 0, 2, -1, -2 ], [ 2, -1, -1, 2, -1, -2 ], [ 2, -2, 0, 2, -1, -3 ] ], 1.75 ], + [ [ [ 2, -2, -1, 2, -1, -3 ], [ 2, -2, -1, 2, -1, -2 ], [ 2, -1, -1, 2, -1, -2 ], [ 2, -2, 0, 2, -1, -3 ] ], 0.625 ] + ], + [ + [ [ [ 2, -2, -1, 2, -1, -3 ], [ 2, -2, -1, 2, -1, -2 ], [ 2, -1, -1, 2, -1, -2 ], [ 1, -1, -2, 2, -1, -2 ] ], 0.5 ], + [ [ [ 2, -3, -1, 2, -1, -2 ], [ 2, -2, -1, 2, -1, -2 ], [ 2, -1, -1, 2, -1, -2 ], [ 1, -1, -2, 2, -1, -2 ] ], 0.875 ] + ], + [ + [ [ [ 2, -3, -1, 2, -1, -2 ], [ 2, -2, -1, 2, -1, -2 ], [ 3, -2, -2, 2, -1, -2 ], [ 1, -1, -2, 2, -1, -2 ] ], 0.875 ] + ], + [ + [ [ [ 2, -3, -1, 2, -1, -2 ], [ 2, -2, -1, 2, -1, -2 ], [ 3, -2, -2, 2, -1, -2 ], [ 2, -2, -3, 2, -1, -2 ] ], 1.125 ], + [ [ [ 2, -3, -2, 2, -1, -2 ], [ 2, -2, -1, 2, -1, -2 ], [ 3, -2, -2, 2, -1, -2 ], [ 2, -2, -3, 2, -1, -2 ] ], 0.5 ] + ], + [ + [ [ [ 2, -3, -2, 2, -1, -2 ], [ 2, -2, -1, 2, -1, -2 ], [ 2, -2, -1, 3, -1, -2 ], [ 2, -2, -3, 2, -1, -2 ] ], 0.875 ], + [ [ [ 2, -3, -2, 2, -1, -2 ], [ 2, -2, -1, 2, -1, -2 ], [ 2, -2, -1, 3, -1, -2 ], [ 2, -2, -1, 2, -2, -2 ] ], 0.875 ], + [ [ [ 2, -2, -1, 1, -1, -2 ], [ 2, -2, -1, 2, -1, -2 ], [ 2, -2, -1, 3, -1, -2 ], [ 2, -2, -1, 2, -2, -2 ] ], 1.375 ] + ], + [ + [ [ [ 2, -2, -1, 2, -1, -3 ], [ 2, -2, -1, 2, -1, -2 ], [ 2, -2, -1, 3, -1, -2 ], [ 2, -2, -1, 2, -2, -2 ] ], 0.875 ] + ], + [ + [ [ [ 2, -2, -1, 2, -1, -3 ], [ 2, -1, -1, 2, -2, -2 ], [ 2, -2, -1, 3, -1, -2 ], [ 2, -2, -1, 2, -2, -2 ] ], 0.875 ] + ], + [ + [ [ [ 2, -2, -1, 2, -1, -3 ], [ 2, -1, -1, 2, -2, -2 ], [ 3, -2, -1, 2, -2, -2 ], [ 2, -2, -1, 2, -2, -2 ] ], 1.75 ] + ], + [ + [ [ [ 2, -2, -1, 2, -1, -3 ], [ 2, -1, -1, 2, -2, -2 ], [ 4, -2, -1, 1, -2, -2 ], [ 2, -2, -1, 2, -2, -2 ] ], 0.875 ], + [ [ [ 2, -2, -1, 2, -1, -3 ], [ 1, -2, -1, 2, -2, -1 ], [ 4, -2, -1, 1, -2, -2 ], [ 2, -2, -1, 2, -2, -2 ] ], 1.5 ] + ], + [ + [ [ [ 2, -2, -1, 2, -1, -3 ], [ 1, -2, -1, 2, -2, -1 ], [ 4, -2, -1, 1, -2, -2 ], [ 1, -2, -1, 2, -1, -1 ] ], 1.625 ], + [ [ [ 2, -2, -1, 1, -2, -1 ], [ 1, -2, -1, 2, -2, -1 ], [ 4, -2, -1, 1, -2, -2 ], [ 1, -2, -1, 2, -1, -1 ] ], 0.625 ], + [ [ [ 2, -2, -1, 1, -2, -1 ], [ 1, -2, -1, 2, -2, -1 ], [ 3, -2, -1, 1, -2, -1 ], [ 1, -2, -1, 2, -1, -1 ] ], 0.875 ] + ], + [ + [ [ [ 2, -2, -1, 1, -2, -1 ], [ 1, -2, -1, 2, -2, -1 ], [ 4, -2, -1, 0, -2, -1 ], [ 1, -2, -1, 2, -1, -1 ] ], 0.625 ], + [ [ [ 2, -2, -1, 1, -2, -1 ], [ 1, -2, -1, 2, -2, -1 ], [ 4, -2, -1, 0, -2, -1 ], [ 3, -3, -1, 1, -2, -1 ] ], 1.75 ], + [ [ [ 2, -2, -1, 1, -2, -1 ], [ 1, -2, -1, 1, -2, 0 ], [ 4, -2, -1, 0, -2, -1 ], [ 3, -3, -1, 1, -2, -1 ] ], 1.25 ] + ], + [ + [ [ [ 2, -2, -1, 1, -2, -1 ], [ 1, -2, -1, 1, -2, 0 ], [ 4, -2, -1, 0, -2, -1 ], [ 3, -2, 0, 0, -2, -1 ] ], 1.375 ] + ], + [ + [ [ [ 2, -2, -1, 0, -2, 0 ], [ 1, -2, -1, 1, -2, 0 ], [ 4, -2, -1, 0, -2, -1 ], [ 3, -2, 0, 0, -2, -1 ] ], 1.875 ], + [ [ [ 2, -2, -1, 0, -2, 0 ], [ 1, -2, -1, 1, -2, 0 ], [ 4, -2, -1, 0, -2, -1 ], [ 2, -2, -1, 1, -2, 0 ] ], 0.5 ], + [ [ [ 2, -2, -1, 0, -2, 0 ], [ 1, -2, -1, 1, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ] ], 0.875 ] + ], + [ + [ [ [ 2, -2, -1, 0, -2, 0 ], [ 0, 0, -1, 1, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ] ], 1.625 ], + [ [ [ 1, -1, -2, 1, -2, 0 ], [ 0, 0, -1, 1, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ] ], 1.625 ] + ], + [ + [ [ [ 1, -1, -2, 1, -2, 0 ], [ 1, -2, 0, 1, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ] ], 1.5 ] + ], + [ + [ [ [ 1, -1, -2, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ] ], 0.375 ], + [ [ [ 1, -1, -2, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 1, 0, -1, 1, -2, 0 ] ], 1.375 ], + [ [ [ 1, -2, -1, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 1, 0, -1, 1, -2, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -1, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 1, 0, -1, 1, -2, 0 ] ], 0.5 ] + ], + [ + [ [ [ 0, 0, -1, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ], [ 1, -1, -1, 2, -2, 0 ], [ 1, 0, -1, 1, -2, 0 ] ], 1.125 ] + ], + [ + [ [ [ 0, 0, -1, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 1, 0, -1, 1, -2, 0 ] ], 0.875 ], + [ [ [ -1, 0, -1, 2, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 1, 0, -1, 1, -2, 0 ] ], 1 ], + [ [ [ -1, 0, -1, 2, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 1, -1, -1, 2, -2, 0 ] ], 1.125 ] + ], + [ + [ [ [ -1, 0, -1, 2, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 0, -1, -1, 3, -2, 0 ] ], 0.875 ], + [ [ [ 0, -1, -2, 2, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 0, -1, -1, 3, -2, 0 ] ], 1.625 ] + ], + [ + [ [ [ 1, -1, -2, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 0, -1, -1, 3, -2, 0 ] ], 0.5 ] + ], + [ + [ [ [ 1, -1, -2, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ], [ 1, -1, -1, 2, -2, 0 ], [ 0, -1, -1, 3, -2, 0 ] ], 0.5 ], + [ [ [ 1, -1, -2, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ], [ 1, -1, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ] ], 0.75 ], + [ [ [ -1, -1, -1, 2, -2, 1 ], [ 0, -1, -1, 2, -2, 0 ], [ 1, -1, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ] ], 0.5 ] + ], + [ + [ [ [ -1, -1, -1, 2, -2, 1 ], [ 0, -1, -1, 2, -2, 0 ], [ 1, -1, -1, 1, -2, 1 ], [ 2, -1, -1, 1, -2, 0 ] ], 1.75 ], + [ [ [ -1, -1, -1, 2, -2, 1 ], [ 1, -1, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 1 ], [ 2, -1, -1, 1, -2, 0 ] ], 0.875 ], + [ [ [ 0, 0, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 1 ], [ 2, -1, -1, 1, -2, 0 ] ], 0.5 ] + ], + [ + [ [ [ 0, 0, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 1 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.375 ], + [ [ [ 0, -1, -1, 1, -2, 1 ], [ 1, -1, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 1 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.25 ] + ], + [ + [ [ [ 1, -2, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 1 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.625 ] + ], + [ + [ [ [ 1, -2, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ], [ 1, -1, -1, 2, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 0.75 ] + ], + [ + [ [ [ 1, -2, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.5 ], + [ [ [ 1, -1, -1, 1, -3, 0 ], [ 1, -1, -1, 1, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 0.5 ] + ], + [ + [ [ [ 1, -1, -1, 1, -3, 0 ], [ 1, -1, -1, 1, -2, 0 ], [ 2, -1, -2, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.625 ], + [ [ [ 1, -1, -2, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ], [ 2, -1, -2, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.625 ] + ], + [ + [ [ [ 1, -1, -2, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ], [ 1, -1, -1, 2, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.375 ], + [ [ [ -1, -1, -1, 3, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ], [ 1, -1, -1, 2, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.375 ] + ], + [ + [ [ [ -1, -1, -1, 3, -2, 0 ], [ 1, -2, -1, 2, -2, 0 ], [ 1, -1, -1, 2, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.625 ], + [ [ [ -1, -1, -1, 3, -2, 0 ], [ 1, -2, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.25 ], + [ [ [ -1, -2, -1, 2, -2, 0 ], [ 1, -2, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.75 ] + ], + [ + [ [ [ 0, -2, -1, 1, -2, 0 ], [ 1, -2, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.5 ], + [ [ [ 0, -2, -1, 1, -2, 0 ], [ 1, -2, -1, 2, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ] ], 0.5 ], + [ [ [ 0, -2, -1, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ], [ 2, -1, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ] ], 0.5 ] + ], + [ + [ [ [ 0, -2, -1, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ], [ 1, -2, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ] ], 0.875 ], + [ [ [ 1, -2, -1, 0, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ], [ 1, -2, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ] ], 1.625 ] + ], + [ + [ [ [ 0, -2, -2, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ], [ 1, -2, -1, 1, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ] ], 1.375 ], + [ [ [ 0, -2, -2, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ], [ 2, -2, -1, 0, -2, 0 ], [ 1, -1, -1, 1, -2, 0 ] ], 1.75 ], + [ [ [ 0, -2, -2, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ], [ 2, -2, -1, 0, -2, 0 ], [ 2, -2, -2, 1, -2, 0 ] ], 1.5 ] + ], + [ + [ [ [ 0, -2, -2, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ], [ 1, -1, -2, 1, -2, 0 ], [ 2, -2, -2, 1, -2, 0 ] ], 1.5 ], + [ [ [ 0, -2, -2, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ], [ 1, -1, -2, 1, -2, 0 ], [ 3, -2, -2, 0, -2, 0 ] ], 0.5 ] + ], + [ + [ [ [ 0, -2, -2, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ], [ 1, -1, -2, 1, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], 1.625 ] + ], + [ + [ [ [ -1, 0, -2, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ], [ 1, -1, -2, 1, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], 1 ], + [ [ [ -1, 0, -2, 1, -2, 0 ], [ 2, -2, -1, 1, -2, 0 ], [ 1, -1, -2, 1, -2, 0 ], [ 2, -1, -2, 1, -2, 0 ] ], 1.75 ], + [ [ [ -1, 0, -2, 1, -2, 0 ], [ 1, 0, -2, 1, -2, 0 ], [ 1, -1, -2, 1, -2, 0 ], [ 2, -1, -2, 1, -2, 0 ] ], 1.75 ] + ], + [ + [ [ [ -1, 0, -2, 1, -2, 0 ], [ 1, 0, -2, 1, -2, 0 ], [ 0, 1, -2, 1, -2, 0 ], [ 2, -1, -2, 1, -2, 0 ] ], 0.875 ] + ], + [ + [ [ [ -1, 0, -2, 1, -2, 0 ], [ 0, 2, -2, 1, -2, 0 ], [ 0, 1, -2, 1, -2, 0 ], [ 2, -1, -2, 1, -2, 0 ] ], 0.75 ], + [ [ [ -2, 2, -2, 1, -2, 0 ], [ 0, 2, -2, 1, -2, 0 ], [ 0, 1, -2, 1, -2, 0 ], [ 2, -1, -2, 1, -2, 0 ] ], 0.5 ], + [ [ [ -2, 2, -2, 1, -2, 0 ], [ 0, 2, -2, 1, -2, 0 ], [ 0, 1, -2, 1, -2, 0 ], [ 1, 1, -2, 1, -2, 0 ] ], 0.875 ] + ], + [ + [ [ [ -2, 2, -2, 1, -2, 0 ], [ 0, 2, -2, 1, -2, 0 ], [ -1, 3, -2, 1, -2, 0 ], [ 1, 1, -2, 1, -2, 0 ] ], 1.375 ], + [ [ [ -1, 1, -3, 1, -2, 0 ], [ 0, 2, -2, 1, -2, 0 ], [ -1, 3, -2, 1, -2, 0 ], [ 1, 1, -2, 1, -2, 0 ] ], 1.375 ] + ], + [ + [ [ [ -1, 1, -3, 1, -2, 0 ], [ 0, 2, -2, 1, -2, 0 ], [ -1, 3, -2, 1, -2, 0 ], [ 0, 3, -2, 1, -2, 0 ] ], 1.875 ], + [ [ [ -1, 1, -3, 1, -2, 0 ], [ 0, 2, -2, 1, -2, 0 ], [ 0, 2, -3, 1, -2, 0 ], [ 0, 3, -2, 1, -2, 0 ] ], 1.5 ] + ], + [ + [ [ [ -1, 1, -3, 1, -2, 0 ], [ 1, 1, -3, 1, -2, 0 ], [ 0, 2, -3, 1, -2, 0 ], [ 0, 3, -2, 1, -2, 0 ] ], 0.875 ] + ], + [ + [ [ [ -2, 3, -3, 1, -2, 0 ], [ 1, 1, -3, 1, -2, 0 ], [ 0, 2, -3, 1, -2, 0 ], [ 0, 3, -2, 1, -2, 0 ] ], 1.25 ], + [ [ [ -2, 3, -3, 1, -2, 0 ], [ 1, 1, -3, 1, -2, 0 ], [ 0, 2, -3, 1, -2, 0 ], [ 0, 3, -3, 1, -2, 0 ] ], 1.625 ] + ], + [ + [ [ [ -1, 2, -4, 1, -2, 0 ], [ 1, 1, -3, 1, -2, 0 ], [ 0, 2, -3, 1, -2, 0 ], [ 0, 3, -3, 1, -2, 0 ] ], 0.5 ] + ], + [ + [ [ [ -1, 2, -4, 1, -2, 0 ], [ 1, 1, -3, 1, -2, 0 ], [ 0, 2, -3, 1, -2, 0 ], [ 1, 2, -4, 1, -2, 0 ] ], 1.25 ], + [ [ [ 0, 0, -3, 1, -2, 0 ], [ 1, 1, -3, 1, -2, 0 ], [ 0, 2, -3, 1, -2, 0 ], [ 1, 2, -4, 1, -2, 0 ] ], 1.25 ] + ], + [ + [ [ [ -1, 2, -3, 1, -2, 0 ], [ 1, 1, -3, 1, -2, 0 ], [ 0, 2, -3, 1, -2, 0 ], [ 1, 2, -4, 1, -2, 0 ] ], 0.75 ], + [ [ [ -1, 2, -3, 1, -2, 0 ], [ 1, 1, -3, 1, -2, 0 ], [ 0, 2, -3, 1, -2, 0 ], [ 2, 0, -3, 1, -2, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 2, -3, 1, -2, 0 ], [ 1, 0, -3, 1, -2, 0 ], [ 0, 2, -3, 1, -2, 0 ], [ 2, 0, -3, 1, -2, 0 ] ], 0.75 ], + [ [ [ -1, 2, -3, 1, -2, 0 ], [ 1, 0, -3, 1, -2, 0 ], [ 1, 0, -2, 1, -2, 0 ], [ 2, 0, -3, 1, -2, 0 ] ], 1 ], + [ [ [ 0, 0, -2, 1, -2, 0 ], [ 1, 0, -3, 1, -2, 0 ], [ 1, 0, -2, 1, -2, 0 ], [ 2, 0, -3, 1, -2, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -2, 1, -2, 0 ], [ 1, 0, -3, 1, -2, 0 ], [ 1, 0, -2, 1, -2, 0 ], [ 1, -1, -2, 1, -2, 0 ] ], 0.5 ], + [ [ [ 0, 0, -2, 1, -2, 0 ], [ 0, 0, -2, 2, -2, 0 ], [ 1, 0, -2, 1, -2, 0 ], [ 1, -1, -2, 1, -2, 0 ] ], 0.75 ], + [ [ [ 0, 0, -2, 1, -2, 0 ], [ 0, 0, -2, 2, -2, 0 ], [ 0, 1, -2, 1, -2, 0 ], [ 1, -1, -2, 1, -2, 0 ] ], 0.625 ] + ], + [ + [ [ [ 0, 0, -2, 1, -2, 0 ], [ 0, 0, -2, 2, -2, 0 ], [ 1, 0, -3, 1, -2, 0 ], [ 1, -1, -2, 1, -2, 0 ] ], 0.75 ] + ], + [ + [ [ [ 0, -1, -2, 2, -2, 0 ], [ 0, 0, -2, 2, -2, 0 ], [ 1, 0, -3, 1, -2, 0 ], [ 1, -1, -2, 1, -2, 0 ] ], 0.625 ], + [ [ [ 0, -1, -2, 2, -2, 0 ], [ 1, 0, -2, 1, -2, 0 ], [ 1, 0, -3, 1, -2, 0 ], [ 1, -1, -2, 1, -2, 0 ] ], 0.875 ] + ], + [ + [ [ [ 0, -1, -2, 2, -2, 0 ], [ 1, 0, -2, 1, -2, 0 ], [ 1, 0, -3, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.625 ], + [ [ [ 0, -1, -2, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ], [ 1, 0, -3, 1, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 0.75 ], + [ [ [ 0, -1, -2, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ], [ 0, 0, -2, 2, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.375 ] + ], + [ + [ [ [ 0, -1, -2, 2, -2, 0 ], [ 1, 0, -2, 2, -3, 0 ], [ 0, 0, -2, 2, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.25 ] + ], + [ + [ [ [ 0, -1, -2, 2, -2, 0 ], [ 1, -1, -1, 2, -2, 0 ], [ 0, 0, -2, 2, -2, 0 ], [ 0, -1, -1, 2, -2, 0 ] ], 1.25 ], + [ [ [ 0, -1, -2, 2, -2, 0 ], [ 1, -1, -1, 2, -2, 0 ], [ 0, 0, -2, 2, -2, 0 ], [ 1, -2, -2, 2, -2, 0 ] ], 0.875 ] + ], + [ + [ [ [ 0, -1, -2, 2, -2, 0 ], [ 1, -1, -1, 2, -2, 0 ], [ 0, 0, -2, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], 1.375 ], + [ [ [ 0, -1, -2, 2, -2, 0 ], [ 1, -1, -1, 2, -2, 0 ], [ 1, -1, -3, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], 0.625 ], + [ [ [ 0, -1, -2, 2, -2, 0 ], [ 2, -2, -2, 2, -2, 0 ], [ 1, -1, -3, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], 1 ] + ], + [ + [ [ [ 1, -2, -3, 2, -2, 0 ], [ 2, -2, -2, 2, -2, 0 ], [ 1, -1, -3, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], 0.875 ], + [ [ [ 1, -2, -3, 2, -2, 0 ], [ 2, -2, -2, 2, -2, 0 ], [ 1, -2, -2, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], 0.75 ] + ], + [ + [ [ [ 1, -2, -3, 2, -2, 0 ], [ 2, -1, -2, 2, -3, 0 ], [ 1, -2, -2, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], 1.125 ], + [ [ [ 1, -2, -3, 2, -2, 0 ], [ 2, -1, -2, 2, -3, 0 ], [ 0, 0, -2, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], 1.5 ], + [ [ [ 1, -1, -2, 1, -2, 0 ], [ 2, -1, -2, 2, -3, 0 ], [ 0, 0, -2, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], 1.625 ], + [ [ [ 1, -1, -2, 1, -2, 0 ], [ "Rest" ], [ 0, 0, -2, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], 1.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 0, -2, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], 1.25 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 0, -2, 2, -2, 0 ], [ "Rest" ] ], 1.25 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 7.25 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -2, -3, 2, -2, 0 ], [ 2, -2, -2, 2, -2, 0 ], [ 1, -1, -3, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], + [ [ 1, -2, -3, 2, -2, 0 ], [ 2, -2, -2, 2, -2, 0 ], [ 1, -2, -2, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], + [ [ 1, -2, -3, 2, -2, 0 ], [ 2, -1, -2, 2, -3, 0 ], [ 1, -2, -2, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], + [ [ 1, -2, -3, 2, -2, 0 ], [ 2, -1, -2, 2, -3, 0 ], [ 0, 0, -2, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ], + [ [ 1, -1, -2, 1, -2, 0 ], [ 2, -1, -2, 2, -3, 0 ], [ 0, 0, -2, 2, -2, 0 ], [ 1, -1, -2, 2, -2, 0 ] ] +], +"cur_uid": "55bd25a1", +"ref_uid": "6a9928d6", +"order_seed": 807265, +"dur_seed": 754968, +"motifs_seed": 848720, +"entrances_probs_vals": [ 0, 0, 0, 0.41, 1.84, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 0.41, 1.84, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 0.41, 1.84, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2430, -293 ], [ -869, 1211 ], [ -832, 1285 ], [ -702, 1211 ] ], +"step_probs_vals": [ -1200, 1200, 0.0020576131687243, 0.068181818181818, 0.074074074074074, 0.0625, 0.20576131687243, 0.0625, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61316872427983, 0, 0.98971193415638, 0 ], +"passages_weights": [ 0.48, 0.46, 0.48, 1, 1 ], +"hd_exp": 9, +"hd_invert": 0, +"order": +[ + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 3, 0 ], [ 1, 2 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 2, 0 ], [ 3, 1 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 2 ], [ 0, 3, 1 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 2, 1 ], [ 0, 3 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 2 ], [ 3, 1, 0 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 2, 0 ], [ 1, 3 ], [ ] ], + [ [ 0, 2 ], [ 1, 3 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 0 ], [ 1, 2, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 3 ], [ 0, 2, 1 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 0 ], [ 1, 2, 3 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 0, 2 ], [ 3, 1 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 1 ], [ 2, 3, 0 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 2 ], [ 1, 3, 0 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 1 ], [ 2, 3, 0 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 2 ], [ 0, 3, 1 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 1 ], [ 0, 2, 3 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 2 ], [ 0, 3, 1 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 1, 2 ], [ 0, 3 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 2, 1 ], [ 0, 3 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 2, 0 ], [ 1, 3 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 100, 100 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/55f9b81e/55f9b81e_code.scd b/resources/string_quartet_3/55f9b81e/55f9b81e_code.scd new file mode 100644 index 0000000..2dad11e --- /dev/null +++ b/resources/string_quartet_3/55f9b81e/55f9b81e_code.scd @@ -0,0 +1,958 @@ +( +// 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) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + if(pDistance < 0, {stepFunc.value(abs(pDistance))}, {0.001}); + //stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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/string_quartet_3/55f9b81e/55f9b81e_mus_model.json b/resources/string_quartet_3/55f9b81e/55f9b81e_mus_model.json new file mode 100644 index 0000000..52fe7d6 --- /dev/null +++ b/resources/string_quartet_3/55f9b81e/55f9b81e_mus_model.json @@ -0,0 +1,443 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ -3, 1, 1, 4, 1, -2 ], [ "Rest" ] ], 1.125 ], + [ [ [ -2, 0, 0, 3, 1, -2 ], [ "Rest" ], [ -3, 1, 1, 4, 1, -2 ], [ "Rest" ] ], 1.75 ], + [ [ [ -2, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -3, 1, 1, 4, 1, -2 ], [ "Rest" ] ], 1.375 ], + [ [ [ -2, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -3, 1, 1, 4, 1, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 1 ] + ], + [ + [ [ [ -2, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -2, 0, 0, 4, 1, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 1.5 ] + ], + [ + [ [ [ -2, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -1, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 0.875 ] + ], + [ + [ [ [ -2, 0, 0, 3, 1, -2 ], [ -3, 1, 2, 4, 1, -2 ], [ -1, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 1.125 ] + ], + [ + [ [ [ -4, 1, 2, 4, 1, -2 ], [ -3, 1, 2, 4, 1, -2 ], [ -1, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 0.75 ] + ], + [ + [ [ [ -4, 1, 1, 4, 2, -2 ], [ -3, 1, 2, 4, 1, -2 ], [ -1, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 1.25 ] + ], + [ + [ [ [ -4, 1, 1, 4, 2, -2 ], [ -2, 0, 1, 4, 1, -2 ], [ -1, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 1.125 ] + ], + [ + [ [ [ -4, 1, 1, 4, 2, -2 ], [ -2, 0, 1, 4, 1, -2 ], [ -3, 1, 2, 4, 1, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 1 ] + ], + [ + [ [ [ -4, 2, 1, 4, 1, -2 ], [ -2, 0, 1, 4, 1, -2 ], [ -3, 1, 2, 4, 1, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 1.75 ] + ], + [ + [ [ [ -4, 2, 1, 4, 1, -2 ], [ -2, 1, 2, 3, 1, -2 ], [ -3, 1, 2, 4, 1, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 1.75 ] + ], + [ + [ [ [ -4, 2, 1, 4, 1, -2 ], [ -2, 1, 2, 3, 1, -2 ], [ -2, 0, 1, 4, 1, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 1.25 ] + ], + [ + [ [ [ -4, 2, 1, 4, 1, -2 ], [ -2, 1, 2, 3, 1, -2 ], [ -2, 1, 1, 4, 0, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 0.875 ] + ], + [ + [ [ [ -2, 1, 1, 3, 0, -2 ], [ -2, 1, 2, 3, 1, -2 ], [ -2, 1, 1, 4, 0, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 1, 3, 0, -2 ], [ -2, 1, 2, 3, 1, -2 ], [ -3, 2, 2, 3, 1, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 1.625 ] + ], + [ + [ [ [ -2, 1, 1, 3, 0, -2 ], [ -2, 2, 2, 3, 0, -2 ], [ -3, 2, 2, 3, 1, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 1, 3, 0, -2 ], [ -2, 2, 2, 3, 0, -2 ], [ -3, 3, 2, 3, 0, -2 ], [ -2, 1, 1, 4, 1, -2 ] ], 1.75 ] + ], + [ + [ [ [ -2, 1, 1, 3, 0, -2 ], [ -2, 2, 2, 3, 0, -2 ], [ -3, 3, 2, 3, 0, -2 ], [ -2, 2, 2, 3, 1, -2 ] ], 1.25 ] + ], + [ + [ [ [ -2, 1, 1, 3, 0, -2 ], [ -2, 2, 2, 3, 0, -2 ], [ -2, 1, 1, 3, 0, -1 ], [ -2, 2, 2, 3, 1, -2 ] ], 1.625 ] + ], + [ + [ [ [ -2, 0, 1, 3, 0, -1 ], [ -2, 2, 2, 3, 0, -2 ], [ -2, 1, 1, 3, 0, -1 ], [ -2, 2, 2, 3, 1, -2 ] ], 1.375 ] + ], + [ + [ [ [ -2, 0, 1, 3, 0, -1 ], [ -2, 2, 2, 3, 0, -2 ], [ -1, 0, 0, 3, 0, -1 ], [ -2, 2, 2, 3, 1, -2 ] ], 1.75 ] + ], + [ + [ [ [ -2, 0, 1, 3, 0, -1 ], [ -2, 2, 2, 3, 0, -2 ], [ -2, 0, 1, 3, 0, 0 ], [ -2, 2, 2, 3, 1, -2 ] ], 1 ] + ], + [ + [ [ [ -2, 0, 1, 3, 0, -1 ], [ -2, 2, 2, 3, 0, -2 ], [ -2, 0, 1, 3, 0, 0 ], [ -1, 2, 2, 3, -1, -2 ] ], 1.375 ] + ], + [ + [ [ [ -2, -1, 1, 3, 0, 0 ], [ -2, 2, 2, 3, 0, -2 ], [ -2, 0, 1, 3, 0, 0 ], [ -1, 2, 2, 3, -1, -2 ] ], 1.375 ] + ], + [ + [ [ [ -2, -1, 1, 3, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -2, 0, 1, 3, 0, 0 ], [ -1, 2, 2, 3, -1, -2 ] ], 1.625 ] + ], + [ + [ [ [ -2, -1, 1, 3, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -2, 0, 1, 3, 0, 0 ], [ -2, 0, 1, 3, 0, 1 ] ], 1.25 ] + ], + [ + [ [ [ -2, -1, 1, 3, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -2, 0, 1, 3, 0, 0 ], [ -2, 0, 1, 4, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ -2, -1, 1, 3, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -2, 0, 1, 3, 0, 0 ], [ -1, -1, 1, 3, 1, 0 ] ], 1.25 ] + ], + [ + [ [ [ -2, -1, 1, 3, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -1, -1, 0, 3, 0, 0 ], [ -1, -1, 1, 3, 1, 0 ] ], 0.75 ] + ], + [ + [ [ [ -2, -1, 1, 3, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -1, -1, 0, 3, 0, 0 ], [ -1, -1, 0, 4, 0, 0 ] ], 1.125 ] + ], + [ + [ [ [ -1, 0, 1, 2, 0, -1 ], [ -1, 0, 1, 2, 0, 0 ], [ -1, -1, 0, 3, 0, 0 ], [ -1, -1, 0, 4, 0, 0 ] ], 0.875 ] + ], + [ + [ [ [ -1, -1, 1, 2, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -1, -1, 0, 3, 0, 0 ], [ -1, -1, 0, 4, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ -2, 0, 0, 3, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -1, -1, 0, 3, 0, 0 ], [ -1, -1, 0, 4, 0, 0 ] ], 1.5 ] + ], + [ + [ [ [ -2, -1, 0, 4, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -1, -1, 0, 3, 0, 0 ], [ -1, -1, 0, 4, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ -2, -1, 0, 4, 0, 0 ], [ -1, -1, 1, 3, 0, 0 ], [ -1, -1, 0, 3, 0, 0 ], [ -1, -1, 0, 4, 0, 0 ] ], 1.75 ] + ], + [ + [ [ [ -2, -1, 0, 4, 0, 0 ], [ -1, -1, 1, 3, 0, 0 ], [ -1, -1, 0, 3, 0, 0 ], [ -1, -1, 1, 4, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, -1, 0, 4, 0, 0 ], [ -1, -1, 1, 3, 0, 0 ], [ -1, -1, 0, 3, 0, 0 ], [ -1, 0, 0, 3, 0, 0 ] ], 1.25 ] + ], + [ + [ [ [ -1, -1, 1, 3, -1, 0 ], [ -1, -1, 1, 3, 0, 0 ], [ -1, -1, 0, 3, 0, 0 ], [ -1, 0, 0, 3, 0, 0 ] ], 0.75 ] + ], + [ + [ [ [ -1, -1, 1, 3, -1, 0 ], [ -1, -1, 1, 3, 0, 0 ], [ -2, -1, 1, 3, 0, 1 ], [ -1, 0, 0, 3, 0, 0 ] ], 1.5 ] + ], + [ + [ [ [ -1, -1, 1, 3, -1, 0 ], [ -1, -1, 1, 3, 0, 0 ], [ -1, 0, 0, 3, -1, 0 ], [ -1, 0, 0, 3, 0, 0 ] ], 1.75 ] + ], + [ + [ [ [ -1, -1, 1, 3, -1, 0 ], [ 0, -1, 1, 3, -2, 0 ], [ -1, 0, 0, 3, -1, 0 ], [ -1, 0, 0, 3, 0, 0 ] ], 1.25 ] + ], + [ + [ [ [ -1, -1, 1, 3, -1, 0 ], [ -1, 0, 1, 3, -1, 0 ], [ -1, 0, 0, 3, -1, 0 ], [ -1, 0, 0, 3, 0, 0 ] ], 1.5 ] + ], + [ + [ [ [ -1, -1, 1, 3, -1, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ -1, 0, 0, 3, -1, 0 ], [ -1, 0, 0, 3, 0, 0 ] ], 1.75 ] + ], + [ + [ [ [ -1, -1, 0, 3, 0, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ -1, 0, 0, 3, -1, 0 ], [ -1, 0, 0, 3, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 0, 0, 3, 1, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ -1, 0, 0, 3, -1, 0 ], [ -1, 0, 0, 3, 0, 0 ] ], 1.375 ] + ], + [ + [ [ [ -2, 0, 0, 3, 1, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ -1, 0, 0, 3, -1, 0 ], [ -1, 1, 0, 3, -1, 0 ] ], 1.875 ] + ], + [ + [ [ [ -2, 1, 0, 3, 0, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ -1, 0, 0, 3, -1, 0 ], [ -1, 1, 0, 3, -1, 0 ] ], 1.375 ] + ], + [ + [ [ [ -2, 1, 0, 3, 0, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ -1, 0, 0, 3, -1, 0 ], [ -1, 0, 0, 3, -1, 1 ] ], 1.75 ] + ], + [ + [ [ [ -2, 1, 0, 3, 0, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ -1, -1, 0, 4, -1, 0 ], [ -1, 0, 0, 3, -1, 1 ] ], 1.625 ] + ], + [ + [ [ [ 0, -1, -1, 3, -1, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ -1, -1, 0, 4, -1, 0 ], [ -1, 0, 0, 3, -1, 1 ] ], 0.625 ] + ], + [ + [ [ [ 0, -1, -1, 3, -1, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ -1, -1, 0, 4, -1, 0 ], [ 1, -2, 0, 3, -1, 0 ] ], 1.125 ] + ], + [ + [ [ [ 0, -1, -1, 3, -1, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ 1, -2, 0, 3, -2, 0 ], [ 1, -2, 0, 3, -1, 0 ] ], 0.75 ] + ], + [ + [ [ [ 1, -3, 0, 3, -1, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ 1, -2, 0, 3, -2, 0 ], [ 1, -2, 0, 3, -1, 0 ] ], 0.875 ] + ], + [ + [ [ [ 1, -3, 0, 3, -1, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ 0, -2, 0, 3, -1, 1 ], [ 1, -2, 0, 3, -1, 0 ] ], 0.875 ] + ], + [ + [ [ [ 1, -3, 0, 3, -1, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ 2, -3, 0, 3, -1, -1 ], [ 1, -2, 0, 3, -1, 0 ] ], 1.375 ] + ], + [ + [ [ [ 1, -2, 0, 3, -2, 0 ], [ 0, -1, 0, 3, -1, 0 ], [ 2, -3, 0, 3, -1, -1 ], [ 1, -2, 0, 3, -1, 0 ] ], 1.125 ] + ], + [ + [ [ [ 1, -2, 0, 3, -2, 0 ], [ 1, -2, -1, 3, -1, 0 ], [ 2, -3, 0, 3, -1, -1 ], [ 1, -2, 0, 3, -1, 0 ] ], 1 ] + ], + [ + [ [ [ 1, -2, 0, 3, -2, 0 ], [ 2, -2, 0, 2, -2, 0 ], [ 2, -3, 0, 3, -1, -1 ], [ 1, -2, 0, 3, -1, 0 ] ], 0.75 ] + ], + [ + [ [ [ 1, -2, 0, 3, -2, 0 ], [ 2, -2, 0, 2, -2, 0 ], [ 0, -2, 0, 4, -1, 0 ], [ 1, -2, 0, 3, -1, 0 ] ], 1.75 ] + ], + [ + [ [ [ 1, -2, 0, 3, -2, 0 ], [ 2, -2, 0, 2, -2, 0 ], [ 0, -2, 0, 4, -1, 0 ], [ 2, -2, 0, 3, -3, 0 ] ], 1.5 ] + ], + [ + [ [ [ 1, -2, 0, 3, -2, 0 ], [ 1, -2, 1, 3, -2, 0 ], [ 0, -2, 0, 4, -1, 0 ], [ 2, -2, 0, 3, -3, 0 ] ], 1.625 ] + ], + [ + [ [ [ 1, -2, 0, 3, -2, 0 ], [ 1, -2, 0, 3, -1, 0 ], [ 0, -2, 0, 4, -1, 0 ], [ 2, -2, 0, 3, -3, 0 ] ], 1 ] + ], + [ + [ [ [ 1, -1, 0, 3, -3, 0 ], [ 1, -2, 0, 3, -1, 0 ], [ 0, -2, 0, 4, -1, 0 ], [ 2, -2, 0, 3, -3, 0 ] ], 1.625 ] + ], + [ + [ [ [ 2, -2, -1, 3, -3, 0 ], [ 1, -2, 0, 3, -1, 0 ], [ 0, -2, 0, 4, -1, 0 ], [ 2, -2, 0, 3, -3, 0 ] ], 0.75 ] + ], + [ + [ [ [ -1, -3, 0, 3, -1, 0 ], [ 1, -2, 0, 3, -1, 0 ], [ 0, -2, 0, 4, -1, 0 ], [ 2, -2, 0, 3, -3, 0 ] ], 1.125 ] + ], + [ + [ [ [ -1, -3, 0, 3, -1, 0 ], [ 1, -2, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, 0 ], [ 2, -2, 0, 3, -3, 0 ] ], 0.875 ] + ], + [ + [ [ [ -1, -3, 0, 3, -1, 0 ], [ 1, -3, 0, 3, -1, 1 ], [ 2, -4, 0, 3, -1, 0 ], [ 2, -2, 0, 3, -3, 0 ] ], 1.25 ] + ], + [ + [ [ [ -1, -3, 0, 3, -1, 0 ], [ 1, -3, 0, 3, -1, 1 ], [ 2, -4, 0, 3, -1, 0 ], [ 2, -4, 1, 3, -1, 0 ] ], 0.875 ] + ], + [ + [ [ [ -1, -3, 0, 3, -1, 0 ], [ 1, -3, 0, 3, -1, 1 ], [ 2, -4, 0, 3, -1, 0 ], [ 1, -4, 0, 3, -1, 0 ] ], 1.875 ] + ], + [ + [ [ [ -1, -3, 0, 3, -1, 0 ], [ 2, -4, 1, 3, -1, 0 ], [ 2, -4, 0, 3, -1, 0 ], [ 1, -4, 0, 3, -1, 0 ] ], 1.625 ] + ], + [ + [ [ [ -1, -3, 0, 3, -1, 0 ], [ 0, -4, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, 0 ], [ 1, -4, 0, 3, -1, 0 ] ], 0.875 ] + ], + [ + [ [ [ -1, -3, 0, 3, -1, 0 ], [ -1, -2, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, 0 ], [ 1, -4, 0, 3, -1, 0 ] ], 1.625 ] + ], + [ + [ [ [ -1, -3, 0, 3, -1, 0 ], [ -1, -2, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, 0 ], [ 0, -2, 0, 3, -1, 0 ] ], 1.75 ] + ], + [ + [ [ [ -1, -3, 0, 3, -1, 0 ], [ 0, -3, -1, 3, -1, 0 ], [ 2, -4, 0, 3, -1, 0 ], [ 0, -2, 0, 3, -1, 0 ] ], 0.875 ] + ], + [ + [ [ [ -2, -2, 0, 3, 0, 0 ], [ 0, -3, -1, 3, -1, 0 ], [ 2, -4, 0, 3, -1, 0 ], [ 0, -2, 0, 3, -1, 0 ] ], 0.625 ] + ], + [ + [ [ [ -1, -4, 0, 3, -1, 1 ], [ 0, -3, -1, 3, -1, 0 ], [ 2, -4, 0, 3, -1, 0 ], [ 0, -2, 0, 3, -1, 0 ] ], 0.875 ] + ], + [ + [ [ [ -1, -4, 0, 3, -1, 1 ], [ 1, -5, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, 0 ], [ 0, -2, 0, 3, -1, 0 ] ], 1.375 ] + ], + [ + [ [ [ -1, -4, 0, 3, -1, 1 ], [ 1, -5, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, 0 ], [ 1, -5, 0, 4, -1, 0 ] ], 1.625 ] + ], + [ + [ [ [ -1, -4, 0, 3, -1, 1 ], [ 1, -5, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 1 ] + ], + [ + [ [ [ 1, -6, 0, 3, -1, 0 ], [ 1, -5, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 0.625 ] + ], + [ + [ [ [ 1, -6, 0, 3, -1, 0 ], [ 1, -5, 0, 3, -1, 0 ], [ 3, -5, -1, 3, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 0.625 ] + ], + [ + [ [ [ 1, -6, 0, 3, -1, 0 ], [ 1, -5, 0, 3, -1, 0 ], [ 3, -6, 1, 3, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 1.625 ] + ], + [ + [ [ [ 0, -6, 1, 3, -1, 1 ], [ 1, -5, 0, 3, -1, 0 ], [ 3, -6, 1, 3, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 1.25 ] + ], + [ + [ [ [ 0, -6, 1, 3, -1, 1 ], [ 0, -5, 1, 3, -1, 1 ], [ 3, -6, 1, 3, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 0.75 ] + ], + [ + [ [ [ 0, -6, 1, 4, -1, 0 ], [ 0, -5, 1, 3, -1, 1 ], [ 3, -6, 1, 3, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 0.75 ] + ], + [ + [ [ [ 0, -6, 1, 4, -1, 0 ], [ 0, -5, 1, 4, -1, 0 ], [ 3, -6, 1, 3, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 0.875 ] + ], + [ + [ [ [ 0, -6, 1, 4, -1, 0 ], [ 0, -5, 1, 4, -1, 0 ], [ 2, -6, 2, 4, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 1 ] + ], + [ + [ [ [ 0, -6, 1, 4, -1, 0 ], [ 0, -5, 1, 4, -1, 0 ], [ 3, -7, 1, 4, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 1 ] + ], + [ + [ [ [ 0, -6, 1, 4, -1, 0 ], [ 2, -7, 1, 3, -1, 0 ], [ 3, -7, 1, 4, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 1.25 ] + ], + [ + [ [ [ 1, -7, 0, 4, -1, 0 ], [ 2, -7, 1, 3, -1, 0 ], [ 3, -7, 1, 4, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 0.625 ] + ], + [ + [ [ [ 0, -7, 1, 5, -1, 0 ], [ 2, -7, 1, 3, -1, 0 ], [ 3, -7, 1, 4, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 0.75 ] + ], + [ + [ [ [ 0, -7, 1, 5, -1, 0 ], [ 2, -7, 1, 3, -1, 0 ], [ 4, -7, 1, 2, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 1 ] + ], + [ + [ [ [ 0, -7, 1, 5, -1, 0 ], [ 2, -5, 0, 3, -1, -1 ], [ 4, -7, 1, 2, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 1.625 ] + ], + [ + [ [ [ 3, -7, 1, 2, -2, 0 ], [ 2, -5, 0, 3, -1, -1 ], [ 4, -7, 1, 2, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 1.75 ] + ], + [ + [ [ [ 3, -7, 1, 2, -2, 0 ], [ 2, -5, 0, 3, -1, -1 ], [ 5, -7, 1, 2, -3, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 0.875 ] + ], + [ + [ [ [ 3, -7, 1, 2, -2, 0 ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -3, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, -1 ] ], 1.25 ] + ], + [ + [ [ [ 3, -7, 1, 2, -2, 0 ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 1.75 ] + ], + [ + [ [ [ 2, -5, -1, 3, -1, -1 ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], 1.625 ] + ], + [ + [ [ [ 2, -5, -1, 3, -1, -1 ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, 0 ], [ 3, -5, -1, 3, -1, -1 ] ], 1.25 ] + ], + [ + [ [ [ 2, -5, -1, 3, -1, -1 ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, 0 ], [ 2, -5, 0, 4, -1, -1 ] ], 1.375 ] + ], + [ + [ [ [ 2, -5, -1, 3, -1, -1 ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, 0 ], [ 4, -5, -1, 2, -1, -1 ] ], 1.625 ], + [ [ [ "Rest" ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, 0 ], [ 4, -5, -1, 2, -1, -1 ] ], 1.125 ], + [ [ [ "Rest" ], [ 2, -5, 0, 3, -1, -1 ], [ "Rest" ], [ 4, -5, -1, 2, -1, -1 ] ], 1.75 ], + [ [ [ "Rest" ], [ 2, -5, 0, 3, -1, -1 ], [ "Rest" ], [ "Rest" ] ], 1.5 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.0 ] + ] + ] +], +"last_changes": +[ + [ [ 3, -7, 1, 2, -2, 0 ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], + [ [ 2, -5, -1, 3, -1, -1 ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, 0 ], [ 2, -4, 0, 3, -1, -1 ] ], + [ [ 2, -5, -1, 3, -1, -1 ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, 0 ], [ 3, -5, -1, 3, -1, -1 ] ], + [ [ 2, -5, -1, 3, -1, -1 ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, 0 ], [ 2, -5, 0, 4, -1, -1 ] ], + [ [ 2, -5, -1, 3, -1, -1 ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, 0 ], [ 4, -5, -1, 2, -1, -1 ] ] +], +"cur_uid": "55f9b81e", +"ref_uid": "726a40c7", +"order_seed": 540514, +"dur_seed": 331257, +"motifs_seed": 728928, +"entrances_probs_vals": [ 0, 0, 0, 0.66, 1.8406593406593, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 0.66, 1.8406593406593, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 0.66, 1.8406593406593, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2467, 2400 ], [ -1167, 2400 ], [ -702, 2400 ], [ -702, 2400 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.090534979423868, 0.92613636363636, 0.17489711934156, 0.079545454545455, 0.37037037037037, 0, 0.7201646090535, 0, 1, 0 ], +"passages_weights": [ 0.63, 0.62, 1, 0.41, 1 ], +"hd_exp": 3.09, +"hd_invert": 0, +"order": +[ + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ] +], +"sus_weights": [ 0, 0, 0.61 ], +"order_size": [ 100, 100 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/577cf188/577cf188_code.scd b/resources/string_quartet_3/577cf188/577cf188_code.scd new file mode 100644 index 0000000..5a3854b --- /dev/null +++ b/resources/string_quartet_3/577cf188/577cf188_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 = true; + 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/string_quartet_3/577cf188/577cf188_mus_model.json b/resources/string_quartet_3/577cf188/577cf188_mus_model.json new file mode 100644 index 0000000..b37c8be --- /dev/null +++ b/resources/string_quartet_3/577cf188/577cf188_mus_model.json @@ -0,0 +1,198 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 2.375 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1.875 ] + ], + [ + [ [ [ 0, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1.5 ] + ], + [ + [ [ [ 0, -1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1.125 ] + ], + [ + [ [ [ 1, -2, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ 0, -1, 1, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 2.5 ] + ], + [ + [ [ [ 0, -1, 1, 0, -1, 0 ], [ 0, 0, 1, 0, -1, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 2 ] + ], + [ + [ [ [ 0, -1, 1, 0, -1, 0 ], [ 0, -1, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 2.625 ] + ], + [ + [ [ [ 0, -1, 1, 0, -1, 0 ], [ 0, -1, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, -1, 0 ] ], 1.125 ] + ], + [ + [ [ [ 0, -1, 1, 0, -1, 0 ], [ 0, -1, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, -1, 0 ] ], 1.875 ] + ], + [ + [ [ [ -1, 0, 2, 0, -1, 0 ], [ 0, -1, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, -1, 0 ] ], 1.125 ] + ], + [ + [ [ [ 0, -2, 1, 0, 0, 0 ], [ 0, -1, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, -1, 0 ] ], 1.625 ] + ], + [ + [ [ [ 0, -2, 1, 0, 0, 0 ], [ 0, -1, 1, 0, 0, 0 ], [ 0, -2, 1, 1, 0, 0 ], [ 0, 0, 1, 0, -1, 0 ] ], 1.375 ] + ], + [ + [ [ [ 0, -2, 1, 0, 0, 0 ], [ 0, -1, 1, 0, 0, 0 ], [ 1, -1, 1, -1, 0, 0 ], [ 0, 0, 1, 0, -1, 0 ] ], 1.875 ] + ], + [ + [ [ [ 0, 0, 1, -1, -1, 0 ], [ 0, -1, 1, 0, 0, 0 ], [ 1, -1, 1, -1, 0, 0 ], [ 0, 0, 1, 0, -1, 0 ] ], 2.5 ] + ], + [ + [ [ [ 0, 0, 1, -1, -1, 0 ], [ -1, 0, 1, 1, -1, 0 ], [ 1, -1, 1, -1, 0, 0 ], [ 0, 0, 1, 0, -1, 0 ] ], 2 ] + ], + [ + [ [ [ 0, 0, 1, -1, -1, 0 ], [ 1, -1, 0, -1, 0, 0 ], [ 1, -1, 1, -1, 0, 0 ], [ 0, 0, 1, 0, -1, 0 ] ], 1.25 ] + ], + [ + [ [ [ 0, 0, 1, -1, -1, 0 ], [ 1, -1, 0, -1, 0, 0 ], [ 1, -1, 1, -1, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ] ], 2 ] + ], + [ + [ [ [ 0, 0, 1, -1, -1, 0 ], [ 1, 0, 2, -1, 0, 0 ], [ 1, -1, 1, -1, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ] ], 2.5 ] + ], + [ + [ [ [ 0, 0, 1, -1, -1, 0 ], [ 1, 0, 2, -1, 0, 0 ], [ 0, 0, 2, -1, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ] ], 2.375 ] + ], + [ + [ [ [ 0, 0, 1, -1, -1, 0 ], [ 1, 0, 2, -1, 0, 0 ], [ 0, 0, 2, -1, 0, 0 ], [ 0, 1, 2, -1, 0, 0 ] ], 2.125 ] + ], + [ + [ [ [ 0, 0, 1, -1, -1, 0 ], [ 1, 0, 2, -1, 0, 0 ], [ 0, 0, 1, -1, -1, 1 ], [ 0, 1, 2, -1, 0, 0 ] ], 1.375 ] + ], + [ + [ [ [ 0, 0, 1, -1, -1, 0 ], [ 1, 0, 2, -1, 0, 0 ], [ 0, 1, 2, -1, 0, -1 ], [ 0, 1, 2, -1, 0, 0 ] ], 1.25 ] + ], + [ + [ [ [ 0, 1, 2, -1, 0, -2 ], [ 1, 0, 2, -1, 0, 0 ], [ 0, 1, 2, -1, 0, -1 ], [ 0, 1, 2, -1, 0, 0 ] ], 2.875 ] + ], + [ + [ [ [ 0, 1, 2, -1, 0, -2 ], [ 1, 1, 2, -1, 0, -1 ], [ 0, 1, 2, -1, 0, -1 ], [ 0, 1, 2, -1, 0, 0 ] ], 2.5 ] + ], + [ + [ [ [ 0, 1, 2, -1, 0, -2 ], [ 1, 1, 2, -1, 0, -1 ], [ 0, 1, 2, -1, 0, -1 ], [ 2, 1, 2, -1, 0, -3 ] ], 1.875 ] + ], + [ + [ [ [ 0, 1, 2, -1, 0, -2 ], [ 3, 1, 2, -1, 0, -4 ], [ 0, 1, 2, -1, 0, -1 ], [ 2, 1, 2, -1, 0, -3 ] ], 1.375 ] + ], + [ + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 3, 1, 2, -1, 0, -4 ], [ 0, 1, 2, -1, 0, -1 ], [ 2, 1, 2, -1, 0, -3 ] ], 2.25 ] + ], + [ + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 3, 1, 2, -1, 0, -4 ], [ 0, 1, 2, -1, 0, -1 ], [ 2, 2, 2, -1, 0, -4 ] ], 2 ] + ], + [ + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 3, 1, 2, -1, 0, -4 ], [ 0, 1, 2, -1, 0, -1 ], [ 2, 1, 2, -3, 0, -1 ] ], 2 ] + ], + [ + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 3, 1, 2, -1, 0, -4 ], [ 0, 1, 2, -1, 0, -1 ], [ 0, 1, 3, -1, 0, -1 ] ], 2 ] + ], + [ + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 2, 2, -2, 0, -1 ], [ 0, 1, 2, -1, 0, -1 ], [ 0, 1, 3, -1, 0, -1 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 2, 2, -2, 0, -1 ], [ 2, 1, 2, -3, 0, -1 ], [ 0, 1, 3, -1, 0, -1 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 2, 2, -2, 0, -1 ], [ 2, 1, 2, -3, 0, -1 ], [ 2, 1, 1, -2, 0, -1 ] ], 2.625 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 2, 1, 2, -2, -1, -1 ], [ 2, 1, 2, -3, 0, -1 ], [ 2, 1, 1, -2, 0, -1 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 2, 1, 2, -2, -1, -1 ], [ 1, 1, 2, -1, 0, -1 ], [ 2, 1, 1, -2, 0, -1 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 2, 1, 2, -2, -1, -1 ], [ 1, 1, 2, -1, 0, -1 ], [ 3, 1, 2, -3, 0, -1 ] ], 1.625 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 1, -1 ], [ 1, 1, 2, -1, 0, -1 ], [ 3, 1, 2, -3, 0, -1 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 1, -1 ], [ 1, 1, 2, -2, 0, 0 ], [ 3, 1, 2, -3, 0, -1 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 1, -1 ], [ 1, 1, 2, -2, 0, 0 ], [ 1, 1, 3, -2, 0, -1 ] ], 1.75 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 2, 0, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 0, 0 ], [ 1, 1, 3, -2, 0, -1 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 2, 0, 2, -2, 0, -1 ], [ 1, 2, 2, -2, 0, -1 ], [ 1, 1, 3, -2, 0, -1 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 2, 0, 2, -2, 0, -1 ], [ 1, 2, 2, -2, 0, -1 ], [ 2, 1, 2, -2, 0, -2 ] ], 1.375 ] + ], + [ + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 0, 2, 2, -1, 0, -1 ], [ 1, 2, 2, -2, 0, -1 ], [ 2, 1, 2, -2, 0, -2 ] ], 1.625 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 0, 2, 2, -2, 0, 0 ], [ 1, 2, 2, -2, 0, -1 ], [ 2, 1, 2, -2, 0, -2 ] ], 1.625 ] + ], + [ + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 2, 1, 2, -3, 0, -1 ], [ 1, 2, 2, -2, 0, -1 ], [ 2, 1, 2, -2, 0, -2 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 2, 1, 2, -3, 0, -1 ], [ 2, 1, 2, -2, -1, -1 ], [ 2, 1, 2, -2, 0, -2 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 2, 1, 2, -3, 0, -1 ], [ 2, 1, 2, -2, -1, -1 ], [ 3, 1, 2, -2, 0, -2 ] ], 2.625 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 0, 2, 2, -2, 0, -1 ], [ 2, 1, 2, -2, -1, -1 ], [ 3, 1, 2, -2, 0, -2 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 0, 2, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 1, -1 ], [ 3, 1, 2, -2, 0, -2 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 0, 2, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 1, -1 ], [ 3, 1, 2, -3, 0, -1 ] ], 2.125 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, -1, -1 ], [ 1, 1, 2, -2, 1, -1 ], [ 3, 1, 2, -3, 0, -1 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, -1, -1 ], [ 2, 0, 2, -2, 0, -1 ], [ 3, 1, 2, -3, 0, -1 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, -1, -1 ], [ 2, 0, 2, -2, 0, -1 ], [ 2, 1, 2, -2, 0, -1 ] ], 1.5 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 0, -1 ], [ 2, 0, 2, -2, 0, -1 ], [ 2, 1, 2, -2, 0, -1 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 0, -1 ], [ 1, 1, 3, -2, 0, -1 ], [ 2, 1, 2, -2, 0, -1 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 0, -1 ], [ 1, 1, 3, -2, 0, -1 ], [ 2, 1, 3, -2, 0, -1 ] ], 2.25 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 0, -1 ], [ "Rest" ], [ 2, 1, 3, -2, 0, -1 ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 0, -1 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ 0, 1, 2, -2, 0, -1 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 7.0 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, -1, -1 ], [ 2, 0, 2, -2, 0, -1 ], [ 3, 1, 2, -3, 0, -1 ] ], + [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, -1, -1 ], [ 2, 0, 2, -2, 0, -1 ], [ 2, 1, 2, -2, 0, -1 ] ], + [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 0, -1 ], [ 2, 0, 2, -2, 0, -1 ], [ 2, 1, 2, -2, 0, -1 ] ], + [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 0, -1 ], [ 1, 1, 3, -2, 0, -1 ], [ 2, 1, 2, -2, 0, -1 ] ], + [ [ 0, 1, 2, -2, 0, -1 ], [ 1, 1, 2, -2, 0, -1 ], [ 1, 1, 3, -2, 0, -1 ], [ 2, 1, 3, -2, 0, -1 ] ] +], +"cur_uid": "577cf188", +"ref_uid": "nil", +"order_seed": 755225, +"dur_seed": 543380, +"motifs_seed": 119394, +"entrances_probs_vals": [ 1, 0, 0, 1.1263736263736, 2.83, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0, 1.1263736263736, 2.83, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0, 1.1263736263736, 2.83, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1315, 1582.6625386997 ], [ -702.16718266254, 1453 ], [ -386, 1676 ], [ -219, 1694 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 1, 1, 0.66, 0.74, 1 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 0 ], [ 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3 ], [ ] ], + [ [ 3, 2, 1 ], [ 1 ], [ ] ], + [ [ 0 ], [ 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3 ], [ ] ] +], +"sus_weights": [ 0, 0, 0.51 ], +"order_size": [ 30, 30 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/5ec14635/5ec14635_code.scd b/resources/string_quartet_3/5ec14635/5ec14635_code.scd new file mode 100644 index 0000000..f822edb --- /dev/null +++ b/resources/string_quartet_3/5ec14635/5ec14635_code.scd @@ -0,0 +1,946 @@ +( +// 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 = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + if(pDistance < 0, {stepFunc.value(abs(pDistance))}, {0.001}); + //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/string_quartet_3/5ec14635/5ec14635_mus_model.json b/resources/string_quartet_3/5ec14635/5ec14635_mus_model.json new file mode 100644 index 0000000..c4309f6 --- /dev/null +++ b/resources/string_quartet_3/5ec14635/5ec14635_mus_model.json @@ -0,0 +1,163 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ -2, -1, 0, 0, 0, -1 ], [ "Rest" ] ], 0.375 ], + [ [ [ "Rest" ], [ -2, -2, 0, 1, 0, -2 ], [ -2, -1, 0, 0, 0, -1 ], [ "Rest" ] ], 0 ], + [ [ [ -2, -1, 0, 1, 0, -2 ], [ -2, -2, 0, 1, 0, -2 ], [ -2, -1, 0, 0, 0, -1 ], [ "Rest" ] ], 0.5 ], + [ [ [ -2, -1, 0, 1, 0, -2 ], [ -2, -2, 0, 1, 0, -2 ], [ -2, -1, 0, 0, 0, -1 ], [ 1, -2, 0, 2, 0, -2 ] ], 0.125 ] + ], + [ + [ [ [ -2, -1, 0, 1, 0, -2 ], [ -2, -2, 0, 1, 0, -2 ], [ 0, 0, 0, 1, 0, -2 ], [ 1, -2, 0, 2, 0, -2 ] ], 1 ] + ], + [ + [ [ [ -2, -1, 0, 1, 0, -2 ], [ -2, -2, 0, 1, 0, -2 ], [ 0, 0, 0, 1, 0, -2 ], [ 0, 0, 0, 1, 0, -1 ] ], 1.125 ] + ], + [ + [ [ [ -2, -1, 0, 1, 0, -2 ], [ -1, 1, 0, 1, 0, -1 ], [ 0, 0, 0, 1, 0, -2 ], [ 0, 0, 0, 1, 0, -1 ] ], 1.125 ] + ], + [ + [ [ [ -2, -1, 0, 1, 0, -2 ], [ 0, 0, 1, 1, 0, -2 ], [ 0, 0, 0, 1, 0, -2 ], [ 0, 0, 0, 1, 0, -1 ] ], 0.125 ] + ], + [ + [ [ [ -2, -1, 0, 1, 0, -2 ], [ 0, 0, 1, 1, 0, -2 ], [ -1, 0, 1, 1, 0, -1 ], [ 0, 0, 0, 1, 0, -1 ] ], 0.75 ] + ], + [ + [ [ [ -2, -1, 0, 1, 0, -2 ], [ 0, 0, 1, 1, 0, -2 ], [ -1, 0, 1, 2, 0, -2 ], [ 0, 0, 0, 1, 0, -1 ] ], 0.75 ] + ], + [ + [ [ [ -2, -1, 0, 1, 0, -2 ], [ 0, 0, 1, 1, 0, -2 ], [ 0, -1, 0, 2, 0, -2 ], [ 0, 0, 0, 1, 0, -1 ] ], 0 ] + ], + [ + [ [ [ -2, -1, 0, 1, 0, -2 ], [ 0, 0, 1, 1, 0, -2 ], [ -1, 1, 0, 1, 0, -1 ], [ 0, 0, 0, 1, 0, -1 ] ], 0.625 ] + ], + [ + [ [ [ -2, -1, 0, 1, 0, -2 ], [ 0, 0, 1, 1, 0, -2 ], [ -1, 1, 0, 1, 0, -1 ], [ 0, 0, 1, 1, 1, -2 ] ], 1.125 ] + ], + [ + [ [ [ -2, -1, 0, 1, 0, -2 ], [ 0, 0, 1, 1, 0, -2 ], [ -1, 1, 1, 1, 1, -2 ], [ 0, 0, 1, 1, 1, -2 ] ], 1 ] + ], + [ + [ [ [ -2, -1, 0, 1, 0, -2 ], [ 0, 0, 1, 1, 0, -2 ], [ -1, 1, 1, 1, 1, -2 ], [ 1, -1, 0, 1, 1, -2 ] ], 0.625 ] + ], + [ + [ [ [ -2, -1, 0, 1, 0, -2 ], [ 1, -1, 0, 1, 0, -2 ], [ -1, 1, 1, 1, 1, -2 ], [ 1, -1, 0, 1, 1, -2 ] ], 0.75 ] + ], + [ + [ [ [ -1, -1, 0, 2, 1, -2 ], [ 1, -1, 0, 1, 0, -2 ], [ -1, 1, 1, 1, 1, -2 ], [ 1, -1, 0, 1, 1, -2 ] ], 1.125 ] + ], + [ + [ [ [ -1, 0, 1, 1, 1, -2 ], [ 1, -1, 0, 1, 0, -2 ], [ -1, 1, 1, 1, 1, -2 ], [ 1, -1, 0, 1, 1, -2 ] ], 0.25 ] + ], + [ + [ [ [ -1, 0, 1, 1, 1, -2 ], [ -1, 0, 1, 1, 1, -1 ], [ -1, 1, 1, 1, 1, -2 ], [ 1, -1, 0, 1, 1, -2 ] ], 0.25 ] + ], + [ + [ [ [ -1, 0, 1, 1, 1, -2 ], [ -1, 0, 1, 2, 1, -2 ], [ -1, 1, 1, 1, 1, -2 ], [ 1, -1, 0, 1, 1, -2 ] ], 1.125 ] + ], + [ + [ [ [ -1, 0, 1, 1, 1, -2 ], [ -1, 0, 1, 2, 1, -2 ], [ -2, 0, 1, 3, 1, -2 ], [ 1, -1, 0, 1, 1, -2 ] ], 1.125 ] + ], + [ + [ [ [ -1, 0, 1, 1, 1, -2 ], [ -1, 0, 1, 2, 1, -2 ], [ -2, 0, 1, 3, 1, -2 ], [ -1, 0, 1, 3, 0, -2 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 1, 1, 1, -2 ], [ -1, 0, 1, 2, 1, -2 ], [ -2, 0, 1, 3, 1, -2 ], [ 0, -1, 1, 2, 1, -2 ] ], 0.25 ] + ], + [ + [ [ [ -1, 0, 1, 2, 1, -3 ], [ -1, 0, 1, 2, 1, -2 ], [ -2, 0, 1, 3, 1, -2 ], [ 0, -1, 1, 2, 1, -2 ] ], 0.25 ] + ], + [ + [ [ [ -3, 1, 1, 3, 1, -2 ], [ -1, 0, 1, 2, 1, -2 ], [ -2, 0, 1, 3, 1, -2 ], [ 0, -1, 1, 2, 1, -2 ] ], 0 ] + ], + [ + [ [ [ -2, 0, 1, 2, 2, -2 ], [ -1, 0, 1, 2, 1, -2 ], [ -2, 0, 1, 3, 1, -2 ], [ 0, -1, 1, 2, 1, -2 ] ], 1 ] + ], + [ + [ [ [ -2, 0, 1, 2, 2, -2 ], [ -1, 0, 1, 2, 1, -2 ], [ -2, 0, 1, 3, 1, -2 ], [ -1, 0, 0, 3, 1, -2 ] ], 0.75 ] + ], + [ + [ [ [ -2, 0, 1, 2, 2, -2 ], [ -1, 0, 1, 2, 1, -2 ], [ -1, -1, 1, 2, 2, -2 ], [ -1, 0, 0, 3, 1, -2 ] ], 0.625 ] + ], + [ + [ [ [ -1, 0, 1, 2, 0, -2 ], [ -1, 0, 1, 2, 1, -2 ], [ -1, -1, 1, 2, 2, -2 ], [ -1, 0, 0, 3, 1, -2 ] ], 0.125 ] + ], + [ + [ [ [ -1, 0, 1, 2, 0, -2 ], [ -1, 0, 1, 2, 1, -2 ], [ -1, -1, 0, 3, 1, -2 ], [ -1, 0, 0, 3, 1, -2 ] ], 0.75 ] + ], + [ + [ [ [ -1, 0, 1, 2, 0, -2 ], [ 0, 0, 1, 2, -1, -2 ], [ -1, -1, 0, 3, 1, -2 ], [ -1, 0, 0, 3, 1, -2 ] ], 0.25 ] + ], + [ + [ [ [ -1, 0, 1, 2, 0, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ -1, -1, 0, 3, 1, -2 ], [ -1, 0, 0, 3, 1, -2 ] ], 1.25 ] + ], + [ + [ [ [ -1, 0, 1, 2, 0, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ -1, -1, 0, 3, 1, -2 ], [ 0, -1, 0, 2, 2, -2 ] ], 0.625 ], + [ [ [ "Rest" ], [ 0, -1, 0, 2, 1, -2 ], [ -1, -1, 0, 3, 1, -2 ], [ 0, -1, 0, 2, 2, -2 ] ], 0.625 ], + [ [ [ "Rest" ], [ 0, -1, 0, 2, 1, -2 ], [ -1, -1, 0, 3, 1, -2 ], [ "Rest" ] ], 0.375 ], + [ [ [ "Rest" ], [ "Rest" ], [ -1, -1, 0, 3, 1, -2 ], [ "Rest" ] ], 0.875 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1.375 ] + ] + ] +], +"last_changes": +[ + [ [ -1, 0, 1, 2, 0, -2 ], [ -1, 0, 1, 2, 1, -2 ], [ -1, -1, 1, 2, 2, -2 ], [ -1, 0, 0, 3, 1, -2 ] ], + [ [ -1, 0, 1, 2, 0, -2 ], [ -1, 0, 1, 2, 1, -2 ], [ -1, -1, 0, 3, 1, -2 ], [ -1, 0, 0, 3, 1, -2 ] ], + [ [ -1, 0, 1, 2, 0, -2 ], [ 0, 0, 1, 2, -1, -2 ], [ -1, -1, 0, 3, 1, -2 ], [ -1, 0, 0, 3, 1, -2 ] ], + [ [ -1, 0, 1, 2, 0, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ -1, -1, 0, 3, 1, -2 ], [ -1, 0, 0, 3, 1, -2 ] ], + [ [ -1, 0, 1, 2, 0, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ -1, -1, 0, 3, 1, -2 ], [ 0, -1, 0, 2, 2, -2 ] ] +], +"cur_uid": "5ec14635", +"ref_uid": "7c2de94c", +"order_seed": 733231, +"dur_seed": 711733, +"motifs_seed": 934821, +"entrances_probs_vals": [ 0, 0, 0, 0, 1.2087912087912, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 0, 1.2087912087912, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 0, 1.2087912087912, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2170, 338 ], [ -2373.9938080495, 1453 ], [ -1650, 1676 ], [ -1370.8978328173, 1694 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 1, 0, 0.2, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ] +], +"sus_weights": [ 0, 0, 0.61 ], +"order_size": [ 30, 30 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/69c568c6/69c568c6_code.scd b/resources/string_quartet_3/69c568c6/69c568c6_code.scd new file mode 100644 index 0000000..a40da01 --- /dev/null +++ b/resources/string_quartet_3/69c568c6/69c568c6_code.scd @@ -0,0 +1,973 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + + + //isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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/string_quartet_3/69c568c6/69c568c6_mus_model.json b/resources/string_quartet_3/69c568c6/69c568c6_mus_model.json new file mode 100644 index 0000000..a490727 --- /dev/null +++ b/resources/string_quartet_3/69c568c6/69c568c6_mus_model.json @@ -0,0 +1,535 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 1.625 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 1 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1.125 ], + [ [ [ 1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 0.625 ], + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 0.75 ] + ], + [ + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ] ], 1.25 ] + ], + [ + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -2, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -2, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ] ], 0.75 ] + ], + [ + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, -1, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ] ], 1.125 ], + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, -1, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 1.125 ] + ], + [ + [ [ [ 1, 0, -1, 0, 0, 0 ], [ -1, 1, -1, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 0.5 ], + [ [ [ 1, 0, -1, 0, 0, 0 ], [ -1, 1, -1, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ] ], 0.875 ], + [ [ [ 1, 0, -1, 0, 0, 0 ], [ -1, 1, -1, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1.5 ] + ], + [ + [ [ [ 1, 0, -1, 0, 0, 0 ], [ -1, 1, -1, 0, 0, 0 ], [ -1, 2, -1, 0, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 2, -1, 0, 0, 0 ], [ -1, 1, -1, 0, 0, 0 ], [ -1, 2, -1, 0, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 0.875 ] + ], + [ + [ [ [ 0, 2, -1, 0, 0, 0 ], [ 0, 1, -1, -1, 0, 0 ], [ -1, 2, -1, 0, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1.375 ], + [ [ [ -2, 2, -1, -1, 0, 0 ], [ 0, 1, -1, -1, 0, 0 ], [ -1, 2, -1, 0, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 0.875 ], + [ [ [ -2, 2, -1, -1, 0, 0 ], [ 0, 1, -1, -1, 0, 0 ], [ 0, 2, -1, -1, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 0.5 ] + ], + [ + [ [ [ -2, 2, -1, -1, 0, 0 ], [ 0, 1, -1, -1, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1.75 ], + [ [ [ -2, 1, -1, -1, 0, 1 ], [ 0, 1, -1, -1, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, -1, -1, 0, 1 ], [ 1, 1, -1, -2, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1.25 ] + ], + [ + [ [ [ -2, 1, -1, -1, 0, 1 ], [ 1, 1, -1, -2, 0, 0 ], [ 1, 0, -1, -1, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 0.625 ], + [ [ [ -2, 1, -1, -1, 0, 1 ], [ -1, 2, -1, -1, 0, 1 ], [ 1, 0, -1, -1, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 0.875 ] + ], + [ + [ [ [ -2, 1, -1, -1, 0, 1 ], [ 0, 1, -2, -1, 0, 1 ], [ 1, 0, -1, -1, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1.25 ] + ], + [ + [ [ [ -2, 1, -1, -1, 0, 1 ], [ 1, 1, -1, -1, -1, 0 ], [ 1, 0, -1, -1, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 0.75 ], + [ [ [ -2, 1, -1, -1, 0, 1 ], [ 1, 1, -1, -1, -1, 0 ], [ 0, 2, -1, -1, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 0.875 ] + ], + [ + [ [ [ -1, 2, -1, -2, 0, 0 ], [ 1, 1, -1, -1, -1, 0 ], [ 0, 2, -1, -1, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ -1, 2, -1, -2, 0, 0 ], [ -1, 2, -1, -1, 0, 0 ], [ 0, 2, -1, -1, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1.375 ] + ], + [ + [ [ [ -1, 2, -1, -2, 0, 0 ], [ -1, 2, -1, -1, 0, 0 ], [ 1, 2, -1, -2, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1.125 ], + [ [ [ -1, 2, -1, -2, 0, 0 ], [ -1, 2, -1, -1, 0, 0 ], [ 1, 2, -1, -2, 0, 0 ], [ 0, 2, -1, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 2, -1, -2, 0, 0 ], [ 0, 2, -1, -2, 0, 0 ], [ 1, 2, -1, -2, 0, 0 ], [ 0, 2, -1, -1, 0, 0 ] ], 1.625 ], + [ [ [ -1, 2, -1, -2, 0, 0 ], [ 0, 2, -1, -2, 0, 0 ], [ 0, 2, 0, -1, 0, 0 ], [ 0, 2, -1, -1, 0, 0 ] ], 0.75 ] + ], + [ + [ [ [ -1, 2, -1, -2, 0, 0 ], [ -1, 2, 0, -1, 0, 0 ], [ 0, 2, 0, -1, 0, 0 ], [ 0, 2, -1, -1, 0, 0 ] ], 1.375 ], + [ [ [ -1, 2, -1, -2, 0, 0 ], [ -1, 2, 0, -1, 0, 0 ], [ 1, 2, 0, -2, 0, 0 ], [ 0, 2, -1, -1, 0, 0 ] ], 1.75 ] + ], + [ + [ [ [ -1, 2, -1, -2, 0, 0 ], [ -1, 2, 0, -1, 0, 0 ], [ 0, 2, -1, -2, 1, 0 ], [ 0, 2, -1, -1, 0, 0 ] ], 1 ], + [ [ [ -1, 2, -1, -2, 0, 0 ], [ 1, 2, -1, -2, 0, -1 ], [ 0, 2, -1, -2, 1, 0 ], [ 0, 2, -1, -1, 0, 0 ] ], 1.125 ], + [ [ [ -1, 2, -1, -2, 0, 0 ], [ 1, 2, -1, -2, 0, -1 ], [ 0, 2, -1, -2, 1, 0 ], [ 1, 2, -1, -2, 0, 0 ] ], 1.125 ] + ], + [ + [ [ [ -1, 2, -1, -2, 0, 0 ], [ 1, 2, -1, -2, 0, -1 ], [ 0, 3, -1, -2, 0, 0 ], [ 1, 2, -1, -2, 0, 0 ] ], 1.375 ], + [ [ [ -1, 2, -1, -2, 0, 0 ], [ 1, 2, -1, -2, 0, -1 ], [ 0, 3, -1, -2, 0, 0 ], [ 1, 2, -1, -1, 0, -1 ] ], 1.125 ] + ], + [ + [ [ [ -1, 2, -1, -2, 0, 0 ], [ 1, 2, -1, -2, 0, -1 ], [ 0, 3, -1, -2, 0, 0 ], [ 1, 3, -2, -2, 0, 0 ] ], 0.75 ], + [ [ [ -1, 2, -1, -2, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ], [ 0, 3, -1, -2, 0, 0 ], [ 1, 3, -2, -2, 0, 0 ] ], 1.5 ], + [ [ [ -1, 3, -1, -2, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ], [ 0, 3, -1, -2, 0, 0 ], [ 1, 3, -2, -2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 2, -2, -2, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ], [ 0, 3, -1, -2, 0, 0 ], [ 1, 3, -2, -2, 0, 0 ] ], 1.75 ] + ], + [ + [ [ [ 0, 2, -2, -2, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ], [ 1, 2, -2, -2, 0, 0 ], [ 1, 3, -2, -2, 0, 0 ] ], 1.375 ] + ], + [ + [ [ [ 0, 2, -2, -2, 0, 0 ], [ -1, 3, -1, -1, 0, 0 ], [ 0, 3, -2, -2, 0, 0 ], [ 1, 3, -2, -2, 0, 0 ] ], 1.625 ], + [ [ [ 0, 2, -2, -2, 0, 0 ], [ 0, 2, -2, -1, 0, 0 ], [ 0, 3, -2, -2, 0, 0 ], [ 1, 3, -2, -2, 0, 0 ] ], 1.75 ], + [ [ [ 0, 2, -2, -2, 0, 0 ], [ 0, 2, -2, -1, 0, 0 ], [ 0, 3, -2, -2, 0, 0 ], [ 1, 2, -2, -2, 0, 1 ] ], 1 ] + ], + [ + [ [ [ 0, 1, -2, -2, 0, 1 ], [ 0, 2, -2, -1, 0, 0 ], [ 0, 3, -2, -2, 0, 0 ], [ 1, 2, -2, -2, 0, 1 ] ], 1.875 ], + [ [ [ 0, 1, -2, -2, 0, 1 ], [ 0, 2, -1, -2, 0, 1 ], [ 0, 3, -2, -2, 0, 0 ], [ 1, 2, -2, -2, 0, 1 ] ], 0.5 ], + [ [ [ 0, 1, -2, -2, 0, 1 ], [ 0, 2, -1, -2, 0, 1 ], [ 0, 2, -2, -2, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 0.875 ] + ], + [ + [ [ [ 0, 1, -2, -2, 0, 1 ], [ 0, 2, -1, -2, 0, 1 ], [ 0, 1, -2, -2, 0, 2 ], [ 1, 2, -2, -2, 0, 1 ] ], 1.375 ], + [ [ [ 0, 1, -2, -2, 0, 1 ], [ 0, 2, -1, -2, 0, 1 ], [ 0, 1, -2, -2, 0, 2 ], [ 1, 1, -2, -2, 0, 2 ] ], 0.625 ] + ], + [ + [ [ [ 0, 1, -2, -2, 0, 1 ], [ 0, 2, -1, -2, 0, 1 ], [ 0, 1, -2, -2, 0, 2 ], [ 1, 2, -1, -2, 0, 1 ] ], 0.625 ], + [ [ [ -1, 1, -1, -2, 0, 1 ], [ 0, 2, -1, -2, 0, 1 ], [ 0, 1, -2, -2, 0, 2 ], [ 1, 2, -1, -2, 0, 1 ] ], 0.5 ] + ], + [ + [ [ [ -1, 1, -1, -2, 0, 1 ], [ 1, 1, -2, -2, 0, 1 ], [ 0, 1, -2, -2, 0, 2 ], [ 1, 2, -1, -2, 0, 1 ] ], 0.75 ] + ], + [ + [ [ [ -1, 1, -1, -2, 0, 1 ], [ 1, 1, -2, -2, 0, 1 ], [ 0, 1, -1, -2, 1, 1 ], [ 1, 2, -1, -2, 0, 1 ] ], 0.875 ] + ], + [ + [ [ [ -1, 1, -1, -2, 0, 1 ], [ 1, 1, -2, -2, 0, 1 ], [ 0, 2, -1, -2, 0, 1 ], [ 1, 2, -1, -2, 0, 1 ] ], 1.5 ], + [ [ [ -2, 3, -1, -2, 0, 1 ], [ 1, 1, -2, -2, 0, 1 ], [ 0, 2, -1, -2, 0, 1 ], [ 1, 2, -1, -2, 0, 1 ] ], 1.125 ] + ], + [ + [ [ [ -1, 2, -2, -2, 0, 1 ], [ 1, 1, -2, -2, 0, 1 ], [ 0, 2, -1, -2, 0, 1 ], [ 1, 2, -1, -2, 0, 1 ] ], 0.625 ], + [ [ [ -1, 2, -2, -2, 0, 1 ], [ 1, 1, -2, -2, 0, 1 ], [ 0, 2, -1, -2, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 1.75 ], + [ [ [ -1, 2, -2, -2, 0, 1 ], [ 1, 1, -2, -2, 0, 1 ], [ 0, 2, -2, -2, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 0.625 ] + ], + [ + [ [ [ -1, 2, -2, -2, 0, 1 ], [ 1, 1, -2, -2, 0, 1 ], [ 1, 1, -3, -2, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 0.625 ], + [ [ [ 0, 1, -3, -2, 0, 1 ], [ 1, 1, -2, -2, 0, 1 ], [ 1, 1, -3, -2, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 1.25 ] + ], + [ + [ [ [ 0, 1, -3, -2, 0, 1 ], [ 1, 0, -3, -2, 0, 1 ], [ 1, 1, -3, -2, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 1.375 ], + [ [ [ -1, 2, -3, -2, 0, 1 ], [ 1, 0, -3, -2, 0, 1 ], [ 1, 1, -3, -2, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 0.5 ] + ], + [ + [ [ [ -1, 2, -3, -2, 0, 1 ], [ 0, 2, -3, -2, 0, 1 ], [ 1, 1, -3, -2, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 1 ], + [ [ [ 0, 1, -4, -2, 0, 1 ], [ 0, 2, -3, -2, 0, 1 ], [ 1, 1, -3, -2, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 1.5 ] + ], + [ + [ [ [ 0, 1, -4, -2, 0, 1 ], [ 1, 1, -4, -2, 0, 1 ], [ 1, 1, -3, -2, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 1.75 ], + [ [ [ -2, 3, -2, -2, 0, 1 ], [ 1, 1, -4, -2, 0, 1 ], [ 1, 1, -3, -2, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 1.625 ] + ], + [ + [ [ [ -2, 2, -2, -2, 0, 2 ], [ 1, 1, -4, -2, 0, 1 ], [ 1, 1, -3, -2, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 1.25 ], + [ [ [ -2, 2, -2, -2, 0, 2 ], [ 1, 1, -4, -2, 0, 1 ], [ 1, 2, -2, -3, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 1.25 ], + [ [ [ -2, 2, -2, -2, 0, 2 ], [ -1, 2, -2, -2, 0, 2 ], [ 1, 2, -2, -3, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 0.5 ] + ], + [ + [ [ [ -2, 2, -2, -2, 0, 2 ], [ 0, 3, -2, -3, 0, 1 ], [ 1, 2, -2, -3, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 0.625 ], + [ [ [ -1, 3, -2, -3, 0, 1 ], [ 0, 3, -2, -3, 0, 1 ], [ 1, 2, -2, -3, 0, 1 ], [ 1, 2, -2, -2, 0, 1 ] ], 0.625 ] + ], + [ + [ [ [ -1, 3, -2, -3, 0, 1 ], [ 0, 3, -2, -3, 0, 1 ], [ 1, 2, -2, -3, 0, 1 ], [ 2, 2, -2, -3, 0, 1 ] ], 0.75 ] + ], + [ + [ [ [ -1, 3, -2, -3, 0, 1 ], [ 0, 3, -2, -3, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 2, 2, -2, -3, 0, 1 ] ], 0.75 ], + [ [ [ 0, 3, -2, -4, 0, 1 ], [ 0, 3, -2, -3, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 2, 2, -2, -3, 0, 1 ] ], 0.875 ] + ], + [ + [ [ [ 0, 3, -2, -4, 0, 1 ], [ 0, 3, -2, -3, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 1, 4, -2, -3, 0, 1 ] ], 0.875 ] + ], + [ + [ [ [ -1, 3, -1, -3, 0, 1 ], [ 0, 3, -2, -3, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 1, 4, -2, -3, 0, 1 ] ], 1.25 ] + ], + [ + [ [ [ 0, 2, -2, -3, 0, 1 ], [ 0, 3, -2, -3, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 1, 4, -2, -3, 0, 1 ] ], 1.25 ] + ], + [ + [ [ [ 0, 2, -2, -3, 0, 1 ], [ 0, 3, -2, -3, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 1, 3, -1, -3, 0, 1 ] ], 1.125 ], + [ [ [ 0, 3, -2, -3, -1, 1 ], [ 0, 3, -2, -3, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 1, 3, -1, -3, 0, 1 ] ], 1.25 ] + ], + [ + [ [ [ 0, 3, -2, -3, -1, 1 ], [ 0, 4, -2, -3, -1, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 1, 3, -1, -3, 0, 1 ] ], 0.875 ], + [ [ [ -1, 3, -2, -3, 0, 1 ], [ 0, 4, -2, -3, -1, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 1, 3, -1, -3, 0, 1 ] ], 1.625 ], + [ [ [ -1, 3, -2, -3, 0, 1 ], [ 0, 4, -2, -3, -1, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 0, 4, -2, -2, 0, 1 ] ], 0.375 ] + ], + [ + [ [ [ -1, 3, -2, -3, 0, 1 ], [ 0, 3, -1, -3, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 0, 4, -2, -2, 0, 1 ] ], 0.5 ], + [ [ [ -1, 3, -2, -3, 0, 1 ], [ 0, 3, -1, -3, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 1, 4, -2, -3, 0, 1 ] ], 0.5 ] + ], + [ + [ [ [ -1, 4, -2, -3, 0, 0 ], [ 0, 3, -1, -3, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 1, 4, -2, -3, 0, 1 ] ], 0.75 ], + [ [ [ -1, 4, -2, -3, 0, 0 ], [ -1, 4, -2, -2, 0, 1 ], [ 0, 4, -2, -3, 0, 1 ], [ 1, 4, -2, -3, 0, 1 ] ], 1.5 ] + ], + [ + [ [ [ -1, 4, -2, -3, 0, 0 ], [ -1, 4, -2, -2, 0, 1 ], [ 0, 4, -2, -2, 0, 0 ], [ 1, 4, -2, -3, 0, 1 ] ], 1.125 ], + [ [ [ -1, 4, -2, -3, 0, 0 ], [ -1, 4, -2, -2, 0, 1 ], [ 0, 4, -2, -2, 0, 0 ], [ 2, 3, -2, -3, 0, 0 ] ], 1.5 ] + ], + [ + [ [ [ -1, 3, -2, -2, 0, 0 ], [ -1, 4, -2, -2, 0, 1 ], [ 0, 4, -2, -2, 0, 0 ], [ 2, 3, -2, -3, 0, 0 ] ], 0.625 ], + [ [ [ -1, 3, -2, -2, 0, 0 ], [ 1, 3, -2, -3, 0, 0 ], [ 0, 4, -2, -2, 0, 0 ], [ 2, 3, -2, -3, 0, 0 ] ], 1.125 ], + [ [ [ -1, 3, -2, -2, 0, 0 ], [ 1, 3, -2, -3, 0, 0 ], [ 1, 4, -2, -3, 0, 0 ], [ 2, 3, -2, -3, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ -1, 3, -2, -2, 0, 0 ], [ 1, 3, -2, -3, 0, 0 ], [ 1, 4, -2, -3, 0, 0 ], [ 1, 5, -2, -3, 0, 0 ] ], 1.125 ] + ], + [ + [ [ [ -1, 3, -2, -2, 0, 0 ], [ 1, 3, -2, -3, 0, 0 ], [ 1, 4, -2, -3, 0, 0 ], [ 3, 3, -2, -3, 0, -1 ] ], 0.75 ], + [ [ [ -1, 3, -2, -2, 0, 0 ], [ 1, 3, -2, -3, 0, 0 ], [ 2, 3, -3, -3, 0, 0 ], [ 3, 3, -2, -3, 0, -1 ] ], 0.5 ], + [ [ [ 0, 3, -2, -3, 0, 0 ], [ 1, 3, -2, -3, 0, 0 ], [ 2, 3, -3, -3, 0, 0 ], [ 3, 3, -2, -3, 0, -1 ] ], 1.25 ] + ], + [ + [ [ [ 0, 3, -2, -3, 0, 0 ], [ 1, 3, -2, -3, 0, 0 ], [ 2, 2, -2, -3, 0, 0 ], [ 3, 3, -2, -3, 0, -1 ] ], 1.5 ] + ], + [ + [ [ [ 0, 3, -2, -3, 0, 0 ], [ 1, 3, -2, -3, 0, 0 ], [ 1, 4, -2, -3, 0, -1 ], [ 3, 3, -2, -3, 0, -1 ] ], 1 ], + [ [ [ 0, 3, -2, -2, 0, -1 ], [ 1, 3, -2, -3, 0, 0 ], [ 1, 4, -2, -3, 0, -1 ], [ 3, 3, -2, -3, 0, -1 ] ], 1.375 ], + [ [ [ 0, 3, -2, -2, 0, -1 ], [ 1, 3, -2, -2, 0, -1 ], [ 1, 4, -2, -3, 0, -1 ], [ 3, 3, -2, -3, 0, -1 ] ], 0.875 ] + ], + [ + [ [ [ 0, 3, -2, -2, 0, -1 ], [ 2, 3, -2, -3, 0, -1 ], [ 1, 4, -2, -3, 0, -1 ], [ 3, 3, -2, -3, 0, -1 ] ], 0.75 ], + [ [ [ 1, 3, -2, -3, 0, -1 ], [ 2, 3, -2, -3, 0, -1 ], [ 1, 4, -2, -3, 0, -1 ], [ 3, 3, -2, -3, 0, -1 ] ], 0.375 ] + ], + [ + [ [ [ 1, 3, -2, -3, 0, -1 ], [ 2, 3, -2, -3, 0, -1 ], [ 2, 3, -3, -3, 0, -1 ], [ 3, 3, -2, -3, 0, -1 ] ], 1.75 ], + [ [ [ 1, 3, -2, -3, 0, -1 ], [ 2, 3, -2, -3, 0, -1 ], [ 2, 3, -3, -3, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ] ], 1.25 ] + ], + [ + [ [ [ 1, 3, -2, -3, 0, -1 ], [ 2, 3, -2, -3, 0, -1 ], [ 3, 3, -3, -4, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ] ], 1.75 ], + [ [ [ 1, 3, -2, -3, 0, -1 ], [ 1, 3, -3, -3, 0, 0 ], [ 3, 3, -3, -4, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ] ], 0.5 ], + [ [ [ 2, 2, -3, -3, 0, -1 ], [ 1, 3, -3, -3, 0, 0 ], [ 3, 3, -3, -4, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ] ], 1.625 ] + ], + [ + [ [ [ 2, 2, -3, -3, 0, -1 ], [ 3, 1, -3, -3, 0, -1 ], [ 3, 3, -3, -4, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ] ], 0.875 ] + ], + [ + [ [ [ 2, 2, -3, -3, 0, -1 ], [ 3, 1, -3, -3, 0, -1 ], [ 3, 3, -3, -4, 0, -1 ], [ 4, 2, -4, -3, 0, -1 ] ], 1.125 ] + ], + [ + [ [ [ 2, 2, -3, -3, 0, -1 ], [ 3, 1, -3, -3, 0, -1 ], [ 3, 3, -3, -4, 0, -1 ], [ 4, 3, -3, -4, 0, -1 ] ], 1.625 ], + [ [ [ 3, 2, -3, -4, 0, -1 ], [ 3, 1, -3, -3, 0, -1 ], [ 3, 3, -3, -4, 0, -1 ], [ 4, 3, -3, -4, 0, -1 ] ], 1.375 ] + ], + [ + [ [ [ 2, 4, -3, -4, 0, -1 ], [ 3, 1, -3, -3, 0, -1 ], [ 3, 3, -3, -4, 0, -1 ], [ 4, 3, -3, -4, 0, -1 ] ], 0.75 ] + ], + [ + [ [ [ 2, 4, -3, -4, 0, -1 ], [ 1, 5, -3, -4, 0, -1 ], [ 3, 3, -3, -4, 0, -1 ], [ 4, 3, -3, -4, 0, -1 ] ], 1.5 ], + [ [ [ 2, 4, -3, -4, 0, -1 ], [ 1, 5, -3, -4, 0, -1 ], [ 3, 3, -3, -4, 0, -1 ], [ 4, 4, -3, -4, -1, -1 ] ], 0.875 ], + [ [ [ 2, 4, -3, -4, 0, -1 ], [ 1, 5, -3, -4, 0, -1 ], [ 3, 4, -3, -4, -1, -1 ], [ 4, 4, -3, -4, -1, -1 ] ], 1.625 ] + ], + [ + [ [ [ 2, 4, -3, -4, 0, -1 ], [ 1, 5, -3, -4, 0, -1 ], [ 3, 4, -4, -4, 0, -1 ], [ 4, 4, -3, -4, -1, -1 ] ], 0.5 ], + [ [ [ 2, 4, -3, -4, 0, -1 ], [ 2, 4, -4, -4, 0, -1 ], [ 3, 4, -4, -4, 0, -1 ], [ 4, 4, -3, -4, -1, -1 ] ], 1.375 ] + ], + [ + [ [ [ 2, 4, -3, -4, 0, -1 ], [ 2, 4, -4, -4, 0, -1 ], [ 3, 4, -4, -4, 0, -1 ], [ 3, 4, -2, -4, 0, -1 ] ], 1.75 ], + [ [ [ 2, 4, -3, -4, 0, -1 ], [ 1, 4, -3, -3, 0, -1 ], [ 3, 4, -4, -4, 0, -1 ], [ 3, 4, -2, -4, 0, -1 ] ], 0.625 ], + [ [ [ 2, 4, -3, -4, 0, -1 ], [ 1, 4, -3, -3, 0, -1 ], [ 2, 4, -3, -3, 0, -1 ], [ 3, 4, -2, -4, 0, -1 ] ], 1.75 ] + ], + [ + [ [ [ 2, 4, -3, -4, 0, -1 ], [ 1, 4, -3, -3, 0, -1 ], [ 2, 4, -3, -3, 0, -1 ], [ 2, 5, -3, -3, 0, -1 ] ], 0.75 ], + [ [ [ 2, 4, -3, -4, 0, -1 ], [ 1, 4, -3, -3, 0, -1 ], [ 3, 4, -3, -4, 0, -1 ], [ 2, 5, -3, -3, 0, -1 ] ], 0.5 ] + ], + [ + [ [ [ 2, 3, -3, -3, 0, -1 ], [ 1, 4, -3, -3, 0, -1 ], [ 3, 4, -3, -4, 0, -1 ], [ 2, 5, -3, -3, 0, -1 ] ], 0.75 ], + [ [ [ 2, 3, -3, -3, 0, -1 ], [ 1, 4, -3, -3, 0, -1 ], [ 2, 4, -2, -3, 0, -1 ], [ 2, 5, -3, -3, 0, -1 ] ], 1.75 ] + ], + [ + [ [ [ 2, 3, -3, -3, 0, -1 ], [ 1, 4, -3, -3, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 2, 5, -3, -3, 0, -1 ] ], 1 ], + [ [ [ 2, 3, -3, -3, 0, -1 ], [ 2, 3, -4, -3, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 2, 5, -3, -3, 0, -1 ] ], 1.125 ] + ], + [ + [ [ [ 2, 3, -3, -3, 0, -1 ], [ 2, 3, -4, -3, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 3, 3, -2, -3, 0, -1 ] ], 1.5 ], + [ [ [ 2, 3, -3, -3, 0, -1 ], [ 2, 2, -3, -3, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 3, 3, -2, -3, 0, -1 ] ], 1.625 ], + [ [ [ 3, 3, -3, -4, 0, -1 ], [ 2, 2, -3, -3, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 3, 3, -2, -3, 0, -1 ] ], 0.5 ] + ], + [ + [ [ [ 3, 3, -3, -4, 0, -1 ], [ 3, 2, -3, -4, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 3, 3, -2, -3, 0, -1 ] ], 0.75 ] + ], + [ + [ [ [ 2, 3, -2, -3, 0, -1 ], [ 3, 2, -3, -4, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 3, 3, -2, -3, 0, -1 ] ], 0.875 ], + [ [ [ 2, 3, -2, -3, 0, -1 ], [ 3, 2, -3, -4, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 2, 3, -3, -3, 0, -1 ] ], 1.125 ] + ], + [ + [ [ [ 2, 3, -2, -3, 0, -1 ], [ 2, 3, -4, -3, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 2, 3, -3, -3, 0, -1 ] ], 0.875 ], + [ [ [ 3, 2, -3, -3, 0, -1 ], [ 2, 3, -4, -3, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 2, 3, -3, -3, 0, -1 ] ], 0.875 ] + ], + [ + [ [ [ 3, 2, -3, -3, 0, -1 ], [ 2, 3, -4, -3, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 3, 3, -3, -4, 0, -1 ] ], 1.25 ], + [ [ [ 3, 2, -3, -3, 0, -1 ], [ 1, 3, -3, -2, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 3, 3, -3, -4, 0, -1 ] ], 1.25 ], + [ [ [ 2, 4, -3, -3, 0, -1 ], [ 1, 3, -3, -2, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 3, 3, -3, -4, 0, -1 ] ], 1.125 ] + ], + [ + [ [ [ 2, 4, -3, -3, 0, -1 ], [ 1, 3, -3, -2, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 1, 4, -3, -2, 0, -1 ] ], 0.625 ] + ], + [ + [ [ [ 2, 3, -3, -2, 0, -1 ], [ 1, 3, -3, -2, 0, -1 ], [ 3, 3, -3, -3, 0, -1 ], [ 1, 4, -3, -2, 0, -1 ] ], 1.25 ], + [ [ [ 2, 3, -3, -2, 0, -1 ], [ 1, 3, -3, -2, 0, -1 ], [ 2, 3, -2, -2, 0, -1 ], [ 1, 4, -3, -2, 0, -1 ] ], 1.375 ] + ], + [ + [ [ [ 1, 3, -2, -2, 0, -1 ], [ 1, 3, -3, -2, 0, -1 ], [ 2, 3, -2, -2, 0, -1 ], [ 1, 4, -3, -2, 0, -1 ] ], 0.875 ] + ], + [ + [ [ [ 1, 3, -2, -2, 0, -1 ], [ 1, 3, -3, -2, 0, -1 ], [ 2, 3, -2, -2, 0, -1 ], [ 2, 2, -2, -2, 0, -1 ] ], 1.5 ] + ], + [ + [ [ [ 1, 3, -2, -2, 0, -1 ], [ 2, 1, -2, -2, 0, -1 ], [ 2, 3, -2, -2, 0, -1 ], [ 2, 2, -2, -2, 0, -1 ] ], 0.5 ] + ], + [ + [ [ [ 1, 3, -2, -2, 0, -1 ], [ 2, 1, -2, -2, 0, -1 ], [ 3, 1, -2, -2, 0, -1 ], [ 2, 2, -2, -2, 0, -1 ] ], 1.625 ], + [ [ [ 2, 0, -2, -2, 0, -1 ], [ 2, 1, -2, -2, 0, -1 ], [ 3, 1, -2, -2, 0, -1 ], [ 2, 2, -2, -2, 0, -1 ] ], 1.5 ], + [ [ [ 2, 0, -2, -2, 0, -1 ], [ 2, 1, -2, -2, 0, -1 ], [ 3, 1, -2, -2, 0, -1 ], [ 3, 1, -3, -2, 0, -1 ] ], 0.75 ] + ], + [ + [ [ [ 2, 0, -2, -2, 0, -1 ], [ 3, 0, -3, -2, 0, -1 ], [ 3, 1, -2, -2, 0, -1 ], [ 3, 1, -3, -2, 0, -1 ] ], 1.5 ] + ], + [ + [ [ [ 2, 0, -2, -2, 0, -1 ], [ 3, 0, -3, -2, 0, -1 ], [ 4, 0, -3, -2, 0, -1 ], [ 3, 1, -3, -2, 0, -1 ] ], 1 ], + [ [ [ 2, 0, -3, -2, 0, -1 ], [ 3, 0, -3, -2, 0, -1 ], [ 4, 0, -3, -2, 0, -1 ], [ 3, 1, -3, -2, 0, -1 ] ], 1.25 ], + [ [ [ 2, 0, -3, -2, 0, -1 ], [ 3, 0, -3, -2, 0, -1 ], [ 4, 0, -3, -2, 0, -1 ], [ 4, 0, -4, -2, 0, -1 ] ], 1.25 ] + ], + [ + [ [ [ 2, 0, -3, -2, 0, -1 ], [ 2, 0, -4, -2, 0, 0 ], [ 4, 0, -3, -2, 0, -1 ], [ 4, 0, -4, -2, 0, -1 ] ], 0.5 ], + [ [ [ 3, -1, -4, -2, 0, -1 ], [ 2, 0, -4, -2, 0, 0 ], [ 4, 0, -3, -2, 0, -1 ], [ 4, 0, -4, -2, 0, -1 ] ], 1.125 ], + [ [ [ 3, -1, -4, -2, 0, -1 ], [ 2, 0, -4, -2, 0, 0 ], [ 5, -1, -4, -2, 0, -1 ], [ 4, 0, -4, -2, 0, -1 ] ], 0.5 ] + ], + [ + [ [ [ 3, -1, -4, -2, 0, -1 ], [ 2, 0, -4, -2, 0, 0 ], [ 5, -1, -4, -2, 0, -1 ], [ 4, -1, -4, -2, 0, 0 ] ], 1.75 ] + ], + [ + [ [ [ 2, 0, -4, -2, 0, -1 ], [ 2, 0, -4, -2, 0, 0 ], [ 5, -1, -4, -2, 0, -1 ], [ 4, -1, -4, -2, 0, 0 ] ], 0.5 ], + [ [ [ 2, 0, -4, -2, 0, -1 ], [ 2, 0, -4, -2, 0, 0 ], [ 5, -1, -4, -2, 0, -1 ], [ 3, 1, -4, -2, 0, 0 ] ], 1.125 ], + [ [ [ 2, 0, -4, -2, 0, -1 ], [ 2, 0, -4, -2, 0, 0 ], [ 3, 0, -4, -1, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ] ], 0.5 ] + ], + [ + [ [ [ 2, -1, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 3, 0, -4, -1, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ] ], 1.625 ], + [ [ [ 2, -1, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 4, 1, -4, -2, 0, -1 ], [ 3, 1, -4, -2, 0, 0 ] ], 0.75 ] + ], + [ + [ [ [ 2, -1, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 4, 0, -5, -2, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ] ], 1.75 ], + [ [ [ 2, -1, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 4, 0, -5, -2, 0, 0 ], [ 4, -1, -4, -2, 0, -1 ] ], 0.5 ] + ], + [ + [ [ [ 2, -1, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 4, 0, -5, -2, 0, 0 ], [ 2, 0, -4, -1, 0, 0 ] ], 1.75 ] + ], + [ + [ [ [ 1, 1, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 4, 0, -5, -2, 0, 0 ], [ 2, 0, -4, -1, 0, 0 ] ], 0.875 ], + [ [ [ 1, 1, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 3, 0, -4, -1, 0, 0 ], [ 2, 0, -4, -1, 0, 0 ] ], 0.875 ] + ], + [ + [ [ [ 1, 1, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 4, 1, -4, -2, 0, -1 ], [ 2, 0, -4, -1, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ 1, 1, -4, -2, 0, 0 ], [ 1, 2, -4, -2, 0, 0 ], [ 4, 1, -4, -2, 0, -1 ], [ 2, 0, -4, -1, 0, 0 ] ], 0.5 ], + [ [ [ 1, 1, -4, -2, 0, 0 ], [ 1, 2, -4, -2, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ], [ 2, 0, -4, -1, 0, 0 ] ], 0.875 ], + [ [ [ 1, 1, -4, -2, 0, 0 ], [ 1, 2, -4, -2, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ], [ 2, 1, -4, -2, 0, 0 ] ], 1.125 ] + ], + [ + [ [ [ 0, 2, -4, -2, 0, 0 ], [ 1, 2, -4, -2, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ], [ 2, 1, -4, -2, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ 0, 2, -4, -2, 0, 0 ], [ 1, 2, -4, -2, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ], [ 1, 3, -4, -2, 0, 0 ] ], 0.75 ] + ], + [ + [ [ [ 1, 2, -4, -3, 0, 0 ], [ 1, 2, -4, -2, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ], [ 1, 3, -4, -2, 0, 0 ] ], 0.875 ] + ], + [ + [ [ [ 1, 2, -4, -3, 0, 0 ], [ 1, 2, -4, -2, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ], [ 3, 1, -4, -2, 0, -1 ] ], 1.5 ], + [ [ [ 1, 2, -4, -3, 0, 0 ], [ 2, 2, -4, -3, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ], [ 3, 1, -4, -2, 0, -1 ] ], 1.5 ] + ], + [ + [ [ [ 1, 2, -4, -3, 0, 0 ], [ 2, 2, -4, -3, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ], [ 3, 0, -4, -2, 0, 0 ] ], 0.5 ], + [ [ [ 1, 1, -4, -2, 0, 0 ], [ 2, 2, -4, -3, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ], [ 3, 0, -4, -2, 0, 0 ] ], 0.5 ], + [ [ [ 1, 1, -4, -2, 0, 0 ], [ 2, 1, -4, -2, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ], [ 3, 0, -4, -2, 0, 0 ] ], 0.5 ] + ], + [ + [ [ [ 1, 1, -4, -2, 0, 0 ], [ 2, 1, -4, -2, 0, 0 ], [ 4, 0, -5, -2, 0, 0 ], [ 3, 0, -4, -2, 0, 0 ] ], 1.375 ], + [ [ [ 2, 0, -5, -2, 0, 0 ], [ 2, 1, -4, -2, 0, 0 ], [ 4, 0, -5, -2, 0, 0 ], [ 3, 0, -4, -2, 0, 0 ] ], 0.875 ], + [ [ [ 2, 0, -5, -2, 0, 0 ], [ 3, 0, -5, -2, 0, 0 ], [ 4, 0, -5, -2, 0, 0 ], [ 3, 0, -4, -2, 0, 0 ] ], 0.625 ] + ], + [ + [ [ [ 1, 0, -4, -2, 0, 0 ], [ 3, 0, -5, -2, 0, 0 ], [ 4, 0, -5, -2, 0, 0 ], [ 3, 0, -4, -2, 0, 0 ] ], 0.625 ], + [ [ [ 1, 0, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 4, 0, -5, -2, 0, 0 ], [ 3, 0, -4, -2, 0, 0 ] ], 1.375 ], + [ [ [ 1, 0, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 3, 1, -4, -2, 0, 0 ], [ 3, 0, -4, -2, 0, 0 ] ], 1.125 ] + ], + [ + [ [ [ 1, 0, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 2, 1, -4, -2, 0, 0 ], [ 3, 0, -4, -2, 0, 0 ] ], 1.625 ], + [ [ [ 1, 0, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 2, 1, -4, -2, 0, 0 ], [ 4, 0, -4, -3, 0, 0 ] ], 1.25 ] + ], + [ + [ [ [ 1, 0, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 3, 0, -5, -2, 0, 0 ], [ 4, 0, -4, -3, 0, 0 ] ], 0.75 ], + [ [ [ 1, 0, -4, -2, 0, 0 ], [ 2, 0, -4, -2, 0, 0 ], [ 3, 0, -5, -2, 0, 0 ], [ 2, 1, -4, -2, 0, 0 ] ], 1.375 ] + ], + [ + [ [ [ 1, 0, -4, -2, 0, 0 ], [ 2, 0, -5, -2, 1, 0 ], [ 3, 0, -5, -2, 0, 0 ], [ 2, 1, -4, -2, 0, 0 ] ], 1.625 ], + [ [ [ 1, 0, -4, -2, 0, 0 ], [ 2, 0, -5, -2, 1, 0 ], [ 3, 0, -5, -2, 0, 0 ], [ 2, 0, -4, -1, 0, 0 ] ], 0.75 ] + ], + [ + [ [ [ 1, 0, -5, -2, 1, 0 ], [ 2, 0, -5, -2, 1, 0 ], [ 3, 0, -5, -2, 0, 0 ], [ 2, 0, -4, -1, 0, 0 ] ], 1.375 ], + [ [ [ 1, 0, -5, -2, 1, 0 ], [ 3, 0, -5, -2, -1, 0 ], [ 3, 0, -5, -2, 0, 0 ], [ 2, 0, -4, -1, 0, 0 ] ], 1.75 ], + [ [ [ 1, 0, -5, -2, 1, 0 ], [ 3, 0, -5, -2, -1, 0 ], [ 3, 0, -5, -2, 0, 0 ], [ 4, 0, -5, -2, 0, -1 ] ], 1.25 ] + ], + [ + [ [ [ 1, 0, -5, -2, 1, 0 ], [ 3, 0, -5, -2, -1, 0 ], [ 3, 0, -6, -2, 1, 0 ], [ 4, 0, -5, -2, 0, -1 ] ], 1.375 ], + [ [ [ 1, 0, -5, -2, 1, 0 ], [ 3, 0, -5, -2, -1, 0 ], [ 3, 0, -6, -2, 1, 0 ], [ "Rest" ] ], 0.625 ], + [ [ [ 1, 0, -5, -2, 1, 0 ], [ 3, 0, -5, -2, -1, 0 ], [ "Rest" ], [ "Rest" ] ], 0.875 ], + [ [ [ 1, 0, -5, -2, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1.375 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 6.75 ] + ] + ] +], +"last_changes": +[ + [ [ 1, 0, -4, -2, 0, 0 ], [ 2, 0, -5, -2, 1, 0 ], [ 3, 0, -5, -2, 0, 0 ], [ 2, 0, -4, -1, 0, 0 ] ], + [ [ 1, 0, -5, -2, 1, 0 ], [ 2, 0, -5, -2, 1, 0 ], [ 3, 0, -5, -2, 0, 0 ], [ 2, 0, -4, -1, 0, 0 ] ], + [ [ 1, 0, -5, -2, 1, 0 ], [ 3, 0, -5, -2, -1, 0 ], [ 3, 0, -5, -2, 0, 0 ], [ 2, 0, -4, -1, 0, 0 ] ], + [ [ 1, 0, -5, -2, 1, 0 ], [ 3, 0, -5, -2, -1, 0 ], [ 3, 0, -5, -2, 0, 0 ], [ 4, 0, -5, -2, 0, -1 ] ], + [ [ 1, 0, -5, -2, 1, 0 ], [ 3, 0, -5, -2, -1, 0 ], [ 3, 0, -6, -2, 1, 0 ], [ 4, 0, -5, -2, 0, -1 ] ] +], +"cur_uid": "69c568c6", +"ref_uid": "nil", +"order_seed": 712406, +"dur_seed": 506999, +"motifs_seed": 551883, +"entrances_probs_vals": [ 0, 0, 0, 0.41, 1.84, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 0.41, 1.84, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 3.2936507936508, 0.41, 1.84, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ -1200, 1200, 0.0020576131687243, 0.068181818181818, 0.074074074074074, 0.0625, 0.20576131687243, 0.0625, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61316872427983, 0, 0.98971193415638, 0 ], +"passages_weights": [ 0.48, 0.46, 0.48, 1, 1 ], +"hd_exp": 9, +"hd_invert": 0, +"order": +[ + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 0, 3 ], [ 1, 2 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 0, 2 ], [ 1, 3 ], [ ] ], + [ [ 1 ], [ 0, 2, 3 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 0, 3 ], [ 2, 1 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 3, 0 ], [ 1, 2 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 3, 0 ], [ 1, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 2 ], [ 3, 1, 0 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 3 ], [ 0, 2, 1 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 2, 0 ], [ 1, 3 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 1 ], [ 3, 2, 0 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 3 ], [ 2, 0, 1 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ], + [ [ 2 ], [ 3, 1, 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 1, 2 ], [ 0, 3 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 2 ], [ 3, 1, 0 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 0 ], [ 1, 2, 3 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 2, 0 ], [ 3, 1 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 3 ], [ 2, 0, 1 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 0, 2 ], [ 1, 3 ], [ ] ], + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 100, 100 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/6a9928d6/6a9928d6_code.scd b/resources/string_quartet_3/6a9928d6/6a9928d6_code.scd new file mode 100644 index 0000000..49436ca --- /dev/null +++ b/resources/string_quartet_3/6a9928d6/6a9928d6_code.scd @@ -0,0 +1,973 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + + + //isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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/string_quartet_3/6a9928d6/6a9928d6_mus_model.json b/resources/string_quartet_3/6a9928d6/6a9928d6_mus_model.json new file mode 100644 index 0000000..7ef872c --- /dev/null +++ b/resources/string_quartet_3/6a9928d6/6a9928d6_mus_model.json @@ -0,0 +1,554 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ 2, -4, 0, 3, -1, 0 ], [ "Rest" ] ], 1.5 ], + [ [ [ "Rest" ], [ "Rest" ], [ 2, -4, 0, 3, -1, 0 ], [ 4, -5, -1, 2, -1, -1 ] ], 0.5 ], + [ [ [ "Rest" ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, 0 ], [ 4, -5, -1, 2, -1, -1 ] ], 1.75 ], + [ [ [ 1, -5, 0, 3, -1, -1 ], [ 2, -5, 0, 3, -1, -1 ], [ 2, -4, 0, 3, -1, 0 ], [ 4, -5, -1, 2, -1, -1 ] ], 0.875 ] + ], + [ + [ [ [ 1, -5, 0, 3, -1, -1 ], [ 4, -5, -1, 2, -1, -2 ], [ 2, -4, 0, 3, -1, 0 ], [ 4, -5, -1, 2, -1, -1 ] ], 1.125 ], + [ [ [ 1, -5, 0, 3, -1, -1 ], [ 4, -5, -1, 2, -1, -2 ], [ 4, -5, -2, 2, -1, -1 ], [ 4, -5, -1, 2, -1, -1 ] ], 1.625 ], + [ [ [ 2, -5, 0, 2, -1, -1 ], [ 4, -5, -1, 2, -1, -2 ], [ 4, -5, -2, 2, -1, -1 ], [ 4, -5, -1, 2, -1, -1 ] ], 1.875 ] + ], + [ + [ [ [ 2, -5, 0, 2, -1, -1 ], [ 4, -5, -1, 2, -1, -2 ], [ 3, -5, 0, 2, 0, -1 ], [ 4, -5, -1, 2, -1, -1 ] ], 1.125 ], + [ [ [ 2, -5, 0, 2, -1, -1 ], [ 4, -5, -1, 2, -1, -2 ], [ 3, -5, 0, 2, 0, -1 ], [ 4, -6, 0, 2, -1, -1 ] ], 0.5 ], + [ [ [ 2, -5, 0, 2, -1, -1 ], [ 3, -6, 0, 2, -1, -1 ], [ 3, -5, 0, 2, 0, -1 ], [ 4, -6, 0, 2, -1, -1 ] ], 1.875 ] + ], + [ + [ [ [ 2, -5, 0, 2, -1, -1 ], [ 2, -5, 0, 2, 0, -2 ], [ 3, -5, 0, 2, 0, -1 ], [ 4, -6, 0, 2, -1, -1 ] ], 0.75 ], + [ [ [ 1, -5, 0, 2, 0, 0 ], [ 2, -5, 0, 2, 0, -2 ], [ 3, -5, 0, 2, 0, -1 ], [ 4, -6, 0, 2, -1, -1 ] ], 0.5 ], + [ [ [ 1, -5, 0, 2, 0, 0 ], [ 2, -5, 0, 2, 0, -2 ], [ 3, -5, 0, 2, 0, -1 ], [ 3, -5, -1, 2, 0, -1 ] ], 0.75 ] + ], + [ + [ [ [ 1, -5, 0, 2, 0, 0 ], [ 2, -5, 0, 2, 0, -2 ], [ 3, -5, 0, 2, 1, -2 ], [ 3, -5, -1, 2, 0, -1 ] ], 1.625 ], + [ [ [ 3, -5, 0, 1, 0, -2 ], [ 2, -5, 0, 2, 0, -2 ], [ 3, -5, 0, 2, 1, -2 ], [ 3, -5, -1, 2, 0, -1 ] ], 1.625 ] + ], + [ + [ [ [ 3, -5, 0, 2, 0, -3 ], [ 2, -5, 0, 2, 0, -2 ], [ 3, -5, 0, 2, 1, -2 ], [ 3, -5, -1, 2, 0, -1 ] ], 1.875 ] + ], + [ + [ [ [ 3, -5, 0, 2, 0, -3 ], [ 2, -5, 0, 2, 0, -2 ], [ 4, -5, 1, 2, 0, -3 ], [ 3, -5, -1, 2, 0, -1 ] ], 0.625 ], + [ [ [ 3, -5, 0, 2, 0, -3 ], [ 2, -5, 0, 3, 0, -3 ], [ 4, -5, 1, 2, 0, -3 ], [ 3, -5, -1, 2, 0, -1 ] ], 1.125 ], + [ [ [ 3, -5, 0, 2, 0, -3 ], [ 2, -5, 0, 3, 0, -3 ], [ 4, -5, 1, 2, 0, -3 ], [ 5, -5, 0, 1, 0, -3 ] ], 1.625 ] + ], + [ + [ [ [ 3, -5, 0, 2, 0, -3 ], [ 4, -5, -1, 2, 0, -3 ], [ 4, -5, 1, 2, 0, -3 ], [ 5, -5, 0, 1, 0, -3 ] ], 1 ], + [ [ [ 3, -5, 0, 2, 0, -3 ], [ 4, -5, -1, 2, 0, -3 ], [ 4, -5, 1, 2, 0, -3 ], [ 5, -5, 0, 2, 0, -4 ] ], 0.5 ], + [ [ [ 3, -5, 0, 2, 0, -3 ], [ 4, -5, -1, 2, 0, -3 ], [ 4, -5, 0, 2, 1, -3 ], [ 5, -5, 0, 2, 0, -4 ] ], 1.5 ] + ], + [ + [ [ [ 3, -5, 0, 3, 0, -4 ], [ 4, -5, -1, 2, 0, -3 ], [ 4, -5, 0, 2, 1, -3 ], [ 5, -5, 0, 2, 0, -4 ] ], 0.5 ] + ], + [ + [ [ [ 3, -5, 0, 2, 1, -4 ], [ 4, -5, -1, 2, 0, -3 ], [ 4, -5, 0, 2, 1, -3 ], [ 5, -5, 0, 2, 0, -4 ] ], 1.875 ], + [ [ [ 3, -5, 0, 2, 1, -4 ], [ 4, -5, 0, 2, 1, -4 ], [ 4, -5, 0, 2, 1, -3 ], [ 5, -5, 0, 2, 0, -4 ] ], 1.75 ], + [ [ [ 3, -5, 0, 2, 1, -4 ], [ 4, -5, 0, 2, 1, -4 ], [ 4, -5, 0, 2, 1, -3 ], [ 3, -4, 0, 2, 1, -3 ] ], 0.5 ] + ], + [ + [ [ [ 3, -5, 0, 2, 1, -4 ], [ 4, -5, 0, 2, 1, -4 ], [ 4, -5, 0, 2, 1, -3 ], [ 3, -5, 0, 2, 1, -2 ] ], 1.5 ] + ], + [ + [ [ [ 3, -5, 0, 2, 1, -4 ], [ 4, -5, 0, 2, 1, -4 ], [ 4, -5, 0, 2, 2, -4 ], [ 3, -5, 0, 2, 1, -2 ] ], 1.25 ] + ], + [ + [ [ [ 2, -5, -1, 2, 1, -2 ], [ 4, -5, 0, 2, 1, -4 ], [ 4, -5, 0, 2, 2, -4 ], [ 3, -5, 0, 2, 1, -2 ] ], 1.375 ], + [ [ [ 2, -5, -1, 2, 1, -2 ], [ 4, -5, 0, 2, 1, -4 ], [ 4, -5, 0, 1, 1, -2 ], [ 3, -5, 0, 2, 1, -2 ] ], 0.875 ], + [ [ [ 2, -5, -1, 2, 1, -2 ], [ 2, -5, 0, 3, 1, -2 ], [ 4, -5, 0, 1, 1, -2 ], [ 3, -5, 0, 2, 1, -2 ] ], 1.875 ] + ], + [ + [ [ [ 2, -5, -1, 2, 1, -2 ], [ 4, -5, -1, 2, 1, -3 ], [ 4, -5, 0, 1, 1, -2 ], [ 3, -5, 0, 2, 1, -2 ] ], 0.875 ], + [ [ [ 2, -5, -1, 2, 1, -2 ], [ 4, -5, -1, 2, 1, -3 ], [ 4, -5, 0, 1, 1, -2 ], [ 4, -6, -1, 2, 1, -2 ] ], 1.75 ], + [ [ [ 2, -5, -1, 2, 1, -2 ], [ 4, -5, -1, 2, 1, -3 ], [ 3, -4, -1, 2, 1, -2 ], [ 4, -6, -1, 2, 1, -2 ] ], 0.875 ] + ], + [ + [ [ [ 2, -5, -1, 2, 1, -2 ], [ 4, -5, -1, 2, 1, -3 ], [ 3, -4, -1, 2, 1, -2 ], [ 5, -5, -1, 1, 1, -3 ] ], 1.125 ], + [ [ [ 2, -5, -1, 2, 1, -2 ], [ 4, -5, -1, 2, 1, -3 ], [ 4, -6, -1, 2, 1, -2 ], [ 5, -5, -1, 1, 1, -3 ] ], 1 ] + ], + [ + [ [ [ 2, -5, -1, 2, 1, -2 ], [ 4, -5, -1, 2, 1, -3 ], [ 4, -6, -1, 2, 1, -2 ], [ 3, -6, -1, 3, 1, -2 ] ], 0.625 ], + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 4, -5, -1, 2, 1, -3 ], [ 4, -6, -1, 2, 1, -2 ], [ 3, -6, -1, 3, 1, -2 ] ], 0.75 ], + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 2, -6, -1, 2, 1, -1 ], [ 4, -6, -1, 2, 1, -2 ], [ 3, -6, -1, 3, 1, -2 ] ], 0.5 ] + ], + [ + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 4, -6, -2, 1, 1, -2 ], [ 4, -6, -1, 2, 1, -2 ], [ 3, -6, -1, 3, 1, -2 ] ], 1.5 ], + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 4, -6, -2, 1, 1, -2 ], [ 4, -6, -1, 2, 1, -2 ], [ 5, -6, -2, 2, 1, -3 ] ], 0.75 ], + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 4, -6, -2, 1, 1, -2 ], [ 5, -7, -2, 2, 1, -2 ], [ 5, -6, -2, 2, 1, -3 ] ], 0.875 ] + ], + [ + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 4, -6, -2, 2, 1, -3 ], [ 5, -7, -2, 2, 1, -2 ], [ 5, -6, -2, 2, 1, -3 ] ], 1 ], + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 4, -6, -2, 2, 1, -3 ], [ 5, -7, -2, 2, 1, -2 ], [ 4, -6, -2, 2, 2, -2 ] ], 0.5 ], + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 4, -6, -2, 2, 1, -3 ], [ 5, -6, -2, 2, 0, -2 ], [ 4, -6, -2, 2, 2, -2 ] ], 1.125 ] + ], + [ + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 3, -6, -2, 2, 2, -2 ], [ 5, -6, -2, 2, 0, -2 ], [ 4, -6, -2, 2, 2, -2 ] ], 1.75 ], + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 3, -6, -2, 2, 2, -2 ], [ 5, -6, -2, 2, 0, -2 ], [ 5, -6, -2, 1, 1, -2 ] ], 0.5 ], + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 3, -6, -2, 2, 2, -2 ], [ 4, -6, -2, 2, 2, -2 ], [ 5, -6, -2, 1, 1, -2 ] ], 1.75 ] + ], + [ + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 5, -7, -2, 1, 1, -2 ], [ 4, -6, -2, 2, 2, -2 ], [ 5, -6, -2, 1, 1, -2 ] ], 0.875 ] + ], + [ + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 5, -7, -2, 1, 1, -2 ], [ 6, -7, -2, 1, 0, -2 ], [ 5, -6, -2, 1, 1, -2 ] ], 1.25 ] + ], + [ + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 5, -7, -2, 1, 1, -2 ], [ 6, -7, -3, 1, 1, -2 ], [ 5, -6, -2, 1, 1, -2 ] ], 1.625 ], + [ [ [ 3, -6, -2, 2, 1, -2 ], [ 5, -7, -2, 1, 1, -2 ], [ 6, -7, -3, 1, 1, -2 ], [ 5, -7, -2, 1, 1, -1 ] ], 1.375 ], + [ [ [ 5, -7, -2, 1, 0, -2 ], [ 5, -7, -2, 1, 1, -2 ], [ 6, -7, -3, 1, 1, -2 ], [ 5, -7, -2, 1, 1, -1 ] ], 1.375 ] + ], + [ + [ [ [ 5, -7, -2, 1, 0, -2 ], [ 5, -7, -2, 1, 1, -2 ], [ 7, -7, -2, 1, 0, -3 ], [ 5, -7, -2, 1, 1, -1 ] ], 1.5 ], + [ [ [ 5, -7, -2, 1, 0, -2 ], [ 5, -6, -2, 1, 0, -2 ], [ 7, -7, -2, 1, 0, -3 ], [ 5, -7, -2, 1, 1, -1 ] ], 0.875 ] + ], + [ + [ [ [ 5, -7, -2, 1, 0, -2 ], [ 5, -6, -2, 1, 0, -2 ], [ 7, -7, -2, 1, 0, -3 ], [ 5, -6, -2, 1, 0, -1 ] ], 0.75 ], + [ [ [ 6, -8, -2, 1, 0, -3 ], [ 5, -6, -2, 1, 0, -2 ], [ 7, -7, -2, 1, 0, -3 ], [ 5, -6, -2, 1, 0, -1 ] ], 1.75 ] + ], + [ + [ [ [ 6, -8, -2, 1, 0, -3 ], [ 5, -6, -2, 1, 0, -2 ], [ 5, -6, -2, 2, 0, -2 ], [ 5, -6, -2, 1, 0, -1 ] ], 0.5 ] + ], + [ + [ [ [ 6, -8, -2, 1, 0, -3 ], [ 5, -6, -2, 1, 0, -2 ], [ 5, -6, -2, 2, 0, -2 ], [ 6, -6, -2, 1, -1, -2 ] ], 1.375 ], + [ [ [ 6, -8, -2, 1, 0, -3 ], [ 5, -6, -2, 1, 0, -2 ], [ 6, -6, -3, 1, 0, -2 ], [ 6, -6, -2, 1, -1, -2 ] ], 1.25 ] + ], + [ + [ [ [ 5, -6, -3, 1, -1, -2 ], [ 5, -6, -2, 1, 0, -2 ], [ 6, -6, -3, 1, 0, -2 ], [ 6, -6, -2, 1, -1, -2 ] ], 1.75 ], + [ [ [ 5, -6, -3, 1, -1, -2 ], [ 5, -5, -2, 1, -1, -2 ], [ 6, -6, -3, 1, 0, -2 ], [ 6, -6, -2, 1, -1, -2 ] ], 1.375 ], + [ [ [ 5, -6, -3, 1, -1, -2 ], [ 5, -5, -2, 1, -1, -2 ], [ 4, -5, -2, 1, -1, -2 ], [ 6, -6, -2, 1, -1, -2 ] ], 1.25 ] + ], + [ + [ [ [ 5, -6, -3, 1, -1, -2 ], [ 6, -6, -3, 1, -1, -2 ], [ 4, -5, -2, 1, -1, -2 ], [ 6, -6, -2, 1, -1, -2 ] ], 1.375 ], + [ [ [ 4, -6, -2, 2, -1, -2 ], [ 6, -6, -3, 1, -1, -2 ], [ 4, -5, -2, 1, -1, -2 ], [ 6, -6, -2, 1, -1, -2 ] ], 1.125 ], + [ [ [ 4, -6, -2, 2, -1, -2 ], [ 6, -6, -3, 1, -1, -2 ], [ 4, -6, -2, 1, -1, -1 ], [ 6, -6, -2, 1, -1, -2 ] ], 1.75 ] + ], + [ + [ [ [ 4, -6, -2, 2, -1, -2 ], [ 5, -6, -2, 2, -1, -3 ], [ 4, -6, -2, 1, -1, -1 ], [ 6, -6, -2, 1, -1, -2 ] ], 1.625 ], + [ [ [ 4, -6, -2, 2, -1, -2 ], [ 5, -6, -2, 2, -1, -3 ], [ 4, -6, -2, 1, -1, -1 ], [ 5, -6, -1, 2, -1, -2 ] ], 0.875 ], + [ [ [ 4, -6, -2, 2, -1, -2 ], [ 5, -6, -2, 2, -1, -3 ], [ 3, -6, -2, 3, -1, -2 ], [ 5, -6, -1, 2, -1, -2 ] ], 1.625 ] + ], + [ + [ [ [ 4, -6, -2, 2, -1, -2 ], [ 5, -6, -2, 2, -1, -3 ], [ 3, -5, -1, 2, -1, -2 ], [ 5, -6, -1, 2, -1, -2 ] ], 0.875 ], + [ [ [ 3, -6, -1, 3, -1, -2 ], [ 5, -6, -2, 2, -1, -3 ], [ 3, -5, -1, 2, -1, -2 ], [ 5, -6, -1, 2, -1, -2 ] ], 1.375 ] + ], + [ + [ [ [ 3, -6, -1, 3, -1, -2 ], [ 4, -6, 0, 2, -1, -2 ], [ 3, -5, -1, 2, -1, -2 ], [ 5, -6, -1, 2, -1, -2 ] ], 1.25 ], + [ [ [ 3, -6, -1, 3, -1, -2 ], [ 4, -6, 0, 2, -1, -2 ], [ 4, -6, -2, 2, -1, -2 ], [ 5, -6, -1, 2, -1, -2 ] ], 1.25 ], + [ [ [ 3, -5, -1, 2, -1, -2 ], [ 4, -6, 0, 2, -1, -2 ], [ 4, -6, -2, 2, -1, -2 ], [ 5, -6, -1, 2, -1, -2 ] ], 1.25 ] + ], + [ + [ [ [ 3, -5, -1, 2, -1, -2 ], [ 4, -6, 0, 2, -1, -2 ], [ 4, -5, -1, 2, -1, -3 ], [ 5, -6, -1, 2, -1, -2 ] ], 1.5 ], + [ [ [ 3, -5, -1, 2, -1, -2 ], [ 5, -7, -1, 2, -1, -2 ], [ 4, -5, -1, 2, -1, -3 ], [ 5, -6, -1, 2, -1, -2 ] ], 1.125 ] + ], + [ + [ [ [ 3, -5, -1, 2, -1, -2 ], [ 5, -7, -1, 2, -1, -2 ], [ 4, -5, -1, 2, -1, -3 ], [ 5, -7, 0, 2, -1, -2 ] ], 0.75 ], + [ [ [ 3, -5, -1, 2, -1, -2 ], [ 5, -7, -1, 2, -1, -2 ], [ 4, -6, -1, 2, -1, -2 ], [ 5, -7, 0, 2, -1, -2 ] ], 1.5 ], + [ [ [ 5, -7, -1, 2, -1, -3 ], [ 5, -7, -1, 2, -1, -2 ], [ 4, -6, -1, 2, -1, -2 ], [ 5, -7, 0, 2, -1, -2 ] ], 1.625 ] + ], + [ + [ [ [ 5, -7, -1, 2, -1, -3 ], [ 4, -7, 0, 3, -1, -2 ], [ 4, -6, -1, 2, -1, -2 ], [ 5, -7, 0, 2, -1, -2 ] ], 1.375 ], + [ [ [ 5, -7, -1, 2, -1, -3 ], [ 4, -7, 0, 3, -1, -2 ], [ 6, -8, -1, 2, -1, -3 ], [ 5, -7, 0, 2, -1, -2 ] ], 0.375 ] + ], + [ + [ [ [ 5, -7, -1, 2, -1, -3 ], [ 4, -7, 0, 3, -1, -2 ], [ 3, -7, 0, 3, -1, -1 ], [ 5, -7, 0, 2, -1, -2 ] ], 1.125 ], + [ [ [ 4, -7, 0, 2, -1, -2 ], [ 4, -7, 0, 3, -1, -2 ], [ 3, -7, 0, 3, -1, -1 ], [ 5, -7, 0, 2, -1, -2 ] ], 0.875 ], + [ [ [ 4, -7, 0, 2, -1, -2 ], [ 4, -7, 0, 3, -1, -2 ], [ 3, -7, 0, 3, -1, -1 ], [ 5, -7, 0, 3, -1, -3 ] ], 1.125 ] + ], + [ + [ [ [ 3, -7, 0, 3, -1, -2 ], [ 4, -7, 0, 3, -1, -2 ], [ 3, -7, 0, 3, -1, -1 ], [ 5, -7, 0, 3, -1, -3 ] ], 0.75 ], + [ [ [ 3, -7, 0, 3, -1, -2 ], [ 4, -7, 0, 3, -1, -2 ], [ 3, -7, 0, 3, -1, -1 ], [ 5, -8, 0, 3, -1, -2 ] ], 0.625 ], + [ [ [ 3, -7, 0, 3, -1, -2 ], [ 4, -7, 0, 3, -1, -2 ], [ 3, -7, 0, 4, -1, -2 ], [ 5, -8, 0, 3, -1, -2 ] ], 1.125 ] + ], + [ + [ [ [ 3, -7, 0, 3, -1, -2 ], [ 4, -7, 0, 4, -1, -3 ], [ 3, -7, 0, 4, -1, -2 ], [ 5, -8, 0, 3, -1, -2 ] ], 0.5 ] + ], + [ + [ [ [ 3, -7, 0, 3, -1, -2 ], [ 4, -7, -1, 3, -1, -2 ], [ 3, -7, 0, 4, -1, -2 ], [ 5, -8, 0, 3, -1, -2 ] ], 0.625 ], + [ [ [ 3, -7, 0, 3, -1, -2 ], [ 4, -7, -1, 3, -1, -2 ], [ 4, -8, 0, 3, 0, -2 ], [ 5, -8, 0, 3, -1, -2 ] ], 1.625 ] + ], + [ + [ [ [ 3, -7, 0, 3, -1, -2 ], [ 4, -7, -1, 3, -1, -2 ], [ 4, -8, 0, 3, 0, -2 ], [ 5, -7, 0, 3, -2, -2 ] ], 1.125 ], + [ [ [ 3, -7, 0, 3, -1, -2 ], [ 4, -7, -1, 3, -1, -2 ], [ 4, -7, 0, 3, -1, -2 ], [ 5, -7, 0, 3, -2, -2 ] ], 1.625 ], + [ [ [ 3, -7, 0, 3, -1, -2 ], [ 3, -7, 0, 4, -1, -2 ], [ 4, -7, 0, 3, -1, -2 ], [ 5, -7, 0, 3, -2, -2 ] ], 1 ] + ], + [ + [ [ [ 3, -7, 0, 3, -1, -2 ], [ 3, -7, 0, 4, -1, -2 ], [ 4, -7, 0, 4, -1, -3 ], [ 5, -7, 0, 3, -2, -2 ] ], 0.875 ] + ], + [ + [ [ [ 4, -7, 0, 3, -3, -2 ], [ 3, -7, 0, 4, -1, -2 ], [ 4, -7, 0, 4, -1, -3 ], [ 5, -7, 0, 3, -2, -2 ] ], 1.625 ], + [ [ [ 4, -7, 0, 3, -3, -2 ], [ 3, -7, 0, 3, -1, -2 ], [ 4, -7, 0, 4, -1, -3 ], [ 5, -7, 0, 3, -2, -2 ] ], 1 ] + ], + [ + [ [ [ 4, -7, 0, 3, -3, -2 ], [ 3, -7, 0, 4, -1, -3 ], [ 4, -7, 0, 4, -1, -3 ], [ 5, -7, 0, 3, -2, -2 ] ], 0.75 ], + [ [ [ 4, -7, 0, 3, -3, -2 ], [ 3, -7, 0, 4, -1, -3 ], [ 4, -7, 0, 4, -1, -3 ], [ 5, -7, 0, 4, -1, -4 ] ], 1.5 ] + ], + [ + [ [ [ 4, -7, 0, 3, -3, -2 ], [ 3, -7, 0, 4, -1, -3 ], [ 3, -6, 0, 4, -1, -3 ], [ 5, -7, 0, 4, -1, -4 ] ], 1 ], + [ [ [ 3, -7, 0, 5, -1, -4 ], [ 3, -7, 0, 4, -1, -3 ], [ 3, -6, 0, 4, -1, -3 ], [ 5, -7, 0, 4, -1, -4 ] ], 0.625 ] + ], + [ + [ [ [ 3, -7, 0, 5, -1, -4 ], [ 3, -7, 0, 4, -1, -3 ], [ 5, -8, 0, 4, -1, -4 ], [ 5, -7, 0, 4, -1, -4 ] ], 1 ], + [ [ [ 3, -7, 0, 5, -1, -4 ], [ 3, -7, 0, 4, 0, -4 ], [ 5, -8, 0, 4, -1, -4 ], [ 5, -7, 0, 4, -1, -4 ] ], 0.5 ] + ], + [ + [ [ [ 3, -7, 0, 5, -1, -4 ], [ 3, -7, 0, 4, 0, -4 ], [ 5, -8, 0, 4, -1, -4 ], [ 4, -7, 1, 5, -1, -4 ] ], 1.25 ] + ], + [ + [ [ [ 4, -8, 0, 4, 0, -4 ], [ 3, -7, 0, 4, 0, -4 ], [ 5, -8, 0, 4, -1, -4 ], [ 4, -7, 1, 5, -1, -4 ] ], 1.75 ], + [ [ [ 4, -8, 0, 4, 0, -4 ], [ 3, -7, 0, 4, 0, -4 ], [ 3, -7, 0, 4, 0, -3 ], [ 4, -7, 1, 5, -1, -4 ] ], 1.75 ], + [ [ [ 4, -8, 0, 4, 0, -4 ], [ 3, -7, 0, 4, 0, -4 ], [ 3, -7, 0, 4, 0, -3 ], [ 4, -7, 0, 4, 1, -4 ] ], 1 ] + ], + [ + [ [ [ 4, -8, 0, 4, 0, -4 ], [ 3, -7, 0, 4, 0, -4 ], [ 3, -7, 0, 4, 2, -4 ], [ 4, -7, 0, 4, 1, -4 ] ], 1.25 ], + [ [ [ 4, -8, 0, 4, 0, -4 ], [ 3, -7, -1, 4, 1, -4 ], [ 3, -7, 0, 4, 2, -4 ], [ 4, -7, 0, 4, 1, -4 ] ], 0.625 ], + [ [ [ 2, -7, 0, 5, 1, -4 ], [ 3, -7, -1, 4, 1, -4 ], [ 3, -7, 0, 4, 2, -4 ], [ 4, -7, 0, 4, 1, -4 ] ], 0.875 ] + ], + [ + [ [ [ 2, -7, 0, 5, 1, -4 ], [ 3, -7, -1, 4, 1, -4 ], [ 3, -7, 0, 4, 2, -4 ], [ 3, -7, 0, 4, 2, -3 ] ], 0.875 ] + ], + [ + [ [ [ 2, -7, 0, 5, 1, -4 ], [ 3, -7, -1, 4, 1, -4 ], [ 3, -7, 0, 4, 2, -4 ], [ 4, -8, 0, 4, 2, -4 ] ], 0.5 ], + [ [ [ 2, -7, 0, 5, 1, -4 ], [ 3, -7, 0, 4, 2, -5 ], [ 3, -7, 0, 4, 2, -4 ], [ 4, -8, 0, 4, 2, -4 ] ], 1.375 ], + [ [ [ 2, -7, 0, 4, 3, -4 ], [ 3, -7, 0, 4, 2, -5 ], [ 3, -7, 0, 4, 2, -4 ], [ 4, -8, 0, 4, 2, -4 ] ], 0.5 ] + ], + [ + [ [ [ 2, -7, 0, 4, 3, -4 ], [ 3, -7, 0, 4, 2, -5 ], [ 3, -7, 0, 4, 2, -4 ], [ 3, -6, 0, 4, 2, -4 ] ], 1.375 ], + [ [ [ 2, -7, 0, 4, 3, -4 ], [ 3, -8, 0, 4, 2, -4 ], [ 3, -7, 0, 4, 2, -4 ], [ 3, -6, 0, 4, 2, -4 ] ], 0.375 ] + ], + [ + [ [ [ 1, -5, 0, 4, 2, -4 ], [ 3, -8, 0, 4, 2, -4 ], [ 3, -7, 0, 4, 2, -4 ], [ 3, -6, 0, 4, 2, -4 ] ], 1.375 ], + [ [ [ 1, -5, 0, 4, 2, -4 ], [ 2, -6, 0, 4, 2, -4 ], [ 3, -7, 0, 4, 2, -4 ], [ 3, -6, 0, 4, 2, -4 ] ], 1.125 ], + [ [ [ 1, -5, 0, 4, 2, -4 ], [ 2, -6, 0, 4, 2, -4 ], [ 2, -5, 0, 4, 2, -4 ], [ 3, -6, 0, 4, 2, -4 ] ], 1.5 ] + ], + [ + [ [ [ 2, -5, 0, 3, 2, -4 ], [ 2, -6, 0, 4, 2, -4 ], [ 2, -5, 0, 4, 2, -4 ], [ 3, -6, 0, 4, 2, -4 ] ], 1.5 ], + [ [ [ 2, -5, 0, 3, 2, -4 ], [ 1, -4, 0, 4, 2, -4 ], [ 2, -5, 0, 4, 2, -4 ], [ 3, -6, 0, 4, 2, -4 ] ], 1.125 ], + [ [ [ 2, -5, 0, 3, 2, -4 ], [ 1, -4, 0, 4, 2, -4 ], [ 2, -5, 0, 4, 2, -4 ], [ 3, -5, 0, 4, 1, -4 ] ], 0.625 ] + ], + [ + [ [ [ 2, -5, 0, 3, 2, -4 ], [ 1, -4, 0, 4, 2, -4 ], [ 2, -4, 0, 4, 1, -4 ], [ 3, -5, 0, 4, 1, -4 ] ], 1.25 ] + ], + [ + [ [ [ 2, -5, 0, 3, 2, -4 ], [ 3, -5, 0, 3, 1, -4 ], [ 2, -4, 0, 4, 1, -4 ], [ 3, -5, 0, 4, 1, -4 ] ], 1.75 ], + [ [ [ 2, -5, 0, 3, 2, -4 ], [ 3, -5, 0, 3, 1, -4 ], [ 3, -5, 0, 3, 2, -5 ], [ 3, -5, 0, 4, 1, -4 ] ], 1.625 ], + [ [ [ 2, -5, 0, 3, 2, -4 ], [ 3, -5, 0, 3, 1, -4 ], [ 3, -5, 0, 3, 2, -5 ], [ 3, -5, 0, 3, 2, -4 ] ], 0.625 ] + ], + [ + [ [ [ 3, -5, 0, 3, 0, -4 ], [ 3, -5, 0, 3, 1, -4 ], [ 3, -5, 0, 3, 2, -5 ], [ 3, -5, 0, 3, 2, -4 ] ], 1.625 ], + [ [ [ 3, -5, 0, 3, 0, -4 ], [ 3, -5, 0, 3, 1, -4 ], [ 3, -6, 0, 3, 2, -4 ], [ 3, -5, 0, 3, 2, -4 ] ], 1 ] + ], + [ + [ [ [ 3, -5, 0, 3, 0, -4 ], [ 3, -5, 0, 3, 1, -4 ], [ 3, -6, 0, 3, 2, -4 ], [ 3, -6, 0, 4, 2, -4 ] ], 1.375 ], + [ [ [ 2, -6, 1, 3, 2, -4 ], [ 3, -5, 0, 3, 1, -4 ], [ 3, -6, 0, 3, 2, -4 ], [ 3, -6, 0, 4, 2, -4 ] ], 1 ], + [ [ [ 2, -6, 1, 3, 2, -4 ], [ 3, -6, 1, 3, 2, -4 ], [ 3, -6, 0, 3, 2, -4 ], [ 3, -6, 0, 4, 2, -4 ] ], 0.75 ] + ], + [ + [ [ [ 2, -6, 1, 3, 2, -4 ], [ 3, -6, 0, 3, 3, -4 ], [ 3, -6, 0, 3, 2, -4 ], [ 3, -6, 0, 4, 2, -4 ] ], 0.875 ] + ], + [ + [ [ [ 2, -6, 0, 3, 3, -4 ], [ 3, -6, 0, 3, 3, -4 ], [ 3, -6, 0, 3, 2, -4 ], [ 3, -6, 0, 4, 2, -4 ] ], 1.5 ], + [ [ [ 2, -6, 0, 3, 3, -4 ], [ 3, -6, 0, 3, 3, -4 ], [ 3, -6, -1, 3, 3, -4 ], [ 3, -6, 0, 4, 2, -4 ] ], 0.5 ], + [ [ [ 2, -6, 0, 3, 3, -4 ], [ 3, -6, 0, 3, 3, -4 ], [ 3, -6, -1, 3, 3, -4 ], [ 3, -6, 0, 3, 4, -4 ] ], 1.5 ] + ], + [ + [ [ [ 2, -6, 0, 3, 3, -4 ], [ 3, -6, -1, 3, 4, -4 ], [ 3, -6, -1, 3, 3, -4 ], [ 3, -6, 0, 3, 4, -4 ] ], 1.125 ] + ], + [ + [ [ [ 2, -6, 0, 3, 3, -4 ], [ 4, -6, -2, 3, 3, -4 ], [ 3, -6, -1, 3, 3, -4 ], [ 3, -6, 0, 3, 4, -4 ] ], 1 ] + ], + [ + [ [ [ 2, -6, 0, 3, 3, -4 ], [ 4, -6, -2, 3, 3, -4 ], [ 3, -6, -1, 3, 3, -4 ], [ 3, -6, -2, 4, 3, -4 ] ], 1.625 ], + [ [ [ 2, -6, 0, 3, 3, -4 ], [ 4, -6, -2, 3, 3, -4 ], [ 3, -6, -2, 3, 4, -4 ], [ 3, -6, -2, 4, 3, -4 ] ], 0.625 ], + [ [ [ 2, -6, -2, 4, 3, -4 ], [ 4, -6, -2, 3, 3, -4 ], [ 3, -6, -2, 3, 4, -4 ], [ 3, -6, -2, 4, 3, -4 ] ], 1.625 ] + ], + [ + [ [ [ 2, -6, -2, 4, 3, -4 ], [ 3, -6, -1, 4, 3, -4 ], [ 3, -6, -2, 3, 4, -4 ], [ 3, -6, -2, 4, 3, -4 ] ], 1.375 ], + [ [ [ 2, -6, -2, 4, 3, -4 ], [ 3, -6, -1, 4, 3, -4 ], [ 2, -6, -2, 5, 3, -4 ], [ 3, -6, -2, 4, 3, -4 ] ], 0.5 ], + [ [ [ 1, -6, -2, 4, 3, -3 ], [ 3, -6, -1, 4, 3, -4 ], [ 2, -6, -2, 5, 3, -4 ], [ 3, -6, -2, 4, 3, -4 ] ], 1.625 ] + ], + [ + [ [ [ 1, -6, -2, 4, 3, -3 ], [ 2, -6, -2, 4, 3, -4 ], [ 2, -6, -2, 5, 3, -4 ], [ 3, -6, -2, 4, 3, -4 ] ], 1.125 ], + [ [ [ 1, -6, -2, 4, 3, -3 ], [ 2, -6, -2, 4, 3, -4 ], [ 3, -6, -2, 4, 3, -5 ], [ 3, -6, -2, 4, 3, -4 ] ], 1.125 ] + ], + [ + [ [ [ 1, -6, -2, 4, 3, -3 ], [ 2, -6, -2, 4, 3, -4 ], [ 3, -6, -2, 4, 3, -5 ], [ 3, -7, -2, 4, 3, -3 ] ], 1 ], + [ [ [ 1, -6, -2, 4, 3, -3 ], [ 2, -6, -2, 4, 3, -4 ], [ 1, -6, -2, 4, 3, -2 ], [ 3, -7, -2, 4, 3, -3 ] ], 0.875 ], + [ [ [ 1, -6, -2, 4, 3, -3 ], [ 2, -7, -2, 4, 3, -3 ], [ 1, -6, -2, 4, 3, -2 ], [ 3, -7, -2, 4, 3, -3 ] ], 1.125 ] + ], + [ + [ [ [ 1, -6, -2, 4, 3, -3 ], [ 3, -7, -2, 3, 3, -3 ], [ 1, -6, -2, 4, 3, -2 ], [ 3, -7, -2, 4, 3, -3 ] ], 1.75 ], + [ [ [ 1, -7, -2, 5, 3, -3 ], [ 3, -7, -2, 3, 3, -3 ], [ 1, -6, -2, 4, 3, -2 ], [ 3, -7, -2, 4, 3, -3 ] ], 1.5 ], + [ [ [ 1, -7, -2, 5, 3, -3 ], [ 3, -7, -2, 3, 3, -3 ], [ 2, -7, -2, 4, 4, -3 ], [ 3, -7, -2, 4, 3, -3 ] ], 0.75 ] + ], + [ + [ [ [ 1, -7, -2, 5, 3, -3 ], [ 3, -7, -2, 3, 3, -3 ], [ 2, -6, -2, 4, 3, -3 ], [ 3, -7, -2, 4, 3, -3 ] ], 0.625 ] + ], + [ + [ [ [ 1, -6, -1, 4, 3, -3 ], [ 3, -7, -2, 3, 3, -3 ], [ 2, -6, -2, 4, 3, -3 ], [ 3, -7, -2, 4, 3, -3 ] ], 1.375 ], + [ [ [ 1, -6, -1, 4, 3, -3 ], [ 3, -7, -2, 4, 3, -4 ], [ 2, -6, -2, 4, 3, -3 ], [ 3, -7, -2, 4, 3, -3 ] ], 1.625 ] + ], + [ + [ [ [ 1, -6, -1, 4, 3, -3 ], [ 3, -7, -2, 4, 3, -4 ], [ 2, -6, -1, 4, 3, -4 ], [ 3, -7, -2, 4, 3, -3 ] ], 0.875 ], + [ [ [ 1, -6, -1, 4, 3, -3 ], [ 3, -7, -2, 4, 3, -4 ], [ 2, -6, -1, 4, 3, -4 ], [ 3, -6, -1, 3, 3, -3 ] ], 1.625 ], + [ [ [ 1, -6, -1, 4, 3, -3 ], [ 2, -6, -1, 4, 2, -3 ], [ 2, -6, -1, 4, 3, -4 ], [ 3, -6, -1, 3, 3, -3 ] ], 1.375 ] + ], + [ + [ [ [ 1, -6, -1, 4, 3, -3 ], [ 0, -6, -1, 4, 3, -2 ], [ 2, -6, -1, 4, 3, -4 ], [ 3, -6, -1, 3, 3, -3 ] ], 1.5 ] + ], + [ + [ [ [ 1, -5, -1, 3, 3, -3 ], [ 0, -6, -1, 4, 3, -2 ], [ 2, -6, -1, 4, 3, -4 ], [ 3, -6, -1, 3, 3, -3 ] ], 1.625 ], + [ [ [ 1, -5, -1, 3, 3, -3 ], [ 0, -6, -1, 4, 3, -2 ], [ 3, -6, -1, 3, 3, -4 ], [ 3, -6, -1, 3, 3, -3 ] ], 1.625 ] + ], + [ + [ [ [ 1, -5, -1, 3, 3, -3 ], [ 1, -6, -1, 3, 3, -2 ], [ 3, -6, -1, 3, 3, -4 ], [ 3, -6, -1, 3, 3, -3 ] ], 1.875 ], + [ [ [ 1, -5, -1, 3, 3, -3 ], [ 1, -6, -1, 3, 3, -2 ], [ 1, -6, -1, 4, 3, -3 ], [ 3, -6, -1, 3, 3, -3 ] ], 1.5 ], + [ [ [ 2, -6, -2, 3, 3, -3 ], [ 1, -6, -1, 3, 3, -2 ], [ 1, -6, -1, 4, 3, -3 ], [ 3, -6, -1, 3, 3, -3 ] ], 0.625 ] + ], + [ + [ [ [ 2, -6, -1, 3, 2, -3 ], [ 1, -6, -1, 3, 3, -2 ], [ 1, -6, -1, 4, 3, -3 ], [ 3, -6, -1, 3, 3, -3 ] ], 1 ] + ], + [ + [ [ [ 0, -6, -1, 5, 3, -3 ], [ 1, -6, -1, 3, 3, -2 ], [ 1, -6, -1, 4, 3, -3 ], [ 3, -6, -1, 3, 3, -3 ] ], 1.75 ] + ], + [ + [ [ [ 0, -6, -1, 5, 3, -3 ], [ 1, -6, -1, 3, 3, -2 ], [ 2, -6, -1, 2, 3, -2 ], [ 3, -6, -1, 3, 3, -3 ] ], 0.625 ], + [ [ [ 1, -6, -1, 4, 3, -3 ], [ 1, -6, -1, 3, 3, -2 ], [ 2, -6, -1, 2, 3, -2 ], [ 3, -6, -1, 3, 3, -3 ] ], 0.875 ] + ], + [ + [ [ [ 1, -5, -1, 2, 3, -2 ], [ 1, -6, -1, 3, 3, -2 ], [ 2, -6, -1, 2, 3, -2 ], [ 3, -6, -1, 3, 3, -3 ] ], 0.875 ], + [ [ [ 1, -5, -1, 2, 3, -2 ], [ 1, -6, -1, 3, 3, -2 ], [ 2, -6, -1, 2, 3, -2 ], [ 4, -6, -1, 1, 3, -2 ] ], 1 ] + ], + [ + [ [ [ 3, -6, -1, 1, 2, -2 ], [ 1, -6, -1, 3, 3, -2 ], [ 2, -6, -1, 2, 3, -2 ], [ 4, -6, -1, 1, 3, -2 ] ], 1.375 ], + [ [ [ 3, -6, -1, 1, 2, -2 ], [ 2, -6, -1, 1, 3, -1 ], [ 2, -6, -1, 2, 3, -2 ], [ 4, -6, -1, 1, 3, -2 ] ], 0.5 ], + [ [ [ 3, -6, -1, 1, 2, -2 ], [ 2, -6, -1, 1, 3, -1 ], [ 3, -6, -1, 1, 3, -2 ], [ 4, -6, -1, 1, 3, -2 ] ], 1.5 ] + ], + [ + [ [ [ 3, -6, -1, 1, 2, -2 ], [ 2, -6, -1, 1, 3, -1 ], [ 3, -6, -1, 1, 3, -2 ], [ 3, -6, -1, 2, 3, -2 ] ], 0.625 ], + [ [ [ 3, -6, -2, 1, 3, -2 ], [ 2, -6, -1, 1, 3, -1 ], [ 3, -6, -1, 1, 3, -2 ], [ 3, -6, -1, 2, 3, -2 ] ], 1.75 ] + ], + [ + [ [ [ 3, -6, -2, 1, 3, -2 ], [ 2, -6, -1, 1, 3, -1 ], [ 3, -6, -2, 1, 4, -2 ], [ 3, -6, -1, 2, 3, -2 ] ], 1.25 ], + [ [ [ 3, -6, -2, 1, 3, -2 ], [ 4, -6, -2, 0, 3, -2 ], [ 3, -6, -2, 1, 4, -2 ], [ 3, -6, -1, 2, 3, -2 ] ], 1.625 ], + [ [ [ 3, -6, -2, 1, 3, -2 ], [ 4, -6, -2, 0, 3, -2 ], [ 3, -6, -2, 1, 4, -2 ], [ 5, -6, -2, 1, 3, -3 ] ], 0.875 ] + ], + [ + [ [ [ 3, -6, -2, 1, 3, -2 ], [ 4, -6, -2, 0, 3, -2 ], [ 3, -6, -2, 1, 4, -2 ], [ 4, -6, -2, 1, 3, -2 ] ], 1.125 ], + [ [ [ 3, -7, -2, 0, 3, -2 ], [ 4, -6, -2, 0, 3, -2 ], [ 3, -6, -2, 1, 4, -2 ], [ 4, -6, -2, 1, 3, -2 ] ], 0.375 ] + ], + [ + [ [ [ 3, -7, -2, 0, 3, -2 ], [ 2, -6, -2, 2, 3, -2 ], [ 3, -6, -2, 1, 4, -2 ], [ 4, -6, -2, 1, 3, -2 ] ], 1 ] + ], + [ + [ [ [ 1, -6, -2, 2, 3, -2 ], [ 2, -6, -2, 2, 3, -2 ], [ 3, -6, -2, 1, 4, -2 ], [ 4, -6, -2, 1, 3, -2 ] ], 0.5 ] + ], + [ + [ [ [ 2, -6, -2, 1, 2, -2 ], [ 2, -6, -2, 2, 3, -2 ], [ 3, -6, -2, 1, 4, -2 ], [ 4, -6, -2, 1, 3, -2 ] ], 0.875 ] + ], + [ + [ [ [ 2, -6, -2, 1, 2, -2 ], [ 2, -6, -2, 2, 3, -2 ], [ 3, -6, -2, 1, 4, -2 ], [ 4, -6, -2, 2, 3, -3 ] ], 1.125 ], + [ [ [ 2, -6, -2, 1, 2, -2 ], [ 2, -6, -2, 2, 3, -2 ], [ 3, -6, -2, 1, 3, -2 ], [ 4, -6, -2, 2, 3, -3 ] ], 0.5 ] + ], + [ + [ [ [ 2, -6, -2, 1, 2, -2 ], [ 2, -6, -2, 2, 3, -2 ], [ 4, -6, -2, 1, 1, -2 ], [ 4, -6, -2, 2, 3, -3 ] ], 1 ], + [ [ [ 2, -6, -2, 1, 2, -2 ], [ 3, -6, -2, 1, 3, -2 ], [ 4, -6, -2, 1, 1, -2 ], [ 4, -6, -2, 2, 3, -3 ] ], 1.625 ] + ], + [ + [ [ [ 2, -6, -2, 1, 2, -2 ], [ 3, -6, -2, 1, 3, -2 ], [ 4, -6, -2, 1, 1, -2 ], [ 6, -6, -2, 0, 1, -2 ] ], 1.375 ], + [ [ [ 3, -6, -2, 1, 0, -2 ], [ 3, -6, -2, 1, 3, -2 ], [ 4, -6, -2, 1, 1, -2 ], [ 6, -6, -2, 0, 1, -2 ] ], 1.5 ], + [ [ [ 3, -6, -2, 1, 0, -2 ], [ 3, -5, -2, 1, 1, -2 ], [ 4, -6, -2, 1, 1, -2 ], [ 6, -6, -2, 0, 1, -2 ] ], 0.5 ] + ], + [ + [ [ [ 3, -6, -2, 1, 0, -2 ], [ 5, -6, -2, 0, 0, -2 ], [ 4, -6, -2, 1, 1, -2 ], [ 6, -6, -2, 0, 1, -2 ] ], 1.25 ], + [ [ [ 4, -6, -2, 0, 0, -2 ], [ 5, -6, -2, 0, 0, -2 ], [ 4, -6, -2, 1, 1, -2 ], [ 6, -6, -2, 0, 1, -2 ] ], 0.5 ], + [ [ [ 4, -6, -2, 0, 0, -2 ], [ 5, -6, -2, 0, 0, -2 ], [ 5, -6, -2, 0, 1, -2 ], [ 6, -6, -2, 0, 1, -2 ] ], 0.625 ] + ], + [ + [ [ [ 5, -6, -2, -1, 0, -2 ], [ 5, -6, -2, 0, 0, -2 ], [ 5, -6, -2, 0, 1, -2 ], [ 6, -6, -2, 0, 1, -2 ] ], 1.625 ], + [ [ [ 5, -6, -2, -1, 0, -2 ], [ 5, -6, -2, 0, 0, -2 ], [ 5, -5, -2, 0, 0, -2 ], [ 6, -6, -2, 0, 1, -2 ] ], 1.625 ], + [ [ [ 5, -6, -2, -1, 0, -2 ], [ 5, -6, -2, 0, 0, -2 ], [ 5, -5, -2, 0, 0, -2 ], [ 7, -6, -2, 0, -1, -2 ] ], 1 ] + ], + [ + [ [ [ 5, -6, -2, -1, 0, -2 ], [ 4, -4, -2, 0, 0, -2 ], [ 5, -5, -2, 0, 0, -2 ], [ 7, -6, -2, 0, -1, -2 ] ], 1.625 ] + ], + [ + [ [ [ 5, -6, -2, -1, 0, -2 ], [ 4, -4, -2, 0, 0, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 7, -6, -2, 0, -1, -2 ] ], 1 ] + ], + [ + [ [ [ 6, -6, -2, -1, -2, -2 ], [ 4, -4, -2, 0, 0, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 7, -6, -2, 0, -1, -2 ] ], 0.5 ], + [ [ [ 6, -6, -2, -1, -2, -2 ], [ 7, -6, -2, -1, -2, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 7, -6, -2, 0, -1, -2 ] ], 1.375 ], + [ [ [ 6, -6, -2, -1, -2, -2 ], [ 7, -6, -2, -1, -2, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 7, -5, -2, -1, -1, -2 ] ], 1.125 ] + ], + [ + [ [ [ 6, -6, -2, -1, -2, -2 ], [ 7, -6, -3, -1, -1, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 7, -5, -2, -1, -1, -2 ] ], 0.625 ], + [ [ [ 6, -6, -3, -1, -1, -2 ], [ 7, -6, -3, -1, -1, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 7, -5, -2, -1, -1, -2 ] ], 0.875 ] + ], + [ + [ [ [ 6, -6, -3, -1, -1, -2 ], [ 7, -6, -3, -1, -1, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 8, -6, -3, -1, -1, -2 ] ], 0.625 ], + [ [ [ 6, -6, -3, -1, -1, -2 ], [ 8, -6, -3, -2, -1, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 8, -6, -3, -1, -1, -2 ] ], 1.5 ] + ], + [ + [ [ [ 6, -6, -3, -1, -1, -2 ], [ 8, -6, -3, -2, -1, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 7, -6, -2, 0, -1, -2 ] ], 1.125 ] + ], + [ + [ [ [ 6, -6, -3, -1, -1, -2 ], [ 8, -6, -3, -2, -1, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 7, -5, -3, -2, -1, -2 ] ], 0.375 ] + ], + [ + [ [ [ 6, -6, -3, -1, -1, -2 ], [ 8, -6, -3, -2, -1, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 7, -6, -2, -1, -2, -2 ] ], 1.5 ] + ], + [ + [ [ [ 6, -6, -3, -1, -1, -2 ], [ 8, -6, -3, -2, -1, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 6, -6, -2, -1, -1, -1 ] ], 1.25 ] + ], + [ + [ [ [ 6, -6, -3, -1, -1, -2 ], [ 8, -6, -3, -2, -1, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 1.625 ], + [ [ [ 6, -6, -3, -1, -1, -2 ], [ 7, -6, -2, -1, -2, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 1.75 ], + [ [ [ 5, -6, -2, 0, -1, -2 ], [ 7, -6, -2, -1, -2, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 0.5 ] + ], + [ + [ [ [ 5, -6, -2, 0, -1, -2 ], [ 7, -6, -2, -1, -2, -2 ], [ 7, -5, -2, -1, -2, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 1.5 ] + ], + [ + [ [ [ 5, -6, -2, 0, -1, -2 ], [ 7, -5, -2, -1, -3, -2 ], [ 7, -5, -2, -1, -2, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 1.375 ], + [ [ [ 5, -5, -2, 0, -2, -2 ], [ 7, -5, -2, -1, -3, -2 ], [ 7, -5, -2, -1, -2, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 1.25 ] + ], + [ + [ [ [ 5, -5, -2, 0, -2, -2 ], [ 7, -5, -2, -1, -3, -2 ], [ 8, -5, -2, -1, -4, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], 0.75 ], + [ [ [ 5, -5, -2, 0, -2, -2 ], [ 7, -5, -2, -1, -3, -2 ], [ "Rest" ], [ 6, -6, -2, 0, -1, -2 ] ], 1.25 ], + [ [ [ "Rest" ], [ 7, -5, -2, -1, -3, -2 ], [ "Rest" ], [ 6, -6, -2, 0, -1, -2 ] ], 0.5 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 6, -6, -2, 0, -1, -2 ] ], 1.25 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 6.625 ] + ] + ] +], +"last_changes": +[ + [ [ 5, -6, -2, 0, -1, -2 ], [ 7, -6, -2, -1, -2, -2 ], [ 7, -6, -2, -1, -1, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], + [ [ 5, -6, -2, 0, -1, -2 ], [ 7, -6, -2, -1, -2, -2 ], [ 7, -5, -2, -1, -2, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], + [ [ 5, -6, -2, 0, -1, -2 ], [ 7, -5, -2, -1, -3, -2 ], [ 7, -5, -2, -1, -2, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], + [ [ 5, -5, -2, 0, -2, -2 ], [ 7, -5, -2, -1, -3, -2 ], [ 7, -5, -2, -1, -2, -2 ], [ 6, -6, -2, 0, -1, -2 ] ], + [ [ 5, -5, -2, 0, -2, -2 ], [ 7, -5, -2, -1, -3, -2 ], [ 8, -5, -2, -1, -4, -2 ], [ 6, -6, -2, 0, -1, -2 ] ] +], +"cur_uid": "6a9928d6", +"ref_uid": "55f9b81e", +"order_seed": 963124, +"dur_seed": 397683, +"motifs_seed": 845264, +"entrances_probs_vals": [ 0, 0, 0, 0.41, 1.8406593406593, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 0.41, 1.8406593406593, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 0.41, 1.8406593406593, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2430, -293.49845201238 ], [ -869, 1211.1455108359 ], [ -832.19814241486, 1285 ], [ -702, 1211.1455108359 ] ], +"step_probs_vals": [ -1200, 1200, 0.0020576131687243, 0.068181818181818, 0.074074074074074, 0.0625, 0.20576131687243, 0.0625, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61316872427983, 0, 0.98971193415638, 0 ], +"passages_weights": [ 0.48, 0.46, 0.48, 1, 1 ], +"hd_exp": 1.52, +"hd_invert": 0, +"order": +[ + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 3 ], [ 0, 2, 1 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 1 ], [ 2, 3, 0 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ], + [ [ 1 ], [ 3, 2, 0 ], [ ] ], + [ [ 3, 0 ], [ 1, 2 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 0, 3 ], [ 1, 2 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 0, 2 ], [ 1, 3 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 1 ], [ 0, 2, 3 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 2 ], [ 3, 1, 0 ], [ ] ], + [ [ 0, 2 ], [ 3, 1 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 0 ], [ 1, 2, 3 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 1 ], [ 0, 2, 3 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 1 ], [ 3, 2, 0 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 3, 0 ], [ 1, 2 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 2, 1 ], [ 0, 3 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 1 ], [ 0, 2, 3 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 0, 2 ], [ 3, 1 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 2 ], [ 3, 1, 0 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 100, 100 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/6f0f638f/6f0f638f_code.scd b/resources/string_quartet_3/6f0f638f/6f0f638f_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_3/6f0f638f/6f0f638f_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/string_quartet_3/6f0f638f/6f0f638f_mus_model.json b/resources/string_quartet_3/6f0f638f/6f0f638f_mus_model.json new file mode 100644 index 0000000..e65877d --- /dev/null +++ b/resources/string_quartet_3/6f0f638f/6f0f638f_mus_model.json @@ -0,0 +1,105 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, -1, -1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ 0, 0, 0, -1, -1, 0 ], [ "Rest" ], [ 1, 0, 0, -1, -1, 1 ], [ "Rest" ] ], 0 ], + [ [ [ 0, 0, 0, -1, -1, 0 ], [ "Rest" ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 2 ] ], 1.25 ], + [ [ [ 0, 0, 0, -1, -1, 0 ], [ 0, 0, 0, -1, -2, 2 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 2 ] ], 1.5 ], + [ [ [ 0, 0, 0, -1, -1, 0 ], [ 0, -1, 0, -1, -1, 2 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 2 ] ], 2.625 ] + ], + [ + [ [ [ 0, 0, 0, -1, -1, 0 ], [ "Rest" ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 2 ] ], 0 ], + [ [ [ -1, 0, -1, -1, -1, 2 ], [ "Rest" ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 2 ] ], 0 ], + [ [ [ -1, 0, -1, -1, -1, 2 ], [ "Rest" ], [ 1, -1, 0, -1, -1, 2 ], [ 1, 0, 0, -1, -1, 2 ] ], 1.25 ], + [ [ [ -1, 0, -1, -1, -1, 2 ], [ "Rest" ], [ 1, 0, 0, -1, -2, 2 ], [ 1, 0, 0, -1, -1, 2 ] ], 0.75 ], + [ [ [ -1, 0, -1, -1, -1, 2 ], [ "Rest" ], [ 0, 0, 0, -1, 0, 2 ], [ 1, 0, 0, -1, -1, 2 ] ], 1.5 ] + ], + [ + [ [ [ -1, 0, -1, -1, -1, 2 ], [ "Rest" ], [ 0, 0, 0, -1, 0, 2 ], [ "Rest" ] ], 0 ], + [ [ [ -1, 0, 0, -1, 0, 1 ], [ "Rest" ], [ 0, 0, 0, -1, 0, 2 ], [ "Rest" ] ], 0 ], + [ [ [ -1, 0, 0, -1, 0, 1 ], [ -1, 0, 0, -1, 0, 2 ], [ 0, 0, 0, -1, 0, 2 ], [ "Rest" ] ], 1.125 ], + [ [ [ -2, 0, 1, -1, 0, 2 ], [ -1, 0, 0, -1, 0, 2 ], [ 0, 0, 0, -1, 0, 2 ], [ "Rest" ] ], 2 ], + [ [ [ -1, 0, 0, -2, 0, 2 ], [ -1, 0, 0, -1, 0, 2 ], [ 0, 0, 0, -1, 0, 2 ], [ "Rest" ] ], 0.875 ], + [ [ [ 0, 0, 0, -2, 0, 2 ], [ -1, 0, 0, -1, 0, 2 ], [ 0, 0, 0, -1, 0, 2 ], [ "Rest" ] ], 0 ], + [ [ [ 0, 0, 0, -2, 0, 2 ], [ -1, 0, 1, -1, 0, 2 ], [ 0, 0, 0, -1, 0, 2 ], [ "Rest" ] ], 1.75 ] + ], + [ + [ [ [ 0, 0, 0, -2, 0, 2 ], [ -1, 0, 1, -1, 0, 2 ], [ 0, 0, 0, -1, 0, 2 ], [ 1, 0, 0, -1, -1, 2 ] ], 3 ], + [ [ [ 0, 0, 0, -2, 0, 2 ], [ -1, 0, 1, -1, 0, 2 ], [ 0, 0, 1, -1, -1, 2 ], [ 1, 0, 0, -1, -1, 2 ] ], 0 ], + [ [ [ 0, 0, 0, -2, 0, 2 ], [ 0, 0, -1, -1, -1, 2 ], [ 0, 0, 1, -1, -1, 2 ], [ 1, 0, 0, -1, -1, 2 ] ], 0.875 ], + [ [ [ 0, 0, 0, -2, 0, 2 ], [ -1, 1, 0, -1, -1, 2 ], [ 0, 0, 1, -1, -1, 2 ], [ 1, 0, 0, -1, -1, 2 ] ], 0 ], + [ [ [ 0, 0, 0, -2, 0, 2 ], [ -1, 1, 0, -1, -1, 2 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 2 ] ], 1.625 ], + [ [ [ 0, 0, 0, -2, 0, 2 ], [ -1, 0, 0, -1, 0, 2 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 2 ] ], 0 ], + [ [ [ 0, 0, 0, -2, 0, 2 ], [ -1, 0, 0, -1, 0, 2 ], [ 1, 0, 0, -2, -1, 2 ], [ 1, 0, 0, -1, -1, 2 ] ], 1.125 ] + ], + [ + [ [ [ 0, 0, 0, -2, 0, 2 ], [ -1, 0, 0, -1, 0, 2 ], [ -1, 0, 0, -1, 0, 3 ], [ 1, 0, 0, -1, -1, 2 ] ], 0 ], + [ [ [ 0, 0, 0, -1, 0, 1 ], [ -1, 0, 0, -1, 0, 2 ], [ -1, 0, 0, -1, 0, 3 ], [ 1, 0, 0, -1, -1, 2 ] ], 1.125 ], + [ [ [ 0, -1, 0, -1, 0, 2 ], [ -1, 0, 0, -1, 0, 2 ], [ -1, 0, 0, -1, 0, 3 ], [ 1, 0, 0, -1, -1, 2 ] ], 2.5 ] + ], + [ + [ [ [ 0, -1, 0, -1, 0, 2 ], [ -1, 0, 0, -1, 0, 2 ], [ -1, 0, 0, -1, 0, 3 ], [ -1, 0, 1, -1, 0, 3 ] ], 1.125 ], + [ [ [ 0, -1, 0, -1, 0, 2 ], [ -1, 0, 0, -1, 0, 2 ], [ -1, 0, 0, -1, 0, 3 ], [ 1, 0, 0, -2, 0, 2 ] ], 3 ] + ], + [ + [ [ [ 0, -1, 0, -1, 0, 2 ], [ -1, 0, 0, -1, 0, 2 ], [ -1, 0, 0, -1, 0, 3 ], [ "Rest" ] ], 0 ], + [ [ [ 0, -1, 0, -1, 0, 2 ], [ 0, -1, 0, -1, -1, 2 ], [ -1, 0, 0, -1, 0, 3 ], [ "Rest" ] ], 1.375 ], + [ [ [ 0, -1, 0, -1, 0, 2 ], [ 0, -2, 0, -1, 0, 2 ], [ -1, 0, 0, -1, 0, 3 ], [ "Rest" ] ], 2.5 ], + [ [ [ 0, -1, 0, -1, 0, 2 ], [ -1, -1, 1, -1, 0, 2 ], [ -1, 0, 0, -1, 0, 3 ], [ "Rest" ] ], 1 ], + [ [ [ 0, -1, 0, -1, 0, 2 ], [ -2, 0, 0, -1, 0, 3 ], [ -1, 0, 0, -1, 0, 3 ], [ "Rest" ] ], 2.125 ], + [ [ [ 0, -1, 0, -1, 0, 2 ], [ 0, -1, 0, -2, 0, 2 ], [ -1, 0, 0, -1, 0, 3 ], [ "Rest" ] ], 1.375 ] + ], + [ + [ [ [ -2, 1, 0, -1, 0, 3 ], [ 0, -1, 0, -2, 0, 2 ], [ -1, 0, 0, -1, 0, 3 ], [ "Rest" ] ], 1.625 ], + [ [ [ -2, 1, 0, -1, 0, 3 ], [ 0, -1, 0, -2, 0, 2 ], [ -1, 0, 0, -1, 0, 3 ], [ 0, -1, 0, -1, 0, 3 ] ], 0.5 ], + [ [ [ -2, 1, 0, -1, 0, 3 ], [ -1, 0, 1, -1, 0, 3 ], [ -1, 0, 0, -1, 0, 3 ], [ 0, -1, 0, -1, 0, 3 ] ], 0 ], + [ [ [ -2, 1, 0, -1, 0, 3 ], [ -1, 0, 1, -1, 0, 3 ], [ -1, 0, 0, -1, 0, 3 ], [ 0, 0, 0, -1, -1, 3 ] ], 2.5 ], + [ [ [ -2, 1, 0, -1, 0, 3 ], [ 0, -1, 0, -1, 0, 3 ], [ -1, 0, 0, -1, 0, 3 ], [ 0, 0, 0, -1, -1, 3 ] ], 0 ], + [ [ [ -2, 0, 0, -1, 1, 3 ], [ 0, -1, 0, -1, 0, 3 ], [ -1, 0, 0, -1, 0, 3 ], [ 0, 0, 0, -1, -1, 3 ] ], 1.125 ], + [ [ [ -2, 0, 0, -1, 1, 3 ], [ 0, -1, 0, -1, 0, 3 ], [ -1, 0, 0, -1, 0, 3 ], [ "Rest" ] ], 0 ], + [ [ [ -2, 0, 0, -1, 1, 3 ], [ "Rest" ], [ -1, 0, 0, -1, 0, 3 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ -1, 0, 0, -1, 0, 3 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0.875 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 1, 0, -1, 0, 3 ], [ 0, -1, 0, -2, 0, 2 ], [ -1, 0, 0, -1, 0, 3 ], [ 0, -1, 0, -1, 0, 3 ] ], + [ [ -2, 1, 0, -1, 0, 3 ], [ -1, 0, 1, -1, 0, 3 ], [ -1, 0, 0, -1, 0, 3 ], [ 0, -1, 0, -1, 0, 3 ] ], + [ [ -2, 1, 0, -1, 0, 3 ], [ -1, 0, 1, -1, 0, 3 ], [ -1, 0, 0, -1, 0, 3 ], [ 0, 0, 0, -1, -1, 3 ] ], + [ [ -2, 1, 0, -1, 0, 3 ], [ 0, -1, 0, -1, 0, 3 ], [ -1, 0, 0, -1, 0, 3 ], [ 0, 0, 0, -1, -1, 3 ] ], + [ [ -2, 0, 0, -1, 1, 3 ], [ 0, -1, 0, -1, 0, 3 ], [ -1, 0, 0, -1, 0, 3 ], [ 0, 0, 0, -1, -1, 3 ] ] +], +"cur_uid": "6f0f638f", +"ref_uid": "781442dc", +"order_seed": 778672, +"dur_seed": 554838, +"motifs_seed": 420644, +"entrances_probs_vals": [ 0.76, 0, 0, 0.38461538461538, 3.08, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0.76, 0, 0, 0.38461538461538, 3.08, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0.76, 0, 0, 0.38461538461538, 3.08, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1984, 932.50773993808 ], [ -999, 951.08359133127 ], [ -15, 1043.9628482972 ], [ 78, 1081.1145510836 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.076131687242798, 0.91477272727273, 0.17489711934156, 0, 0.57818930041152, 0, 1, 0 ], +"passages_weights": [ 1, 1, 1, 1, 1 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 0, 2, 3 ], [ 1, 1 ], [ ] ], + [ [ 3 ], [ 0, 2, 2, 2 ], [ 1 ] ], + [ [ 2 ], [ 0, 1, 0, 0, 0, 1 ], [ 3 ] ], + [ [ 0, 3 ], [ 2, 1, 1, 2, 1, 2 ], [ ] ], + [ [ 1, 3 ], [ 2, 0, 0 ], [ ] ], + [ [ 2, 1, 0 ], [ 3, 3 ], [ ] ], + [ [ 2, 0 ], [ 1, 1, 1, 1, 1 ], [ 3 ] ], + [ [ 2 ], [ 0, 3, 1, 3, 1, 0 ], [ ] ] +], +"sus_weights": [ 1, 1, 1 ], +"order_size": [ 5, 10 ], +"passages_size": [ 0, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/726a40c7/726a40c7_code.scd b/resources/string_quartet_3/726a40c7/726a40c7_code.scd new file mode 100644 index 0000000..f822edb --- /dev/null +++ b/resources/string_quartet_3/726a40c7/726a40c7_code.scd @@ -0,0 +1,946 @@ +( +// 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 = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + if(pDistance < 0, {stepFunc.value(abs(pDistance))}, {0.001}); + //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/string_quartet_3/726a40c7/726a40c7_mus_model.json b/resources/string_quartet_3/726a40c7/726a40c7_mus_model.json new file mode 100644 index 0000000..06e11db --- /dev/null +++ b/resources/string_quartet_3/726a40c7/726a40c7_mus_model.json @@ -0,0 +1,163 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 0, -1, 0, 2, 1, -2 ], [ "Rest" ], [ "Rest" ] ], 0.5 ], + [ [ [ -1, 0, 1, 2, 0, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ "Rest" ], [ "Rest" ] ], 0.625 ], + [ [ [ -1, 0, 1, 2, 0, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ -1, -1, 0, 3, 1, -2 ], [ "Rest" ] ], 0.25 ], + [ [ [ -1, 0, 1, 2, 0, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ -1, -1, 0, 3, 1, -2 ], [ -1, -1, 0, 3, 1, -1 ] ], 0.875 ] + ], + [ + [ [ [ 0, -1, 0, 2, 0, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ -1, -1, 0, 3, 1, -2 ], [ -1, -1, 0, 3, 1, -1 ] ], 1 ] + ], + [ + [ [ [ 0, -1, 0, 2, 0, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ -2, -1, 1, 3, 1, -1 ], [ -1, -1, 0, 3, 1, -1 ] ], 0.375 ] + ], + [ + [ [ [ 0, -1, 0, 2, 0, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ -2, -1, 1, 3, 1, -1 ], [ -2, -1, 1, 3, 1, 0 ] ], 0.625 ] + ], + [ + [ [ [ 0, -1, 0, 2, 0, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ -2, -1, 1, 3, 1, -1 ], [ 1, -1, 0, 2, 0, -2 ] ], 1.125 ] + ], + [ + [ [ [ 0, -1, 0, 2, 0, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ -2, -1, 1, 3, 1, -1 ], [ 0, 0, 0, 2, 1, -2 ] ], 0.125 ] + ], + [ + [ [ [ 0, -1, 0, 2, 0, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ -2, -1, 1, 3, 1, -1 ], [ 0, -1, 0, 2, 1, -1 ] ], 0.75 ] + ], + [ + [ [ [ -1, 0, 0, 2, 1, -2 ], [ 0, -1, 0, 2, 1, -2 ], [ -2, -1, 1, 3, 1, -1 ], [ 0, -1, 0, 2, 1, -1 ] ], 0.625 ] + ], + [ + [ [ [ -1, 0, 0, 2, 1, -2 ], [ 0, -2, 0, 2, 1, -1 ], [ -2, -1, 1, 3, 1, -1 ], [ 0, -1, 0, 2, 1, -1 ] ], 0.875 ] + ], + [ + [ [ [ -1, 0, 0, 2, 1, -2 ], [ 0, -2, 0, 2, 1, -1 ], [ -2, -1, 1, 3, 1, -1 ], [ 1, -2, -1, 2, 1, -1 ] ], 1.125 ] + ], + [ + [ [ [ -1, 0, 0, 2, 1, -2 ], [ 0, -2, 0, 2, 1, -1 ], [ -1, 0, 1, 2, 1, -2 ], [ 1, -2, -1, 2, 1, -1 ] ], 0.375 ] + ], + [ + [ [ [ -1, 0, 0, 2, 1, -2 ], [ 0, -2, 0, 2, 1, -1 ], [ -1, 0, 1, 2, 1, -2 ], [ 1, 0, 0, 2, 1, -3 ] ], 0.875 ] + ], + [ + [ [ [ -1, 0, 0, 2, 1, -2 ], [ 0, -2, 0, 2, 1, -1 ], [ -1, 0, 1, 2, 1, -2 ], [ 0, 0, 1, 2, 1, -2 ] ], 0.5 ] + ], + [ + [ [ [ 0, -2, 0, 2, 0, -1 ], [ 0, -2, 0, 2, 1, -1 ], [ -1, 0, 1, 2, 1, -2 ], [ 0, 0, 1, 2, 1, -2 ] ], 0.75 ] + ], + [ + [ [ [ 0, -2, 0, 2, 0, -1 ], [ 0, 0, 1, 2, 1, -3 ], [ -1, 0, 1, 2, 1, -2 ], [ 0, 0, 1, 2, 1, -2 ] ], 1.125 ] + ], + [ + [ [ [ -2, 0, 1, 3, 1, -2 ], [ 0, 0, 1, 2, 1, -3 ], [ -1, 0, 1, 2, 1, -2 ], [ 0, 0, 1, 2, 1, -2 ] ], 0.625 ] + ], + [ + [ [ [ -2, 0, 1, 3, 1, -2 ], [ -1, 0, 1, 3, 0, -2 ], [ -1, 0, 1, 2, 1, -2 ], [ 0, 0, 1, 2, 1, -2 ] ], 0.5 ] + ], + [ + [ [ [ -2, 0, 1, 3, 1, -2 ], [ -1, 0, 1, 3, 0, -2 ], [ -1, 0, 0, 3, 0, -2 ], [ 0, 0, 1, 2, 1, -2 ] ], 1.125 ] + ], + [ + [ [ [ -2, 0, 1, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -1, 0, 0, 3, 0, -2 ], [ 0, 0, 1, 2, 1, -2 ] ], 0.125 ] + ], + [ + [ [ [ -2, 0, 1, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -2, 1, 0, 3, 1, -2 ], [ 0, 0, 1, 2, 1, -2 ] ], 0.125 ] + ], + [ + [ [ [ -3, 1, 0, 4, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -2, 1, 0, 3, 1, -2 ], [ 0, 0, 1, 2, 1, -2 ] ], 0.25 ] + ], + [ + [ [ [ -3, 2, 1, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -2, 1, 0, 3, 1, -2 ], [ 0, 0, 1, 2, 1, -2 ] ], 0.5 ] + ], + [ + [ [ [ -2, 0, 2, 2, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -2, 1, 0, 3, 1, -2 ], [ 0, 0, 1, 2, 1, -2 ] ], 0.875 ] + ], + [ + [ [ [ -2, 0, 2, 2, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -2, 1, 0, 3, 1, -2 ], [ -1, 1, 0, 3, 1, -2 ] ], 0.75 ] + ], + [ + [ [ [ -2, 1, 0, 3, 1, -3 ], [ -2, 1, 1, 3, 1, -2 ], [ -2, 1, 0, 3, 1, -2 ], [ -1, 1, 0, 3, 1, -2 ] ], 0 ] + ], + [ + [ [ [ -3, 1, 1, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -2, 1, 0, 3, 1, -2 ], [ -1, 1, 0, 3, 1, -2 ] ], 0.5 ] + ], + [ + [ [ [ -2, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -2, 1, 0, 3, 1, -2 ], [ -1, 1, 0, 3, 1, -2 ] ], 0.75 ] + ], + [ + [ [ [ -2, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -1, 0, -1, 3, 1, -2 ], [ -1, 1, 0, 3, 1, -2 ] ], 0.875 ] + ], + [ + [ [ [ -2, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -1, 0, -1, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -1 ] ], 0.75 ] + ], + [ + [ [ [ -2, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -3, 1, 1, 4, 1, -2 ], [ -2, 1, 1, 3, 1, -1 ] ], 0.125 ], + [ [ [ -2, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -3, 1, 1, 4, 1, -2 ], [ "Rest" ] ], 1 ], + [ [ [ -2, 0, 0, 3, 1, -2 ], [ "Rest" ], [ -3, 1, 1, 4, 1, -2 ], [ "Rest" ] ], 0.875 ], + [ [ [ "Rest" ], [ "Rest" ], [ -3, 1, 1, 4, 1, -2 ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1.75 ] + ] + ] +], +"last_changes": +[ + [ [ -3, 1, 1, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -2, 1, 0, 3, 1, -2 ], [ -1, 1, 0, 3, 1, -2 ] ], + [ [ -2, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -2, 1, 0, 3, 1, -2 ], [ -1, 1, 0, 3, 1, -2 ] ], + [ [ -2, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -1, 0, -1, 3, 1, -2 ], [ -1, 1, 0, 3, 1, -2 ] ], + [ [ -2, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -1, 0, -1, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -1 ] ], + [ [ -2, 0, 0, 3, 1, -2 ], [ -2, 1, 1, 3, 1, -2 ], [ -3, 1, 1, 4, 1, -2 ], [ -2, 1, 1, 3, 1, -1 ] ] +], +"cur_uid": "726a40c7", +"ref_uid": "5ec14635", +"order_seed": 439818, +"dur_seed": 544932, +"motifs_seed": 109123, +"entrances_probs_vals": [ 0, 0, 0, 0, 1.2087912087912, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 0, 1.2087912087912, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 0, 1.2087912087912, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2170, 338 ], [ -2373.9938080495, 1453 ], [ -1650, 1676 ], [ -1370.8978328173, 1694 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 1, 0, 0.2, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ] +], +"sus_weights": [ 0, 0, 0.61 ], +"order_size": [ 30, 30 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/75316bf0/75316bf0_code.scd b/resources/string_quartet_3/75316bf0/75316bf0_code.scd new file mode 100644 index 0000000..49436ca --- /dev/null +++ b/resources/string_quartet_3/75316bf0/75316bf0_code.scd @@ -0,0 +1,973 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + + + //isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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/string_quartet_3/75316bf0/75316bf0_mus_model.json b/resources/string_quartet_3/75316bf0/75316bf0_mus_model.json new file mode 100644 index 0000000..e03d000 --- /dev/null +++ b/resources/string_quartet_3/75316bf0/75316bf0_mus_model.json @@ -0,0 +1,550 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 2, -1, -2, 2, -3, 0 ], [ "Rest" ], [ "Rest" ] ], 0.625 ], + [ [ [ "Rest" ], [ 2, -1, -2, 2, -3, 0 ], [ 2, -1, -2, 1, -3, 0 ], [ "Rest" ] ], 1 ], + [ [ [ "Rest" ], [ 2, -1, -2, 2, -3, 0 ], [ 2, -1, -2, 1, -3, 0 ], [ 1, 0, -2, 2, -3, 0 ] ], 1.5 ], + [ [ [ 1, -2, -2, 2, -3, 0 ], [ 2, -1, -2, 2, -3, 0 ], [ 2, -1, -2, 1, -3, 0 ], [ 1, 0, -2, 2, -3, 0 ] ], 1.875 ] + ], + [ + [ [ [ 1, -2, -2, 2, -3, 0 ], [ 2, -1, -2, 2, -3, 0 ], [ 2, -1, -2, 1, -3, 0 ], [ 2, -2, -1, 2, -3, 0 ] ], 1.625 ], + [ [ [ 1, -2, -2, 2, -3, 0 ], [ 3, -2, -2, 1, -3, 0 ], [ 2, -1, -2, 1, -3, 0 ], [ 2, -2, -1, 2, -3, 0 ] ], 1.5 ] + ], + [ + [ [ [ 1, -2, -2, 2, -3, 0 ], [ 3, -2, -2, 1, -3, 0 ], [ 2, -3, -2, 2, -3, 0 ], [ 2, -2, -1, 2, -3, 0 ] ], 1.5 ], + [ [ [ 1, -2, -2, 2, -3, 0 ], [ 3, -2, -2, 1, -3, 0 ], [ 2, -3, -2, 2, -3, 0 ], [ 3, -3, -2, 2, -3, 0 ] ], 0.875 ] + ], + [ + [ [ [ 2, -3, -3, 2, -3, 0 ], [ 3, -2, -2, 1, -3, 0 ], [ 2, -3, -2, 2, -3, 0 ], [ 3, -3, -2, 2, -3, 0 ] ], 1.125 ], + [ [ [ 2, -3, -3, 2, -3, 0 ], [ 3, -2, -2, 1, -3, 0 ], [ 3, -3, -2, 1, -3, 0 ], [ 3, -3, -2, 2, -3, 0 ] ], 1.75 ] + ], + [ + [ [ [ 2, -3, -3, 2, -3, 0 ], [ 3, -2, -2, 1, -3, 0 ], [ 3, -3, -2, 1, -3, 0 ], [ 4, -4, -3, 2, -3, 0 ] ], 1.5 ], + [ [ [ 2, -3, -3, 2, -3, 0 ], [ 4, -3, -3, 1, -3, 0 ], [ 3, -3, -2, 1, -3, 0 ], [ 4, -4, -3, 2, -3, 0 ] ], 1.5 ] + ], + [ + [ [ [ 2, -3, -3, 2, -3, 0 ], [ 3, -3, -2, 2, -3, 0 ], [ 3, -3, -2, 1, -3, 0 ], [ 4, -4, -3, 2, -3, 0 ] ], 1.75 ], + [ [ [ 2, -3, -3, 2, -3, 0 ], [ 3, -3, -2, 2, -3, 0 ], [ 3, -4, -3, 2, -3, 0 ], [ 4, -4, -3, 2, -3, 0 ] ], 1.5 ] + ], + [ + [ [ [ 2, -3, -3, 2, -3, 0 ], [ 3, -3, -2, 2, -3, 0 ], [ 2, -2, -3, 2, -3, 0 ], [ 4, -4, -3, 2, -3, 0 ] ], 0.5 ] + ], + [ + [ [ [ 2, -3, -3, 2, -3, 0 ], [ 4, -4, -4, 2, -3, 0 ], [ 2, -2, -3, 2, -3, 0 ], [ 4, -4, -3, 2, -3, 0 ] ], 0.5 ], + [ [ [ 2, -3, -3, 2, -3, 0 ], [ 4, -4, -4, 2, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 4, -4, -3, 2, -3, 0 ] ], 0.75 ], + [ [ [ 3, -4, -4, 2, -3, 0 ], [ 4, -4, -4, 2, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 4, -4, -3, 2, -3, 0 ] ], 0.625 ] + ], + [ + [ [ [ 3, -4, -4, 2, -3, 0 ], [ 4, -4, -4, 2, -3, 0 ], [ 3, -5, -3, 2, -3, 0 ], [ 4, -4, -3, 2, -3, 0 ] ], 0.625 ] + ], + [ + [ [ [ 1, -3, -4, 2, -3, 0 ], [ 4, -4, -4, 2, -3, 0 ], [ 3, -5, -3, 2, -3, 0 ], [ 4, -4, -3, 2, -3, 0 ] ], 1.25 ], + [ [ [ 1, -3, -4, 2, -3, 0 ], [ 4, -4, -4, 2, -3, 0 ], [ 2, -4, -4, 3, -3, 0 ], [ 4, -4, -3, 2, -3, 0 ] ], 1.375 ], + [ [ [ 1, -3, -4, 2, -3, 0 ], [ 4, -4, -4, 2, -3, 0 ], [ 2, -4, -4, 3, -3, 0 ], [ 5, -4, -4, 1, -3, 0 ] ], 0.75 ] + ], + [ + [ [ [ 1, -3, -4, 2, -3, 0 ], [ 4, -4, -4, 2, -3, 0 ], [ 3, -4, -4, 2, -3, 0 ], [ 5, -4, -4, 1, -3, 0 ] ], 1.375 ] + ], + [ + [ [ [ 1, -3, -4, 2, -3, 0 ], [ 4, -4, -4, 2, -3, 0 ], [ 3, -4, -4, 2, -3, 0 ], [ 4, -3, -5, 2, -3, 0 ] ], 1.5 ], + [ [ [ 1, -3, -4, 2, -3, 0 ], [ 3, -2, -4, 2, -3, 0 ], [ 3, -4, -4, 2, -3, 0 ], [ 4, -3, -5, 2, -3, 0 ] ], 0.5 ] + ], + [ + [ [ [ 1, -3, -4, 2, -3, 0 ], [ 3, -2, -4, 2, -3, 0 ], [ 3, -4, -4, 2, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 1.75 ], + [ [ [ 1, -3, -4, 2, -3, 0 ], [ 3, -2, -4, 2, -3, 0 ], [ 2, -2, -4, 2, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 1.625 ] + ], + [ + [ [ [ 1, -2, -4, 2, -4, 0 ], [ 3, -2, -4, 2, -3, 0 ], [ 2, -2, -4, 2, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 0.625 ] + ], + [ + [ [ [ 1, -2, -4, 2, -4, 0 ], [ 1, -2, -4, 3, -3, 0 ], [ 2, -2, -4, 2, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 0.875 ], + [ [ [ 1, -4, -4, 3, -3, 0 ], [ 1, -2, -4, 3, -3, 0 ], [ 2, -2, -4, 2, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 1.625 ] + ], + [ + [ [ [ 1, -4, -4, 3, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 2, -2, -4, 2, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 1.625 ] + ], + [ + [ [ [ 1, -4, -4, 3, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 2, -3, -4, 3, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 0.5 ] + ], + [ + [ [ [ 1, -4, -4, 3, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 2, -4, -4, 3, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 0.75 ] + ], + [ + [ [ [ 1, -4, -4, 3, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 1, -4, -3, 4, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 0.75 ], + [ [ [ 0, -4, -3, 4, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 1, -4, -3, 4, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 1.25 ], + [ [ [ 0, -4, -3, 4, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 1, -4, -3, 4, -3, 0 ], [ 3, -4, -3, 3, -3, 0 ] ], 1.25 ] + ], + [ + [ [ [ 0, -4, -3, 4, -3, 0 ], [ 3, -4, -3, 2, -3, 0 ], [ 1, -4, -3, 4, -3, 0 ], [ 3, -4, -3, 3, -3, 0 ] ], 1 ], + [ [ [ 0, -4, -3, 4, -3, 0 ], [ 3, -4, -3, 2, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 3, -4, -3, 3, -3, 0 ] ], 0.375 ], + [ [ [ 1, -4, -3, 3, -3, 0 ], [ 3, -4, -3, 2, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 3, -4, -3, 3, -3, 0 ] ], 1.375 ] + ], + [ + [ [ [ 1, -4, -3, 3, -3, 0 ], [ 3, -4, -3, 2, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 4, -4, -3, 2, -3, 0 ] ], 1.625 ], + [ [ [ 2, -4, -3, 2, -3, 0 ], [ 3, -4, -3, 2, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 4, -4, -3, 2, -3, 0 ] ], 1.625 ] + ], + [ + [ [ [ 2, -4, -3, 2, -3, 0 ], [ 3, -4, -3, 2, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 3, -4, -3, 2, -3, 1 ] ], 1.625 ], + [ [ [ 2, -4, -3, 2, -3, 0 ], [ 3, -4, -3, 2, -3, 0 ], [ 3, -5, -3, 2, -3, 0 ], [ 3, -4, -3, 2, -3, 1 ] ], 1.75 ] + ], + [ + [ [ [ 2, -4, -3, 2, -3, 0 ], [ 3, -4, -3, 2, -3, 0 ], [ 2, -3, -3, 2, -3, 0 ], [ 3, -4, -3, 2, -3, 1 ] ], 1.75 ], + [ [ [ 2, -4, -3, 2, -3, 0 ], [ 3, -4, -3, 2, -3, 0 ], [ 2, -3, -3, 2, -3, 0 ], [ 3, -4, -3, 3, -3, 0 ] ], 1.75 ] + ], + [ + [ [ [ 2, -4, -3, 2, -3, 0 ], [ 2, -2, -3, 2, -3, 0 ], [ 2, -3, -3, 2, -3, 0 ], [ 3, -4, -3, 3, -3, 0 ] ], 1 ] + ], + [ + [ [ [ 2, -5, -3, 3, -3, 0 ], [ 2, -2, -3, 2, -3, 0 ], [ 2, -3, -3, 2, -3, 0 ], [ 3, -4, -3, 3, -3, 0 ] ], 1.625 ], + [ [ [ 2, -5, -3, 3, -3, 0 ], [ 2, -2, -3, 2, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 3, -4, -3, 3, -3, 0 ] ], 1.75 ], + [ [ [ 2, -5, -3, 3, -3, 0 ], [ 2, -3, -3, 3, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 3, -4, -3, 3, -3, 0 ] ], 1.625 ] + ], + [ + [ [ [ 1, -3, -3, 3, -3, 0 ], [ 2, -3, -3, 3, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 3, -4, -3, 3, -3, 0 ] ], 1 ], + [ [ [ 1, -3, -3, 3, -3, 0 ], [ 2, -3, -3, 3, -3, 0 ], [ 2, -4, -3, 3, -3, 0 ], [ 2, -2, -3, 3, -3, 0 ] ], 1 ], + [ [ [ 1, -3, -3, 3, -3, 0 ], [ 2, -3, -3, 3, -3, 0 ], [ 1, -2, -3, 3, -3, 0 ], [ 2, -2, -3, 3, -3, 0 ] ], 1.25 ] + ], + [ + [ [ [ 1, -3, -3, 3, -3, 0 ], [ 2, -3, -3, 3, -3, 0 ], [ 2, -3, -4, 3, -3, 0 ], [ 2, -2, -3, 3, -3, 0 ] ], 0.875 ], + [ [ [ 1, -3, -3, 3, -3, 0 ], [ 2, -3, -3, 3, -3, 0 ], [ 2, -3, -4, 3, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 1.625 ], + [ [ [ 1, -3, -4, 3, -3, 0 ], [ 2, -3, -3, 3, -3, 0 ], [ 2, -3, -4, 3, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 0.875 ] + ], + [ + [ [ [ 1, -3, -4, 3, -3, 0 ], [ 2, -3, -3, 3, -3, 0 ], [ 2, -4, -4, 3, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 0.75 ], + [ [ [ 1, -3, -4, 3, -3, 0 ], [ 3, -4, -4, 3, -3, 0 ], [ 2, -4, -4, 3, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 0.5 ], + [ [ [ 1, -3, -4, 3, -3, 0 ], [ 3, -4, -4, 3, -3, 0 ], [ 2, -4, -4, 3, -3, 0 ], [ 2, -3, -4, 4, -3, 0 ] ], 1.125 ] + ], + [ + [ [ [ 1, -3, -4, 3, -3, 0 ], [ 3, -4, -4, 3, -3, 0 ], [ 1, -2, -4, 3, -3, 0 ], [ 2, -3, -4, 4, -3, 0 ] ], 1.625 ] + ], + [ + [ [ [ 1, -3, -4, 3, -3, 0 ], [ 2, -2, -4, 3, -3, 0 ], [ 1, -2, -4, 3, -3, 0 ], [ 2, -3, -4, 4, -3, 0 ] ], 1.75 ] + ], + [ + [ [ [ 1, -3, -4, 3, -3, 0 ], [ 2, -2, -4, 3, -3, 0 ], [ 1, -2, -4, 3, -3, 0 ], [ 2, -2, -3, 3, -3, 0 ] ], 1.75 ], + [ [ [ 1, -3, -4, 3, -3, 0 ], [ 2, -2, -4, 3, -3, 0 ], [ 2, -3, -5, 3, -3, 0 ], [ 2, -2, -3, 3, -3, 0 ] ], 0.75 ] + ], + [ + [ [ [ 1, -3, -4, 3, -3, 0 ], [ 2, -2, -4, 3, -3, 0 ], [ 2, -3, -5, 3, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 0.375 ], + [ [ [ 1, -3, -4, 3, -3, 0 ], [ 2, -3, -4, 3, -3, 0 ], [ 2, -3, -5, 3, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 1.125 ] + ], + [ + [ [ [ 1, -3, -4, 3, -3, 0 ], [ 2, -3, -4, 3, -4, 0 ], [ 2, -3, -5, 3, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 0.5 ], + [ [ [ 1, -3, -4, 2, -3, 0 ], [ 2, -3, -4, 3, -4, 0 ], [ 2, -3, -5, 3, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 1.75 ], + [ [ [ 1, -3, -4, 2, -3, 0 ], [ 2, -3, -4, 3, -4, 0 ], [ 1, -3, -4, 4, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 1.25 ] + ], + [ + [ [ [ 1, -3, -4, 2, -3, 0 ], [ 0, -2, -4, 4, -3, 0 ], [ 1, -3, -4, 4, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 1 ], + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 0, -2, -4, 4, -3, 0 ], [ 1, -3, -4, 4, -3, 0 ], [ 3, -3, -4, 3, -3, 0 ] ], 1.75 ], + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 0, -2, -4, 4, -3, 0 ], [ 1, -3, -4, 4, -3, 0 ], [ 2, -3, -4, 4, -3, 0 ] ], 0.875 ] + ], + [ + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 0, -2, -4, 4, -3, 0 ], [ 0, -1, -4, 4, -3, 0 ], [ 2, -3, -4, 4, -3, 0 ] ], 1.375 ], + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 0, -2, -4, 4, -3, 0 ], [ 0, -1, -4, 4, -3, 0 ], [ 1, -1, -4, 4, -3, 0 ] ], 1.25 ] + ], + [ + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 1, -2, -4, 3, -3, 0 ], [ 0, -1, -4, 4, -3, 0 ], [ 1, -1, -4, 4, -3, 0 ] ], 0.625 ], + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 1, -2, -4, 3, -3, 0 ], [ 0, -1, -4, 4, -3, 0 ], [ 1, -2, -3, 4, -3, 0 ] ], 0.625 ], + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 1, -2, -4, 3, -3, 0 ], [ 0, -2, -4, 4, -3, 0 ], [ 1, -2, -3, 4, -3, 0 ] ], 0.5 ] + ], + [ + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 0, -2, -3, 4, -3, 0 ], [ 0, -2, -4, 4, -3, 0 ], [ 1, -2, -3, 4, -3, 0 ] ], 0.75 ], + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 0, -2, -3, 4, -3, 0 ], [ -1, -2, -3, 5, -3, 0 ], [ 1, -2, -3, 4, -3, 0 ] ], 1.125 ] + ], + [ + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 1, -3, -4, 4, -3, 0 ], [ -1, -2, -3, 5, -3, 0 ], [ 1, -2, -3, 4, -3, 0 ] ], 1.625 ], + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 1, -3, -4, 4, -3, 0 ], [ -1, -2, -3, 5, -3, 0 ], [ 2, -3, -4, 4, -3, 0 ] ], 0.875 ], + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 1, -3, -4, 4, -3, 0 ], [ 0, -2, -3, 4, -3, 0 ], [ 2, -3, -4, 4, -3, 0 ] ], 1.25 ] + ], + [ + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 1, -3, -4, 4, -3, 0 ], [ 1, -3, -4, 4, -4, 0 ], [ 2, -3, -4, 4, -3, 0 ] ], 1 ] + ], + [ + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 1, -3, -4, 4, -3, 0 ], [ 1, -3, -4, 4, -4, 0 ], [ 1, -1, -4, 4, -3, 0 ] ], 1.5 ] + ], + [ + [ [ [ -1, -2, -4, 4, -3, 0 ], [ 1, -2, -4, 4, -4, 0 ], [ 1, -3, -4, 4, -4, 0 ], [ 1, -1, -4, 4, -3, 0 ] ], 1 ], + [ [ [ 1, -3, -4, 3, -4, 0 ], [ 1, -2, -4, 4, -4, 0 ], [ 1, -3, -4, 4, -4, 0 ], [ 1, -1, -4, 4, -3, 0 ] ], 1.25 ], + [ [ [ 1, -3, -4, 3, -4, 0 ], [ 1, -2, -4, 4, -4, 0 ], [ 1, -3, -4, 4, -4, 0 ], [ 2, -2, -4, 4, -4, 0 ] ], 0.5 ] + ], + [ + [ [ [ 1, -3, -4, 3, -4, 0 ], [ 1, -2, -4, 4, -4, 0 ], [ 2, -3, -4, 3, -4, 0 ], [ 2, -2, -4, 4, -4, 0 ] ], 1 ] + ], + [ + [ [ [ 1, -3, -4, 3, -4, 0 ], [ 2, -2, -4, 3, -4, 0 ], [ 2, -3, -4, 3, -4, 0 ], [ 2, -2, -4, 4, -4, 0 ] ], 0.875 ], + [ [ [ 0, -2, -4, 3, -4, 0 ], [ 2, -2, -4, 3, -4, 0 ], [ 2, -3, -4, 3, -4, 0 ], [ 2, -2, -4, 4, -4, 0 ] ], 1.75 ] + ], + [ + [ [ [ 0, -2, -4, 3, -4, 0 ], [ 2, -2, -4, 3, -4, 0 ], [ 2, -3, -4, 3, -4, 0 ], [ 2, -1, -4, 3, -4, 0 ] ], 1.75 ], + [ [ [ 0, -2, -4, 3, -4, 0 ], [ 2, -2, -4, 3, -4, 0 ], [ 1, -1, -4, 3, -4, 0 ], [ 2, -1, -4, 3, -4, 0 ] ], 1.75 ], + [ [ [ 1, -2, -4, 3, -4, 0 ], [ 2, -2, -4, 3, -4, 0 ], [ 1, -1, -4, 3, -4, 0 ], [ 2, -1, -4, 3, -4, 0 ] ], 1.125 ] + ], + [ + [ [ [ 1, -2, -4, 3, -4, 0 ], [ 1, 0, -4, 3, -4, 0 ], [ 1, -1, -4, 3, -4, 0 ], [ 2, -1, -4, 3, -4, 0 ] ], 0.875 ] + ], + [ + [ [ [ 0, 0, -4, 3, -4, 0 ], [ 1, 0, -4, 3, -4, 0 ], [ 1, -1, -4, 3, -4, 0 ], [ 2, -1, -4, 3, -4, 0 ] ], 1.75 ], + [ [ [ 0, 0, -4, 3, -4, 0 ], [ 1, 0, -4, 3, -4, 0 ], [ 0, 1, -4, 3, -4, 0 ], [ 2, -1, -4, 3, -4, 0 ] ], 0.625 ] + ], + [ + [ [ [ -1, 1, -4, 3, -4, 0 ], [ 1, 0, -4, 3, -4, 0 ], [ 0, 1, -4, 3, -4, 0 ], [ 2, -1, -4, 3, -4, 0 ] ], 0.75 ], + [ [ [ -1, 1, -4, 3, -4, 0 ], [ 1, 0, -4, 3, -4, 0 ], [ 1, 0, -5, 3, -4, 0 ], [ 2, -1, -4, 3, -4, 0 ] ], 0.75 ] + ], + [ + [ [ [ -1, 1, -4, 3, -4, 0 ], [ 1, 0, -4, 3, -4, 0 ], [ 1, 0, -5, 3, -4, 0 ], [ 1, 1, -4, 3, -4, 0 ] ], 1.5 ], + [ [ [ -1, 1, -4, 3, -4, 0 ], [ 1, 0, -4, 3, -4, 0 ], [ 0, 0, -4, 4, -4, 0 ], [ 1, 1, -4, 3, -4, 0 ] ], 1.25 ], + [ [ [ -1, 0, -4, 3, -4, 0 ], [ 1, 0, -4, 3, -4, 0 ], [ 0, 0, -4, 4, -4, 0 ], [ 1, 1, -4, 3, -4, 0 ] ], 0.875 ] + ], + [ + [ [ [ -1, 0, -4, 4, -4, -1 ], [ 1, 0, -4, 3, -4, 0 ], [ 0, 0, -4, 4, -4, 0 ], [ 1, 1, -4, 3, -4, 0 ] ], 0.75 ], + [ [ [ -1, 0, -4, 4, -4, -1 ], [ 1, 0, -4, 3, -4, 0 ], [ 0, 0, -4, 4, -4, 0 ], [ 0, 1, -4, 4, -4, 0 ] ], 1 ], + [ [ [ -1, 0, -4, 4, -4, -1 ], [ 0, 0, -3, 4, -4, 0 ], [ 0, 0, -4, 4, -4, 0 ], [ 0, 1, -4, 4, -4, 0 ] ], 1.5 ] + ], + [ + [ [ [ -1, 0, -4, 4, -4, -1 ], [ 1, 0, -5, 4, -4, -1 ], [ 0, 0, -4, 4, -4, 0 ], [ 0, 1, -4, 4, -4, 0 ] ], 1.625 ], + [ [ [ -1, 0, -4, 4, -4, -1 ], [ 1, 0, -5, 4, -4, -1 ], [ 0, 0, -4, 4, -4, 0 ], [ 2, -1, -4, 4, -4, -1 ] ], 0.5 ], + [ [ [ -1, 0, -4, 4, -4, -1 ], [ 1, 0, -5, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 0.75 ] + ], + [ + [ [ [ -1, 0, -4, 4, -4, -1 ], [ 2, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 0.5 ], + [ [ [ 0, -1, -5, 4, -4, -1 ], [ 2, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1.375 ], + [ [ [ 0, -1, -5, 4, -4, -1 ], [ 2, -2, -4, 4, -4, -1 ], [ 1, -1, -5, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 0.625 ] + ], + [ + [ [ [ 0, -1, -5, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 1, -1, -5, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 0.625 ], + [ [ [ 0, -1, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 1, -1, -5, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1 ], + [ [ [ 0, -1, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 0, 0, -4, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1 ] + ], + [ + [ [ [ 0, -1, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 0, -1, -4, 5, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1 ], + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 0, -1, -4, 5, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1.375 ] + ], + [ + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -2, -4, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1.25 ], + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -2, -4, 4, -4, -1 ], [ 3, -3, -4, 4, -4, -1 ] ], 0.875 ] + ], + [ + [ [ [ 0, -3, -4, 4, -3, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -2, -4, 4, -4, -1 ], [ 3, -3, -4, 4, -4, -1 ] ], 0.875 ] + ], + [ + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -2, -4, 4, -4, -1 ], [ 3, -3, -4, 4, -4, -1 ] ], 0.875 ], + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -2, -4, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1.5 ], + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 1, 0, -4, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 0.875 ] + ], + [ + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -3, -4, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1.5 ], + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -3, -4, 4, -4, -1 ], [ 2, -2, -4, 4, -4, 0 ] ], 0.375 ] + ], + [ + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -3, -4, 4, -4, -1 ], [ 1, -1, -4, 5, -4, -1 ] ], 0.5 ], + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 0, 0, -4, 4, -4, -1 ], [ 1, -1, -4, 5, -4, -1 ] ], 0.75 ] + ], + [ + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 1, -1, -5, 4, -4, -1 ], [ 1, -1, -4, 5, -4, -1 ] ], 0.5 ] + ], + [ + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 1, -2, -4, 4, -3, -1 ], [ 1, -1, -4, 5, -4, -1 ] ], 0.5 ], + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 2, -2, -5, 4, -4, -1 ], [ 1, -2, -4, 4, -3, -1 ], [ 1, -1, -4, 5, -4, -1 ] ], 1.125 ], + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 2, -2, -5, 4, -4, -1 ], [ 1, -2, -4, 4, -3, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1.625 ] + ], + [ + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 2, -2, -5, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 0.875 ] + ], + [ + [ [ [ 0, -2, -4, 4, -4, -1 ], [ 0, 0, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1.375 ], + [ [ [ -1, 0, -4, 4, -4, -1 ], [ 0, 0, -4, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 0.875 ] + ], + [ + [ [ [ -1, 0, -4, 4, -4, -1 ], [ 1, -1, -5, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1.5 ], + [ [ [ -1, 0, -4, 4, -4, -1 ], [ 1, -1, -5, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 1, -1, -4, 5, -4, -1 ] ], 0.5 ], + [ [ [ 0, -1, -5, 4, -4, -1 ], [ 1, -1, -5, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 1, -1, -4, 5, -4, -1 ] ], 1.375 ] + ], + [ + [ [ [ 0, -1, -5, 4, -4, -1 ], [ 1, -1, -5, 4, -4, -1 ], [ 1, -1, -4, 4, -4, -1 ], [ 3, -1, -5, 4, -4, -2 ] ], 0.625 ], + [ [ [ 0, -1, -5, 4, -4, -1 ], [ 1, -1, -5, 4, -4, -1 ], [ 2, -2, -5, 4, -4, -1 ], [ 3, -1, -5, 4, -4, -2 ] ], 1.5 ] + ], + [ + [ [ [ 0, -1, -5, 4, -4, -1 ], [ 2, -3, -5, 4, -4, -1 ], [ 2, -2, -5, 4, -4, -1 ], [ 3, -1, -5, 4, -4, -2 ] ], 1.5 ], + [ [ [ 1, -2, -5, 4, -4, -2 ], [ 2, -3, -5, 4, -4, -1 ], [ 2, -2, -5, 4, -4, -1 ], [ 3, -1, -5, 4, -4, -2 ] ], 0.5 ], + [ [ [ 1, -2, -5, 4, -4, -2 ], [ 2, -3, -5, 4, -4, -1 ], [ 2, -2, -5, 4, -4, -1 ], [ 3, -2, -5, 4, -4, -1 ] ], 1.25 ] + ], + [ + [ [ [ 1, -2, -5, 4, -4, -2 ], [ 2, -2, -5, 4, -5, -1 ], [ 2, -2, -5, 4, -4, -1 ], [ 3, -2, -5, 4, -4, -1 ] ], 0.625 ], + [ [ [ 1, -3, -5, 4, -4, -1 ], [ 2, -2, -5, 4, -5, -1 ], [ 2, -2, -5, 4, -4, -1 ], [ 3, -2, -5, 4, -4, -1 ] ], 0.75 ], + [ [ [ 1, -3, -5, 4, -4, -1 ], [ 2, -2, -5, 4, -5, -1 ], [ 2, -3, -5, 4, -4, -1 ], [ 3, -2, -5, 4, -4, -1 ] ], 1.625 ] + ], + [ + [ [ [ 1, -2, -5, 4, -5, -1 ], [ 2, -2, -5, 4, -5, -1 ], [ 2, -3, -5, 4, -4, -1 ], [ 3, -2, -5, 4, -4, -1 ] ], 1.5 ], + [ [ [ 1, -2, -5, 4, -5, -1 ], [ 2, -2, -5, 4, -5, -1 ], [ 3, -2, -5, 4, -5, -1 ], [ 3, -2, -5, 4, -4, -1 ] ], 0.5 ], + [ [ [ 1, -2, -5, 4, -5, -1 ], [ 2, -2, -5, 4, -5, -1 ], [ 3, -2, -5, 4, -5, -1 ], [ 3, -2, -4, 4, -5, -1 ] ], 1.5 ] + ], + [ + [ [ [ 1, -2, -5, 4, -5, -1 ], [ 2, -2, -5, 4, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 3, -2, -4, 4, -5, -1 ] ], 1.375 ], + [ [ [ 1, -2, -5, 4, -5, -1 ], [ 1, -2, -4, 5, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 3, -2, -4, 4, -5, -1 ] ], 0.625 ], + [ [ [ 0, -2, -4, 5, -5, -1 ], [ 1, -2, -4, 5, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 3, -2, -4, 4, -5, -1 ] ], 0.75 ] + ], + [ + [ [ [ 0, -2, -4, 5, -5, -1 ], [ 1, -1, -4, 4, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 3, -2, -4, 4, -5, -1 ] ], 0.875 ], + [ [ [ 0, -2, -4, 5, -5, -1 ], [ 1, -1, -4, 4, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 4, -2, -4, 3, -5, -1 ] ], 1 ] + ], + [ + [ [ [ 1, -2, -4, 4, -5, -1 ], [ 1, -1, -4, 4, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 4, -2, -4, 3, -5, -1 ] ], 0.75 ] + ], + [ + [ [ [ 1, -2, -4, 4, -5, -1 ], [ 1, -1, -4, 4, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 0.75 ] + ], + [ + [ [ [ 1, -2, -4, 4, -5, -1 ], [ 2, -1, -4, 4, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 0.875 ], + [ [ [ 1, -2, -4, 4, -5, -1 ], [ 2, -1, -4, 4, -5, -1 ], [ 1, -1, -4, 4, -5, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1.25 ], + [ [ [ 0, -1, -4, 4, -5, -1 ], [ 2, -1, -4, 4, -5, -1 ], [ 1, -1, -4, 4, -5, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1.125 ] + ], + [ + [ [ [ 1, -1, -4, 3, -5, -1 ], [ 2, -1, -4, 4, -5, -1 ], [ 1, -1, -4, 4, -5, -1 ], [ 2, -1, -4, 4, -4, -1 ] ], 1 ], + [ [ [ 1, -1, -4, 3, -5, -1 ], [ 2, -1, -4, 4, -5, -1 ], [ 1, -1, -4, 4, -5, -1 ], [ 2, 0, -4, 4, -5, -1 ] ], 1.375 ] + ], + [ + [ [ [ 0, -1, -3, 4, -5, -1 ], [ 2, -1, -4, 4, -5, -1 ], [ 1, -1, -4, 4, -5, -1 ], [ 2, 0, -4, 4, -5, -1 ] ], 0.75 ], + [ [ [ 0, -1, -3, 4, -5, -1 ], [ 2, -1, -4, 4, -5, -1 ], [ 2, -1, -4, 3, -5, -1 ], [ 2, 0, -4, 4, -5, -1 ] ], 1.5 ], + [ [ [ 0, -1, -3, 4, -5, -1 ], [ 2, -1, -4, 4, -5, -1 ], [ 2, -1, -4, 3, -5, -1 ], [ 3, -2, -4, 4, -5, -1 ] ], 0.5 ] + ], + [ + [ [ [ 1, -2, -4, 4, -5, -1 ], [ 2, -1, -4, 4, -5, -1 ], [ 2, -1, -4, 3, -5, -1 ], [ 3, -2, -4, 4, -5, -1 ] ], 1.375 ], + [ [ [ 1, -2, -4, 4, -5, -1 ], [ 2, -1, -4, 4, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 3, -2, -4, 4, -5, -1 ] ], 0.875 ], + [ [ [ 1, -2, -4, 4, -5, -1 ], [ 3, -2, -5, 4, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 3, -2, -4, 4, -5, -1 ] ], 0.5 ] + ], + [ + [ [ [ 1, -2, -4, 4, -5, -1 ], [ 3, -2, -5, 4, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 4, -3, -5, 4, -5, -1 ] ], 0.875 ] + ], + [ + [ [ [ 1, -2, -4, 4, -5, -1 ], [ 2, -2, -4, 5, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 4, -3, -5, 4, -5, -1 ] ], 1 ], + [ [ [ 2, -3, -5, 4, -5, -1 ], [ 2, -2, -4, 5, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 4, -3, -5, 4, -5, -1 ] ], 1.5 ] + ], + [ + [ [ [ 2, -3, -5, 4, -5, -1 ], [ 2, -2, -4, 5, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 3, -3, -4, 5, -5, -1 ] ], 1.375 ], + [ [ [ 1, -3, -4, 5, -5, -1 ], [ 2, -2, -4, 5, -5, -1 ], [ 2, -2, -4, 4, -5, -1 ], [ 3, -3, -4, 5, -5, -1 ] ], 1.25 ], + [ [ [ 1, -3, -4, 5, -5, -1 ], [ 2, -2, -4, 5, -5, -1 ], [ 2, -3, -4, 5, -5, -1 ], [ 3, -3, -4, 5, -5, -1 ] ], 0.5 ] + ], + [ + [ [ [ 1, -3, -4, 5, -5, -1 ], [ 2, -2, -4, 5, -5, -1 ], [ 1, -2, -4, 5, -5, -1 ], [ 3, -3, -4, 5, -5, -1 ] ], 0.75 ], + [ [ [ 1, -3, -4, 5, -5, -1 ], [ 2, -3, -4, 5, -5, -1 ], [ 1, -2, -4, 5, -5, -1 ], [ 3, -3, -4, 5, -5, -1 ] ], 1.5 ] + ], + [ + [ [ [ 1, -3, -4, 5, -5, -1 ], [ 2, -4, -4, 5, -5, -1 ], [ 1, -2, -4, 5, -5, -1 ], [ 3, -3, -4, 5, -5, -1 ] ], 1.75 ], + [ [ [ 1, -3, -4, 5, -5, -1 ], [ 2, -4, -4, 5, -5, -1 ], [ 2, -3, -5, 5, -5, -1 ], [ 3, -3, -4, 5, -5, -1 ] ], 1.125 ], + [ [ [ 1, -4, -4, 5, -5, -1 ], [ 2, -4, -4, 5, -5, -1 ], [ 2, -3, -5, 5, -5, -1 ], [ 3, -3, -4, 5, -5, -1 ] ], 1.75 ] + ], + [ + [ [ [ 2, -4, -4, 4, -5, -1 ], [ 2, -4, -4, 5, -5, -1 ], [ 2, -3, -5, 5, -5, -1 ], [ 3, -3, -4, 5, -5, -1 ] ], 1.25 ], + [ [ [ 2, -4, -4, 4, -5, -1 ], [ 2, -4, -4, 5, -5, -1 ], [ 2, -3, -5, 5, -5, -1 ], [ 3, -3, -5, 5, -5, -1 ] ], 1.5 ] + ], + [ + [ [ [ 1, -3, -5, 5, -5, -1 ], [ 2, -4, -4, 5, -5, -1 ], [ 2, -3, -5, 5, -5, -1 ], [ 3, -3, -5, 5, -5, -1 ] ], 1.375 ], + [ [ [ 1, -3, -5, 5, -5, -1 ], [ 3, -4, -5, 5, -5, -1 ], [ 2, -3, -5, 5, -5, -1 ], [ 3, -3, -5, 5, -5, -1 ] ], 1.625 ] + ], + [ + [ [ [ 1, -4, -5, 5, -5, -1 ], [ 3, -4, -5, 5, -5, -1 ], [ 2, -3, -5, 5, -5, -1 ], [ 3, -3, -5, 5, -5, -1 ] ], 1 ] + ], + [ + [ [ [ 1, -4, -5, 5, -5, -1 ], [ 3, -4, -5, 5, -5, -1 ], [ 3, -4, -6, 5, -5, -1 ], [ 3, -3, -5, 5, -5, -1 ] ], 1 ] + ], + [ + [ [ [ 1, -4, -5, 5, -5, -1 ], [ 3, -4, -5, 5, -5, -1 ], [ 3, -3, -6, 5, -5, -1 ], [ 3, -3, -5, 5, -5, -1 ] ], 1.75 ] + ], + [ + [ [ [ 1, -4, -5, 5, -5, -1 ], [ 3, -4, -5, 5, -5, -1 ], [ 4, -5, -5, 5, -5, -1 ], [ 3, -3, -5, 5, -5, -1 ] ], 1.375 ], + [ [ [ 1, -4, -5, 5, -5, -1 ], [ 3, -4, -5, 5, -5, -1 ], [ 4, -5, -5, 5, -5, -1 ], [ 4, -4, -6, 5, -5, -1 ] ], 0.75 ] + ], + [ + [ [ [ 1, -4, -5, 5, -5, -1 ], [ 3, -4, -5, 5, -5, -1 ], [ 3, -3, -5, 5, -5, -1 ], [ 4, -4, -6, 5, -5, -1 ] ], 1.5 ] + ], + [ + [ [ [ 1, -4, -5, 5, -5, -1 ], [ 3, -4, -5, 5, -5, -1 ], [ 3, -4, -5, 6, -5, -1 ], [ 4, -4, -6, 5, -5, -1 ] ], 1.375 ], + [ [ [ 1, -4, -5, 5, -5, -1 ], [ 3, -5, -5, 5, -5, -1 ], [ 3, -4, -5, 6, -5, -1 ], [ 4, -4, -6, 5, -5, -1 ] ], 1.375 ], + [ [ [ 1, -4, -5, 5, -5, -1 ], [ 3, -5, -5, 5, -5, -1 ], [ 3, -4, -5, 6, -5, -1 ], [ 2, -4, -5, 5, -5, 0 ] ], 1.25 ] + ], + [ + [ [ [ 1, -5, -5, 5, -5, 0 ], [ 3, -5, -5, 5, -5, -1 ], [ 3, -4, -5, 6, -5, -1 ], [ 2, -4, -5, 5, -5, 0 ] ], 0.5 ], + [ [ [ 1, -5, -5, 5, -5, 0 ], [ 3, -5, -5, 5, -5, -1 ], [ 4, -4, -5, 4, -5, 0 ], [ 2, -4, -5, 5, -5, 0 ] ], 1 ] + ], + [ + [ [ [ 2, -5, -5, 4, -5, 0 ], [ 3, -5, -5, 5, -5, -1 ], [ 4, -4, -5, 4, -5, 0 ], [ 2, -4, -5, 5, -5, 0 ] ], 1.375 ], + [ [ [ 2, -5, -5, 4, -5, 0 ], [ 2, -3, -5, 4, -5, 0 ], [ 4, -4, -5, 4, -5, 0 ], [ 2, -4, -5, 5, -5, 0 ] ], 1 ] + ], + [ + [ [ [ 2, -5, -5, 4, -5, 0 ], [ 2, -3, -5, 4, -5, 0 ], [ 4, -4, -5, 4, -5, 0 ], [ 3, -4, -5, 4, -5, 0 ] ], 1.625 ] + ], + [ + [ [ [ 1, -4, -5, 4, -5, 0 ], [ 2, -3, -5, 4, -5, 0 ], [ 4, -4, -5, 4, -5, 0 ], [ 3, -4, -5, 4, -5, 0 ] ], 1.375 ], + [ [ [ 1, -4, -5, 4, -5, 0 ], [ 2, -3, -5, 4, -5, 0 ], [ 4, -4, -5, 4, -5, 0 ], [ 2, -2, -5, 4, -5, 0 ] ], 1.75 ] + ], + [ + [ [ [ 2, -4, -5, 3, -5, 0 ], [ 2, -3, -5, 4, -5, 0 ], [ 4, -4, -5, 4, -5, 0 ], [ 2, -2, -5, 4, -5, 0 ] ], 1.375 ], + [ [ [ 2, -4, -5, 3, -5, 0 ], [ 3, -4, -6, 4, -5, 0 ], [ 4, -4, -5, 4, -5, 0 ], [ 2, -2, -5, 4, -5, 0 ] ], 1.25 ], + [ [ [ 2, -4, -5, 3, -5, 0 ], [ 3, -4, -6, 4, -5, 0 ], [ 4, -4, -5, 4, -5, 0 ], [ 3, -4, -4, 4, -5, 0 ] ], 0.75 ] + ], + [ + [ [ [ 2, -4, -5, 3, -5, 0 ], [ 3, -4, -6, 4, -5, 0 ], [ 4, -4, -6, 4, -5, 0 ], [ 3, -4, -4, 4, -5, 0 ] ], 1.125 ], + [ [ [ 2, -4, -5, 3, -5, 0 ], [ 3, -4, -6, 4, -5, 0 ], [ 4, -4, -6, 4, -5, 0 ], [ 4, -4, -6, 3, -5, 0 ] ], 0.875 ] + ], + [ + [ [ [ 2, -4, -5, 3, -5, 0 ], [ 3, -4, -7, 4, -5, 0 ], [ 4, -4, -6, 4, -5, 0 ], [ 4, -4, -6, 3, -5, 0 ] ], 1.375 ], + [ [ [ 3, -5, -6, 3, -5, 0 ], [ 3, -4, -7, 4, -5, 0 ], [ 4, -4, -6, 4, -5, 0 ], [ 4, -4, -6, 3, -5, 0 ] ], 1.75 ] + ], + [ + [ [ [ 3, -5, -6, 3, -5, 0 ], [ 3, -4, -7, 4, -5, 0 ], [ 5, -5, -7, 4, -5, 0 ], [ 4, -4, -6, 3, -5, 0 ] ], 1.125 ], + [ [ [ 3, -5, -6, 3, -5, 0 ], [ 3, -4, -7, 4, -5, 0 ], [ 5, -5, -7, 4, -5, 0 ], [ 5, -5, -7, 3, -5, 0 ] ], 1.5 ] + ], + [ + [ [ [ 3, -5, -6, 3, -5, 0 ], [ 3, -4, -7, 4, -5, 0 ], [ 5, -5, -7, 4, -5, 0 ], [ 4, -5, -6, 4, -5, 0 ] ], 0.625 ], + [ [ [ 3, -5, -6, 3, -5, 0 ], [ 5, -5, -6, 2, -5, 0 ], [ 5, -5, -7, 4, -5, 0 ], [ 4, -5, -6, 4, -5, 0 ] ], 1.375 ], + [ [ [ 3, -5, -6, 3, -5, 0 ], [ 5, -5, -6, 2, -5, 0 ], [ 5, -4, -6, 3, -5, 0 ], [ 4, -5, -6, 4, -5, 0 ] ], 1.375 ] + ], + [ + [ [ [ 3, -5, -6, 3, -5, 0 ], [ 5, -5, -6, 2, -5, 0 ], [ 5, -4, -6, 3, -5, 0 ], [ 5, -5, -6, 3, -5, 0 ] ], 0.75 ] + ], + [ + [ [ [ 3, -5, -6, 3, -5, 0 ], [ 5, -5, -6, 2, -5, 0 ], [ 5, -4, -6, 3, -5, 0 ], [ 6, -5, -6, 2, -5, 0 ] ], 0.5 ], + [ [ [ 4, -5, -6, 2, -5, 0 ], [ 5, -5, -6, 2, -5, 0 ], [ 5, -4, -6, 3, -5, 0 ], [ 6, -5, -6, 2, -5, 0 ] ], 1.125 ] + ], + [ + [ [ [ 4, -5, -6, 2, -5, 0 ], [ 5, -5, -6, 2, -5, 0 ], [ 5, -4, -6, 3, -5, 0 ], [ 4, -4, -6, 3, -5, 0 ] ], 1.25 ], + [ [ [ 4, -5, -6, 2, -5, 0 ], [ 4, -4, -7, 3, -5, 0 ], [ 5, -4, -6, 3, -5, 0 ], [ 4, -4, -6, 3, -5, 0 ] ], 1.25 ], + [ [ [ 4, -5, -6, 2, -5, 0 ], [ "Rest" ], [ 5, -4, -6, 3, -5, 0 ], [ 4, -4, -6, 3, -5, 0 ] ], 0.5 ], + [ [ [ "Rest" ], [ "Rest" ], [ 5, -4, -6, 3, -5, 0 ], [ 4, -4, -6, 3, -5, 0 ] ], 1.75 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 4, -4, -6, 3, -5, 0 ] ], 1.125 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 7.25 ] + ] + ] +], +"last_changes": +[ + [ [ 3, -5, -6, 3, -5, 0 ], [ 5, -5, -6, 2, -5, 0 ], [ 5, -4, -6, 3, -5, 0 ], [ 5, -5, -6, 3, -5, 0 ] ], + [ [ 3, -5, -6, 3, -5, 0 ], [ 5, -5, -6, 2, -5, 0 ], [ 5, -4, -6, 3, -5, 0 ], [ 6, -5, -6, 2, -5, 0 ] ], + [ [ 4, -5, -6, 2, -5, 0 ], [ 5, -5, -6, 2, -5, 0 ], [ 5, -4, -6, 3, -5, 0 ], [ 6, -5, -6, 2, -5, 0 ] ], + [ [ 4, -5, -6, 2, -5, 0 ], [ 5, -5, -6, 2, -5, 0 ], [ 5, -4, -6, 3, -5, 0 ], [ 4, -4, -6, 3, -5, 0 ] ], + [ [ 4, -5, -6, 2, -5, 0 ], [ 4, -4, -7, 3, -5, 0 ], [ 5, -4, -6, 3, -5, 0 ], [ 4, -4, -6, 3, -5, 0 ] ] +], +"cur_uid": "75316bf0", +"ref_uid": "55bd25a1", +"order_seed": 144453, +"dur_seed": 681001, +"motifs_seed": 933698, +"entrances_probs_vals": [ 0, 0, 0, 0.41, 1.84, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 0.41, 1.84, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 0.41, 1.84, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2430, -293 ], [ -869, 1211 ], [ -832, 1285 ], [ -702, 1211 ] ], +"step_probs_vals": [ -1200, 1200, 0.0020576131687243, 0.068181818181818, 0.074074074074074, 0.0625, 0.20576131687243, 0.0625, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61316872427983, 0, 0.98971193415638, 0 ], +"passages_weights": [ 0.48, 0.46, 0.48, 1, 1 ], +"hd_exp": 9, +"hd_invert": 0, +"order": +[ + [ [ 1 ], [ 2, 3, 0 ], [ ] ], + [ [ 0, 2 ], [ 3, 1 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 0, 2 ], [ 3, 1 ], [ ] ], + [ [ 3, 0 ], [ 1, 2 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 1 ], [ 0, 2, 3 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 0, 2 ], [ 3, 1 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 3 ], [ 0, 2, 1 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 1 ], [ 2, 3, 0 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 2, 0 ], [ 3, 1 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 0, 3 ], [ 1, 2 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 1 ], [ 3, 2, 0 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 1 ], [ 3, 2, 0 ], [ ] ], + [ [ 2 ], [ 0, 3, 1 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 2 ], [ 1, 3, 0 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 1 ], [ 0, 2, 3 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 2, 0 ], [ 1, 3 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 2, 1 ], [ 0, 3 ], [ ] ], + [ [ 1 ], [ 0, 2, 3 ], [ ] ], + [ [ 3 ], [ 0, 2, 1 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 0, 3 ], [ 2, 1 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 2, 1 ], [ 0, 3 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 2, 1 ], [ 0, 3 ], [ ] ], + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 0, 2 ], [ 3, 1 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 100, 100 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/781442dc/781442dc_code.scd b/resources/string_quartet_3/781442dc/781442dc_code.scd new file mode 100644 index 0000000..c194eef --- /dev/null +++ b/resources/string_quartet_3/781442dc/781442dc_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/string_quartet_3/781442dc/781442dc_mus_model.json b/resources/string_quartet_3/781442dc/781442dc_mus_model.json new file mode 100644 index 0000000..d2de366 --- /dev/null +++ b/resources/string_quartet_3/781442dc/781442dc_mus_model.json @@ -0,0 +1,117 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ] ], 0.25 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ] ], 0.5 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 1, 0, -1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 0.125 ], + [ [ [ 0, 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1.375 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 0, 1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 1 ], [ -1, 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, -1, 0, 0, 0, 1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 1 ], [ -1, 0, 0, 0, 1, 1 ], [ -1, 0, 0, 1, 0, 1 ], [ 0, -1, 0, 0, 0, 1 ] ], 0.25 ], + [ [ [ 0, 0, 0, 0, 0, 1 ], [ -1, 0, 0, 0, 1, 1 ], [ -1, 0, 0, 1, 0, 1 ], [ 0, 0, 0, 0, -1, 1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 1 ], [ -1, 0, 0, 0, 1, 1 ], [ -1, 0, 0, 0, 0, 2 ], [ 0, 0, 0, 0, -1, 1 ] ], 0.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 1 ], [ -1, 0, 0, 0, 1, 1 ], [ -1, 0, 0, 0, 0, 2 ], [ "Rest" ] ], 0 ], + [ [ [ 0, -1, 0, 0, 0, 2 ], [ -1, 0, 0, 0, 1, 1 ], [ -1, 0, 0, 0, 0, 2 ], [ "Rest" ] ], 0 ], + [ [ [ 0, -1, 0, 0, 0, 2 ], [ -1, 0, -1, 0, 0, 2 ], [ -1, 0, 0, 0, 0, 2 ], [ "Rest" ] ], 0.25 ], + [ [ [ -1, 0, 0, 0, 1, 2 ], [ -1, 0, -1, 0, 0, 2 ], [ -1, 0, 0, 0, 0, 2 ], [ "Rest" ] ], 0.25 ], + [ [ [ -1, 0, 1, 0, 0, 2 ], [ -1, 0, -1, 0, 0, 2 ], [ -1, 0, 0, 0, 0, 2 ], [ "Rest" ] ], 0.25 ], + [ [ [ -2, 0, 1, 0, 0, 2 ], [ -1, 0, -1, 0, 0, 2 ], [ -1, 0, 0, 0, 0, 2 ], [ "Rest" ] ], 1.375 ] + ], + [ + [ [ [ -2, 0, 1, 0, 0, 2 ], [ -1, 0, -1, 0, 0, 2 ], [ -1, 0, 0, 0, 0, 2 ], [ 0, 0, 0, 0, -1, 1 ] ], 0.5 ], + [ [ [ -2, 0, 1, 0, 0, 2 ], [ -1, 0, 1, 0, 0, 1 ], [ -1, 0, 0, 0, 0, 2 ], [ 0, 0, 0, 0, -1, 1 ] ], 0 ], + [ [ [ -2, 0, 1, 0, 0, 2 ], [ -1, 0, 1, 0, 0, 1 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 1 ] ], 0.625 ], + [ [ [ -2, 0, 1, 0, 0, 2 ], [ 0, 0, -1, 0, -1, 1 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 0, 0, -1, 1 ] ], 0 ], + [ [ [ -2, 0, 1, 0, 0, 2 ], [ 0, 0, -1, 0, -1, 1 ], [ 1, -1, 0, 0, -1, 1 ], [ 0, 0, 0, 0, -1, 1 ] ], 1 ] + ], + [ + [ [ [ -1, 1, 0, 0, -1, 1 ], [ 0, 0, -1, 0, -1, 1 ], [ 1, -1, 0, 0, -1, 1 ], [ 0, 0, 0, 0, -1, 1 ] ], 0 ], + [ [ [ -1, 1, 0, 0, -1, 1 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 1 ], [ 0, 0, 0, 0, -1, 1 ] ], 0.625 ], + [ [ [ 0, -1, 1, 0, -1, 1 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 1 ], [ 0, 0, 0, 0, -1, 1 ] ], 0.625 ], + [ [ [ 0, 0, -1, 0, -1, 1 ], [ 1, -1, 0, 0, -1, 0 ], [ 1, -1, 0, 0, -1, 1 ], [ 0, 0, 0, 0, -1, 1 ] ], 0 ], + [ [ [ 0, 0, -1, 0, -1, 1 ], [ 1, -1, 0, -1, -1, 1 ], [ 1, -1, 0, 0, -1, 1 ], [ 0, 0, 0, 0, -1, 1 ] ], 1.125 ] + ], + [ + [ [ [ 0, 0, -1, 0, -1, 1 ], [ 1, -1, 0, -1, -1, 1 ], [ 1, -1, 0, 0, -1, 1 ], [ "Rest" ] ], 0 ], + [ [ [ 0, 0, -1, 0, -1, 1 ], [ 1, -1, 0, -1, -1, 1 ], [ 1, -1, 0, -1, -1, 2 ], [ "Rest" ] ], 0.625 ], + [ [ [ 0, 0, -1, 0, -1, 1 ], [ 1, -1, 0, -1, -1, 1 ], [ 2, -1, -1, -1, -1, 1 ], [ "Rest" ] ], 0.5 ], + [ [ [ 0, 0, -1, 0, -1, 1 ], [ 1, -1, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ "Rest" ] ], 1.375 ] + ], + [ + [ [ [ 0, 0, 0, -1, 0, 1 ], [ 1, -1, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ "Rest" ] ], 0 ], + [ [ [ 0, 0, 0, -1, 0, 1 ], [ 1, -1, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, -1, 1, -1, -1, 1 ] ], 0.375 ], + [ [ [ 2, -1, 0, -2, -1, 1 ], [ 1, -1, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, -1, 1, -1, -1, 1 ] ], 0 ], + [ [ [ 2, -1, 0, -2, -1, 1 ], [ 1, -1, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, -1, -1, -1, 1 ] ], 1 ] + ], + [ + [ [ [ 1, 0, 0, -1, -2, 1 ], [ 1, -1, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, -1, -1, -1, 1 ] ], 0 ], + [ [ [ 1, 0, 0, -1, -2, 1 ], [ 0, 0, 1, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, -1, -1, -1, 1 ] ], 0.625 ], + [ [ [ 1, 0, 0, -1, -2, 1 ], [ 1, 0, -2, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, -1, -1, -1, 1 ] ], 0.25 ], + [ [ [ 1, 0, 0, -1, -2, 1 ], [ 0, 1, -1, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, -1, -1, -1, 1 ] ], 1.375 ] + ], + [ + [ [ [ 1, 1, -1, -1, -1, 0 ], [ 0, 1, -1, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, -1, -1, -1, 1 ] ], 0.375 ], + [ [ [ 0, 0, 0, -2, -1, 1 ], [ 0, 1, -1, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, -1, -1, -1, 1 ] ], 0.375 ], + [ [ [ -1, 1, -1, -1, -1, 1 ], [ 0, 1, -1, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, -1, -1, -1, 1 ] ], 1.25 ] + ], + [ + [ [ [ -1, 1, -1, -1, -1, 1 ], [ 0, 1, -1, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ 0, 0, 0, -1, -1, 2 ] ], 0 ], + [ [ [ -1, 1, -1, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 1 ], [ 0, 0, 0, -1, -1, 2 ] ], 0 ], + [ [ [ 0, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 1 ], [ 0, 0, 0, -1, -1, 2 ] ], 0.25 ], + [ [ [ 0, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 1 ], [ 0, 0, 0, 0, -1, 1 ] ], 0.625 ], + [ [ [ 0, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 2 ] ], 1.375 ], + [ [ [ 0, 0, 0, -1, -1, 0 ], [ "Rest" ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 2 ] ], 0 ], + [ [ [ 0, 0, 0, -1, -1, 0 ], [ "Rest" ], [ 1, 0, 0, -1, -1, 1 ], [ "Rest" ] ], 0 ], + [ [ [ 0, 0, 0, -1, -1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 5.625 ] + ] + ] +], +"last_changes": +[ + [ [ -1, 1, -1, -1, -1, 1 ], [ 0, 1, -1, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 1 ], [ 0, 0, 0, -1, -1, 2 ] ], + [ [ -1, 1, -1, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 1 ], [ 0, 0, 0, -1, -1, 2 ] ], + [ [ 0, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 1 ], [ 0, 0, 0, -1, -1, 2 ] ], + [ [ 0, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 1 ], [ 0, 0, 0, 0, -1, 1 ] ], + [ [ 0, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 0 ], [ 1, 0, 0, -1, -1, 1 ], [ 1, 0, 0, -1, -1, 2 ] ] +], +"cur_uid": "781442dc", +"ref_uid": "nil", +"order_seed": 479669, +"dur_seed": 398026, +"motifs_seed": 801394, +"entrances_probs_vals": [ 0.43, 0, 0, 0, 0.85164835164835, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 1, 0, 0.99206349206349, 0.16483516483516, 0.66, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 1, 0, 0.95238095238095, 0, 1.043956043956, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -1983.9009287926, 1100 ], [ -999, 1322.600619195 ], [ -14.860681114551, 1676 ], [ 78.018575851393, 1694 ] ], +"step_probs_vals": [ 0, 1200, 0, 0.5, 0.076131687242798, 0.91477272727273, 0.17489711934156, 0, 0.57818930041152, 0, 1, 0 ], +"passages_weights": [ 0.54, 0.47, 0.52, 0.57, 0.58 ], +"hd_exp": -0.22, +"hd_invert": 0, +"order": +[ + [ [ 3, 1 ], [ 2, 0, 0, 2 ], [ ] ], + [ [ 0 ], [ 3, 1, 2, 3, 2 ], [ ] ], + [ [ 2 ], [ 0, 1, 0, 0, 0 ], [ 3 ] ], + [ [ 0, 3 ], [ 1, 2, 1, 2 ], [ ] ], + [ [ 2, 3 ], [ 0, 1, 0, 0, 1 ], [ ] ], + [ [ 1, 0 ], [ 2, 2, 2 ], [ 3 ] ], + [ [ 1, 2 ], [ 0, 3, 0, 3 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 1, 1 ], [ ] ], + [ [ 1, 2, 3 ], [ 0, 0, 0 ], [ ] ], + [ [ 2 ], [ 3, 1, 0, 3, 3 ], [ ] ] +], +"sus_weights": [ 0.44, 0.41, 0.28 ], +"order_size": [ 10, 10 ], +"passages_size": [ 2, 4 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/7c2de94c/7c2de94c_code.scd b/resources/string_quartet_3/7c2de94c/7c2de94c_code.scd new file mode 100644 index 0000000..dc13462 --- /dev/null +++ b/resources/string_quartet_3/7c2de94c/7c2de94c_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 = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + if(pDistance < 0, {stepFunc.value(pDistance)}, {0}); +}; + +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 = [[-3, 0, 0, 0, 0, 0], [-3, 0, 0, 0, 0, 0], [-3, 0, 0, 0, 0, 0], [-3, 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 = [[-3, 0, 0, 0, 0, 0], [-3, 0, 0, 0, 0, 0], [-3, 0, 0, 0, 0, 0], [-3, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3/7c2de94c/7c2de94c_mus_model.json b/resources/string_quartet_3/7c2de94c/7c2de94c_mus_model.json new file mode 100644 index 0000000..37e7285 --- /dev/null +++ b/resources/string_quartet_3/7c2de94c/7c2de94c_mus_model.json @@ -0,0 +1,163 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ 1, -1, 0, 0, 0, 0 ], [ "Rest" ] ], 0.125 ], + [ [ [ "Rest" ], [ "Rest" ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 1.125 ], + [ [ [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 1.125 ], + [ [ [ -1, 0, 1, 0, 0, 1 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0.875 ] + ], + [ + [ [ [ 1, 0, 0, -1, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0.375 ] + ], + [ + [ [ [ 1, 0, 0, -1, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 2, -1, 0, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, 0, -1, 0, 0 ], [ 3, -1, 0, -1, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 2, -1, 0, -1, 0, 0 ] ], 0.75 ] + ], + [ + [ [ [ 0, -1, 0, 1, 0, 0 ], [ 3, -1, 0, -1, -1, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 2, -1, 0, -1, 0, 0 ] ], 0.375 ] + ], + [ + [ [ [ 0, -1, 0, 1, 0, 0 ], [ 2, 0, 0, -1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 2, -1, 0, -1, 0, 0 ] ], 0.125 ] + ], + [ + [ [ [ 0, -1, 0, 1, 0, 0 ], [ 2, 0, 0, -1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, -1, 1, 0, 0, 0 ] ], 0.75 ] + ], + [ + [ [ [ 0, -1, 0, 1, 0, 0 ], [ 2, 0, 0, -1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, -1, -1, 1, 0, 0 ] ], 0.375 ] + ], + [ + [ [ [ 0, -1, 0, 0, 0, -1 ], [ 2, 0, 0, -1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, -1, -1, 1, 0, 0 ] ], 0.5 ] + ], + [ + [ [ [ 0, -1, 0, 0, 0, -1 ], [ 2, -1, -1, 1, 0, -1 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, -1, -1, 1, 0, 0 ] ], 0 ] + ], + [ + [ [ [ 0, -1, 0, 0, 0, -1 ], [ 2, -1, -1, 1, 0, -1 ], [ 2, 0, 0, 0, 0, -1 ], [ 1, -1, -1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, -1, 0, 0, 0, -1 ], [ 3, -1, 0, 0, 0, -2 ], [ 2, 0, 0, 0, 0, -1 ], [ 1, -1, -1, 1, 0, 0 ] ], 0.5 ] + ], + [ + [ [ [ 0, -1, 0, 0, 0, -1 ], [ 2, -1, 1, 0, 0, -1 ], [ 2, 0, 0, 0, 0, -1 ], [ 1, -1, -1, 1, 0, 0 ] ], 0.875 ] + ], + [ + [ [ [ 0, -1, 0, 0, 0, -1 ], [ 2, -1, 1, 0, 0, -1 ], [ 2, 0, 0, 0, 0, -1 ], [ 3, -1, 0, 0, 0, -2 ] ], 1.125 ] + ], + [ + [ [ [ 0, -1, 0, 0, 0, -1 ], [ 2, -1, 1, 0, 0, -1 ], [ 3, -1, 1, 0, 0, -2 ], [ 3, -1, 0, 0, 0, -2 ] ], 0.625 ] + ], + [ + [ [ [ 1, -1, 1, 0, 0, -2 ], [ 2, -1, 1, 0, 0, -1 ], [ 3, -1, 1, 0, 0, -2 ], [ 3, -1, 0, 0, 0, -2 ] ], 0.125 ] + ], + [ + [ [ [ 1, -1, 1, 0, 0, -2 ], [ 2, -1, 1, 0, 0, -1 ], [ 3, -1, 1, 0, 0, -2 ], [ 3, -1, 1, -1, 0, -1 ] ], 0.5 ] + ], + [ + [ [ [ 1, -1, 1, 0, 0, -2 ], [ 2, -1, 1, 0, 0, -1 ], [ 3, -1, 1, 0, 0, -2 ], [ 2, -1, 2, 0, 0, -1 ] ], 0.25 ] + ], + [ + [ [ [ 1, -1, 1, 0, 0, -2 ], [ 2, -1, 1, 0, 0, -1 ], [ -1, 0, 1, 0, 0, -1 ], [ 2, -1, 2, 0, 0, -1 ] ], 0.75 ] + ], + [ + [ [ [ 1, -1, 1, 0, 0, -2 ], [ 2, -1, 1, 0, 0, -1 ], [ -1, 0, 2, 0, 0, -1 ], [ 2, -1, 2, 0, 0, -1 ] ], 0.125 ] + ], + [ + [ [ [ 1, -1, 1, 0, 0, -2 ], [ 2, -1, 1, 0, 0, -1 ], [ 3, -1, 0, 0, 0, -2 ], [ 2, -1, 2, 0, 0, -1 ] ], 0.625 ] + ], + [ + [ [ [ 1, -1, 1, 0, 0, -2 ], [ 2, -1, 1, 0, 0, -1 ], [ 3, -1, 1, 0, 0, -2 ], [ 2, -1, 2, 0, 0, -1 ] ], 1 ] + ], + [ + [ [ [ 1, -1, 1, 0, 0, -2 ], [ 2, -1, 1, 0, 0, -1 ], [ 3, -1, 1, 0, 0, -2 ], [ 3, -1, 0, 0, 0, -2 ] ], 0.125 ] + ], + [ + [ [ [ 1, -1, 1, 0, 0, -2 ], [ 4, -1, 0, -1, 0, -2 ], [ 3, -1, 1, 0, 0, -2 ], [ 3, -1, 0, 0, 0, -2 ] ], 0.5 ] + ], + [ + [ [ [ 1, -1, 1, 0, 0, -2 ], [ 0, -1, 0, 0, 0, -1 ], [ 3, -1, 1, 0, 0, -2 ], [ 3, -1, 0, 0, 0, -2 ] ], 0.625 ] + ], + [ + [ [ [ 1, -1, 0, 1, 0, -2 ], [ 0, -1, 0, 0, 0, -1 ], [ 3, -1, 1, 0, 0, -2 ], [ 3, -1, 0, 0, 0, -2 ] ], 0.75 ] + ], + [ + [ [ [ 1, -1, 0, 1, 0, -2 ], [ 0, -1, 0, 0, 0, -1 ], [ 3, -1, 0, 1, -1, -2 ], [ 3, -1, 0, 0, 0, -2 ] ], 0.5 ] + ], + [ + [ [ [ 1, -1, 0, 1, 0, -2 ], [ 1, -2, 0, 1, 0, -2 ], [ 3, -1, 0, 1, -1, -2 ], [ 3, -1, 0, 0, 0, -2 ] ], 1 ] + ], + [ + [ [ [ 1, -1, 0, 1, 0, -2 ], [ 1, -2, 0, 1, 0, -2 ], [ 3, -1, -1, 1, 0, -2 ], [ 3, -1, 0, 0, 0, -2 ] ], 0.125 ] + ], + [ + [ [ [ 1, -1, 0, 1, 0, -2 ], [ 1, -2, 0, 1, 0, -2 ], [ 0, -1, 0, 0, 0, -1 ], [ 3, -1, 0, 0, 0, -2 ] ], 0.875 ], + [ [ [ 1, -1, 0, 1, 0, -2 ], [ 1, -2, 0, 1, 0, -2 ], [ 0, -1, 0, 0, 0, -1 ], [ "Rest" ] ], 0.375 ], + [ [ [ 1, -1, 0, 1, 0, -2 ], [ 1, -2, 0, 1, 0, -2 ], [ "Rest" ], [ "Rest" ] ], 0.5 ], + [ [ [ 1, -1, 0, 1, 0, -2 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0.625 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 6.5 ] + ] + ] +], +"last_changes": +[ + [ [ 1, -1, 0, 1, 0, -2 ], [ 0, -1, 0, 0, 0, -1 ], [ 3, -1, 1, 0, 0, -2 ], [ 3, -1, 0, 0, 0, -2 ] ], + [ [ 1, -1, 0, 1, 0, -2 ], [ 0, -1, 0, 0, 0, -1 ], [ 3, -1, 0, 1, -1, -2 ], [ 3, -1, 0, 0, 0, -2 ] ], + [ [ 1, -1, 0, 1, 0, -2 ], [ 1, -2, 0, 1, 0, -2 ], [ 3, -1, 0, 1, -1, -2 ], [ 3, -1, 0, 0, 0, -2 ] ], + [ [ 1, -1, 0, 1, 0, -2 ], [ 1, -2, 0, 1, 0, -2 ], [ 3, -1, -1, 1, 0, -2 ], [ 3, -1, 0, 0, 0, -2 ] ], + [ [ -2, -1, 0, 1, 0, -2 ], [ -2, -2, 0, 1, 0, -2 ], [ -2, -1, 0, 0, 0, -1 ], [ -2, -1, 0, 0, 0, -2 ] ] +], +"cur_uid": "7c2de94c", +"ref_uid": "5201b8af", +"order_seed": 869423, +"dur_seed": 680450, +"motifs_seed": 533263, +"entrances_probs_vals": [ 0, 0, 0, 0, 1.2087912087912, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 0, 1.2087912087912, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 0, 1.2087912087912, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2169.6594427245, 338 ], [ -2522.600619195, 1453 ], [ -1649.5356037152, 1676 ], [ -2894.1176470588, 1694 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.063786008230453, 0.92613636363636, 0.12757201646091, 0, 0.54732510288066, 0, 0.71604938271605, 0, 1, 0 ], +"passages_weights": [ 1, 0, 0.2, 0.74, 0.68 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ] +], +"sus_weights": [ 0, 0, 0.61 ], +"order_size": [ 30, 30 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/7e230015/7e230015_code.scd b/resources/string_quartet_3/7e230015/7e230015_code.scd new file mode 100644 index 0000000..7c7b526 --- /dev/null +++ b/resources/string_quartet_3/7e230015/7e230015_code.scd @@ -0,0 +1,975 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-10); +}; + +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/string_quartet_3/7e230015/7e230015_mus_model.json b/resources/string_quartet_3/7e230015/7e230015_mus_model.json new file mode 100644 index 0000000..7948320 --- /dev/null +++ b/resources/string_quartet_3/7e230015/7e230015_mus_model.json @@ -0,0 +1,532 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 1 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 0, -1, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 0, -1, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, -1, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 0, 0, 0, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 0, 0, 0, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 0, -1, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 0, -1, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 0, 0, 0, 0 ], [ -1, 0, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ -1, 0, 1, 0, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 0, 0, 0, 0 ], [ -1, 0, 1, 0, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 0, 0, 0, 0 ], [ -1, 0, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 1, 1, 0, 0, 0 ], [ -1, 0, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 1, 1, 0, 0, 0 ], [ -1, 0, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 1, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 1, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 1, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 1, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ -1, 0, 2, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 1, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ], [ -1, 0, 2, 1, 0, 0 ], [ -1, 0, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 1, 0, 0, 0 ], [ 0, -1, 2, 0, 0, 0 ], [ -1, 0, 2, 1, 0, 0 ], [ -1, 0, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -1, -1, 2, 0, 0, 0 ], [ 0, -1, 2, 0, 0, 0 ], [ -1, 0, 2, 1, 0, 0 ], [ -1, 0, 2, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, -1, 2, 1, 0, 0 ], [ 0, -1, 2, 0, 0, 0 ], [ -1, 0, 2, 1, 0, 0 ], [ -1, 0, 2, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, -1, 2, 1, 0, 0 ], [ -1, 1, 2, 0, 0, 0 ], [ -1, 0, 2, 1, 0, 0 ], [ -1, 0, 2, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, -1, 2, 1, 0, 0 ], [ -1, 1, 2, 0, 0, 0 ], [ -1, 0, 2, 1, 0, 0 ], [ -2, 0, 2, 1, 0, 0 ] ], 1 ], + [ [ [ -2, 1, 2, 1, 0, 0 ], [ -1, 1, 2, 0, 0, 0 ], [ -1, 0, 2, 1, 0, 0 ], [ -2, 0, 2, 1, 0, 0 ] ], 1 ], + [ [ [ -2, 1, 2, 1, 0, 0 ], [ -1, -1, 2, 1, 0, 0 ], [ -1, 0, 2, 1, 0, 0 ], [ -2, 0, 2, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 2, 1, 0, 0 ], [ -1, -1, 2, 1, 0, 0 ], [ -1, 0, 2, 1, 0, 0 ], [ -1, -1, 1, 1, 0, 0 ] ], 1 ], + [ [ [ -1, -1, 3, 1, 0, 0 ], [ -1, -1, 2, 1, 0, 0 ], [ -1, 0, 2, 1, 0, 0 ], [ -1, -1, 1, 1, 0, 0 ] ], 1 ], + [ [ [ -1, -1, 3, 1, 0, 0 ], [ -1, -1, 2, 1, 0, 0 ], [ 0, -1, 1, 1, 0, 0 ], [ -1, -1, 1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, -1, 3, 1, 0, 0 ], [ -1, -1, 2, 1, 0, 0 ], [ 0, -1, 1, 1, 0, 0 ], [ -1, -1, 1, 1, 0, 1 ] ], 1 ], + [ [ [ -1, -1, 3, 1, 0, 0 ], [ 0, -2, 1, 1, 0, 0 ], [ 0, -1, 1, 1, 0, 0 ], [ -1, -1, 1, 1, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -1, -1, 3, 1, 0, 0 ], [ 0, -2, 1, 1, 0, 0 ], [ 0, -2, 1, 1, 0, 1 ], [ -1, -1, 1, 1, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -1, -1, 3, 1, 0, 0 ], [ 0, -2, 1, 1, 0, 0 ], [ 0, -2, 1, 1, 0, 1 ], [ 1, -3, 1, 1, 0, 0 ] ], 1 ], + [ [ [ 0, -2, 2, 1, 0, 0 ], [ 0, -2, 1, 1, 0, 0 ], [ 0, -2, 1, 1, 0, 1 ], [ 1, -3, 1, 1, 0, 0 ] ], 1 ], + [ [ [ 0, -2, 2, 1, 0, 0 ], [ 0, -2, 1, 1, 0, 0 ], [ 0, -2, 1, 2, 0, 0 ], [ 1, -3, 1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, -2, 2, 1, 0, 0 ], [ 0, -2, 1, 1, 0, 0 ], [ 0, -2, 1, 2, 0, 0 ], [ 1, -2, 1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, -2, 2, 1, 0, 0 ], [ 0, -2, 1, 1, 0, 0 ], [ 0, -2, 1, 2, 0, 0 ], [ 0, -2, 0, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, -2, 1, 2, 0, 0 ], [ 0, -2, 1, 1, 0, 0 ], [ 0, -2, 1, 2, 0, 0 ], [ 0, -2, 0, 2, 0, 0 ] ], 1 ], + [ [ [ -1, -2, 1, 2, 0, 0 ], [ 0, -2, 1, 1, 0, 0 ], [ 0, -2, 1, 2, 0, 0 ], [ -1, -2, 1, 3, 0, 0 ] ], 1 ], + [ [ [ -1, -2, 1, 2, 0, 0 ], [ -1, -3, 1, 2, 0, 0 ], [ 0, -2, 1, 2, 0, 0 ], [ -1, -2, 1, 3, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, -2, 1, 2, 0, 0 ], [ -1, -3, 1, 2, 0, 0 ], [ 0, -2, 1, 2, 0, 0 ], [ 0, -3, 1, 2, 0, 0 ] ], 1 ], + [ [ [ -1, -2, 1, 2, 0, 0 ], [ -2, -1, 1, 2, 0, 0 ], [ 0, -2, 1, 2, 0, 0 ], [ 0, -3, 1, 2, 0, 0 ] ], 1 ], + [ [ [ -1, -2, 1, 2, 0, 0 ], [ -2, -1, 1, 2, 0, 0 ], [ 1, -2, 1, 1, 0, 0 ], [ 0, -3, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, -2, 1, 2, 0, 0 ], [ -2, -1, 1, 2, 0, 0 ], [ 1, -2, 1, 1, 0, 0 ], [ -1, -1, 1, 2, 0, 0 ] ], 1 ], + [ [ [ 0, -2, 1, 1, 0, 0 ], [ -2, -1, 1, 2, 0, 0 ], [ 1, -2, 1, 1, 0, 0 ], [ -1, -1, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 1, 2, 0, 0 ], [ -2, -1, 1, 2, 0, 0 ], [ 1, -2, 1, 1, 0, 0 ], [ -1, -1, 1, 2, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 1, 2, 0, 0 ], [ -2, -1, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ], [ -1, -1, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, -1, 1, 2, 0, 0 ], [ -2, -1, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ], [ -1, -1, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, -1, 1, 2, 0, 0 ], [ -2, -1, 1, 2, 0, 0 ], [ -1, -1, 0, 2, 0, 0 ], [ -1, -1, 1, 2, 0, 0 ] ], 1 ], + [ [ [ -3, -1, 1, 2, 0, 0 ], [ -2, -1, 1, 2, 0, 0 ], [ -1, -1, 0, 2, 0, 0 ], [ -2, -1, 2, 2, 0, 0 ] ], 1 ], + [ [ [ -3, -1, 1, 2, 0, 0 ], [ -1, -1, 1, 1, 0, 0 ], [ -1, -1, 0, 2, 0, 0 ], [ -2, -1, 2, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, -1, 1, 2, 0, 0 ], [ -1, -1, 1, 1, 0, 0 ], [ -1, -1, 2, 2, 0, 0 ], [ -2, -1, 2, 2, 0, 0 ] ], 1 ], + [ [ [ -3, -1, 1, 2, 0, 0 ], [ -2, -2, 2, 2, 0, 0 ], [ -1, -1, 2, 2, 0, 0 ], [ -2, -1, 2, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, -1, 1, 2, 0, 0 ], [ -2, -2, 2, 2, 0, 0 ], [ -1, -1, 2, 2, 0, 0 ], [ -1, -2, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, -1, 1, 2, 0, 0 ], [ -2, -2, 2, 2, 0, 0 ], [ -1, -1, 2, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ] ], 1 ], + [ [ [ -3, -1, 1, 2, 0, 0 ], [ -2, -2, 2, 2, 0, 0 ], [ 0, -2, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ] ], 1 ], + [ [ [ -3, -1, 1, 2, 0, 0 ], [ -2, -1, 2, 2, 0, 0 ], [ 0, -2, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, -1, 1, 2, 0, 0 ], [ -1, -2, 1, 2, 0, 0 ], [ 0, -2, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ] ], 1 ], + [ [ [ -2, -2, 1, 2, 0, 0 ], [ -1, -2, 1, 2, 0, 0 ], [ 0, -2, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, -1, 1, 2, 0, 0 ], [ -1, -2, 1, 2, 0, 0 ], [ 0, -2, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ] ], 1 ], + [ [ [ -2, -1, 1, 2, 0, 0 ], [ -1, -2, 1, 2, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 0, 1, 2, 0, 0 ], [ -1, -2, 1, 2, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ] ], 1 ], + [ [ [ -3, 0, 1, 2, 0, 0 ], [ -2, -1, 1, 2, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 0, 1, 2, 0, 0 ], [ -3, 1, 1, 2, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 0, 1, 2, 0, 0 ], [ -3, 1, 1, 2, 0, 0 ], [ -1, 0, 1, 2, 0, 0 ], [ -3, 2, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 0, 1, 2, 0, 0 ], [ -3, 1, 1, 2, 0, 0 ], [ -2, 1, 1, 2, 0, 0 ], [ -3, 2, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 0, 1, 2, 0, 0 ], [ -3, 1, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ], [ -3, 2, 1, 2, 0, 0 ] ], 1 ], + [ [ [ -3, 0, 1, 2, 0, 0 ], [ -3, 1, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ], [ -2, 1, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 1, 1, 2, 0, 0 ], [ -3, 1, 1, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ], [ -2, 1, 1, 2, 0, 0 ] ], 1 ], + [ [ [ -4, 1, 1, 2, 0, 0 ], [ -2, 0, 0, 2, 0, 0 ], [ -2, 0, 1, 2, 0, 0 ], [ -2, 1, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 1, 1, 2, 0, 0 ], [ -2, 0, 0, 2, 0, 0 ], [ -3, 1, 1, 2, 0, 0 ], [ -2, 1, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 1, 1, 2, 0, 0 ], [ -2, 0, 0, 2, 0, 0 ], [ -2, 1, 1, 1, 0, 0 ], [ -2, 1, 1, 2, 0, 0 ] ], 1 ], + [ [ [ -4, 1, 1, 2, 0, 0 ], [ -4, 2, 1, 2, 0, 0 ], [ -2, 1, 1, 1, 0, 0 ], [ -2, 1, 1, 2, 0, 0 ] ], 1 ], + [ [ [ -3, 1, 1, 1, 0, 0 ], [ -4, 2, 1, 2, 0, 0 ], [ -2, 1, 1, 1, 0, 0 ], [ -2, 1, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 1, 1, 1, 0, 0 ], [ -4, 2, 1, 2, 0, 0 ], [ -2, 1, 1, 1, 0, 0 ], [ -1, 1, 1, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 1, 1, 1, 0, 0 ], [ -3, 2, 1, 1, 0, 0 ], [ -2, 1, 1, 1, 0, 0 ], [ -1, 1, 1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 1, 1, 1, 0, 0 ], [ -3, 2, 1, 1, 0, 0 ], [ -2, 1, 1, 1, 0, 0 ], [ -2, 2, 1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 1, 1, 1, 0, 0 ], [ -3, 2, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 0, 0 ], [ -2, 2, 1, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 1, 1, 1, 0, 0 ], [ -3, 2, 1, 1, 0, 1 ], [ -3, 3, 1, 1, 0, 0 ], [ -2, 2, 1, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 2, 1, 1, 0, 0 ], [ -3, 2, 1, 1, 0, 1 ], [ -3, 3, 1, 1, 0, 0 ], [ -2, 2, 1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 1, 1, 0, 0 ], [ -4, 4, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 0, 0 ], [ -2, 2, 1, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 2, 1, 1, 0, 0 ], [ -4, 4, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 0, 0 ], [ -3, 4, 1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 1, 1, 0, 0 ], [ -4, 4, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 2, 1, 1, 0, 0 ], [ -3, 3, 0, 1, 0, 0 ], [ -3, 3, 1, 1, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 1, 1, 0, 0 ], [ -4, 3, 1, 2, 0, 0 ], [ -3, 3, 1, 1, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 1, 1, 0, 0 ], [ -4, 3, 1, 2, 0, 0 ], [ -4, 3, 2, 2, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 1, 1, 0, 0 ], [ -4, 3, 1, 2, 0, 0 ], [ -3, 2, 1, 2, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 2, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 0, 0 ], [ -3, 2, 1, 2, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 2, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 0, 0 ], [ -3, 2, 1, 2, 0, 0 ], [ -1, 1, 1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 1, 2, 0, 0 ], [ -3, 3, 1, 1, 0, 0 ], [ -3, 2, 1, 2, 0, 0 ], [ -1, 1, 1, 1, 0, 0 ] ], 1 ], + [ [ [ -5, 3, 1, 2, 0, 0 ], [ -2, 2, 0, 2, 0, 0 ], [ -3, 2, 1, 2, 0, 0 ], [ -1, 1, 1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 1, 1, 2, 0, 0 ], [ -2, 2, 0, 2, 0, 0 ], [ -3, 2, 1, 2, 0, 0 ], [ -1, 1, 1, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 1, 1, 2, 0, 0 ], [ -2, 1, 1, 2, 0, 0 ], [ -3, 2, 1, 2, 0, 0 ], [ -1, 1, 1, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 1, 1, 2, 0, 0 ], [ -2, 1, 1, 2, 0, 0 ], [ -2, 1, 1, 1, 0, 0 ], [ -1, 1, 1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 1, 1, 2, 0, 0 ], [ -3, 2, 1, 2, 0, 0 ], [ -2, 1, 1, 1, 0, 0 ], [ -1, 1, 1, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 1, 1, 2, 0, 0 ], [ -3, 2, 1, 2, 0, 0 ], [ -2, 1, 1, 1, 1, 0 ], [ -1, 1, 1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 1, 1, 2, 0, 0 ], [ -3, 2, 1, 2, 0, 0 ], [ -2, 1, 1, 1, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -2, 1, 2, 1, 1, 0 ], [ -3, 2, 1, 2, 0, 0 ], [ -2, 1, 1, 1, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -2, 1, 2, 1, 1, 0 ], [ -3, 1, 1, 1, 1, 0 ], [ -2, 1, 1, 1, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 2, 1, 1, 0 ], [ -4, 1, 2, 2, 1, 0 ], [ -2, 1, 1, 1, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 2, 1, 1, 0 ], [ -3, 1, 2, 1, 1, 0 ], [ -2, 1, 1, 1, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -2, 1, 2, 1, 1, 0 ], [ -3, 1, 2, 1, 1, 0 ], [ -2, 1, 1, 1, 1, 0 ], [ -3, 2, 2, 1, 1, 0 ] ], 1 ], + [ [ [ -2, 1, 2, 1, 1, 0 ], [ -3, 1, 2, 1, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ], [ -3, 2, 2, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 2, 1, 1, 0 ], [ -4, 3, 2, 1, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ], [ -3, 2, 2, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 3, 2, 1, 1, 0 ], [ -4, 3, 2, 1, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ], [ -3, 2, 2, 1, 1, 0 ] ], 1 ], + [ [ [ -3, 3, 2, 1, 1, 0 ], [ -4, 2, 2, 1, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ], [ -3, 2, 2, 1, 1, 0 ] ], 1 ], + [ [ [ -3, 3, 2, 1, 1, 0 ], [ -4, 2, 2, 1, 1, 0 ], [ -4, 3, 2, 1, 1, 0 ], [ -3, 2, 2, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 3, 2, 1, 1, 0 ], [ -5, 4, 2, 1, 1, 0 ], [ -4, 3, 2, 1, 1, 0 ], [ -3, 2, 2, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 4, 2, 1, 1, 0 ], [ -5, 4, 2, 1, 1, 0 ], [ -4, 3, 2, 1, 1, 0 ], [ -3, 2, 2, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 4, 2, 1, 1, 0 ], [ -5, 4, 2, 1, 1, 0 ], [ -4, 3, 2, 1, 1, 0 ], [ -3, 3, 2, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 4, 2, 1, 1, 0 ], [ -4, 3, 1, 1, 1, 0 ], [ -4, 3, 2, 1, 1, 0 ], [ -3, 3, 2, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 4, 2, 1, 1, 0 ], [ -4, 3, 1, 1, 1, 0 ], [ -4, 3, 2, 1, 1, 0 ], [ -3, 3, 1, 1, 2, 0 ] ], 1 ], + [ [ [ -3, 3, 1, 1, 1, 0 ], [ -4, 3, 1, 1, 1, 0 ], [ -4, 3, 2, 1, 1, 0 ], [ -3, 3, 1, 1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 3, 1, 1, 1, 0 ], [ -4, 3, 1, 1, 1, 0 ], [ -4, 3, 2, 1, 1, 0 ], [ -3, 4, 1, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 4, 1, 1, 1, 0 ], [ -4, 3, 1, 1, 1, 0 ], [ -4, 3, 2, 1, 1, 0 ], [ -3, 4, 1, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 4, 1, 1, 1, 0 ], [ -4, 3, 1, 1, 1, 0 ], [ -3, 3, 1, 1, 1, 0 ], [ -3, 4, 1, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 4, 1, 1, 1, 0 ], [ -4, 4, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 1, 0 ], [ -3, 4, 1, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 4, 1, 1, 1, 0 ], [ -4, 4, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 1, 0 ], [ -4, 5, 1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 4, 1, 1, 1, 0 ], [ -4, 4, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 1, 0 ], [ -3, 4, 1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 1, 1, 0, 0 ], [ -4, 4, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 1, 0 ], [ -3, 4, 1, 1, 0, 0 ] ], 1 ], + [ [ [ -2, 3, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 1, 0 ], [ -3, 4, 1, 1, 0, 0 ] ], 1 ], + [ [ [ -2, 3, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 1, 0 ], [ -2, 3, 1, 1, 1, -1 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 1, 1, 0, 0 ], [ -3, 3, 0, 1, 1, 0 ], [ -3, 3, 1, 1, 1, 0 ], [ -2, 3, 1, 1, 1, -1 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 1, 1, 0, 0 ], [ -2, 3, 1, 1, 0, -1 ], [ -3, 3, 1, 1, 1, 0 ], [ -2, 3, 1, 1, 1, -1 ] ], 1 ], + [ [ [ -2, 3, 1, 1, 0, 0 ], [ -2, 3, 1, 1, 0, -1 ], [ -3, 4, 1, 1, 0, 0 ], [ -2, 3, 1, 1, 1, -1 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 1, 0 ], [ -3, 4, 1, 1, 0, 0 ], [ -2, 3, 1, 1, 1, -1 ] ], 1 ], + [ [ [ -2, 3, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 1, 0 ], [ -3, 4, 1, 1, 0, 0 ], [ -4, 4, 1, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 1, 0 ], [ -3, 4, 1, 1, 0, 0 ], [ -3, 3, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 1, 1, 0, 0 ], [ -3, 3, 1, 1, 1, 0 ], [ -3, 4, 0, 1, 1, 0 ], [ -3, 3, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 1, 1, 0, 0 ], [ -3, 4, 1, 1, 1, 0 ], [ -3, 4, 0, 1, 1, 0 ], [ -3, 3, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 3, 1, 1, 1, 0 ], [ -3, 4, 1, 1, 1, 0 ], [ -3, 4, 0, 1, 1, 0 ], [ -3, 3, 0, 1, 1, 0 ] ], 1 ], + [ [ [ -3, 3, 1, 1, 1, 0 ], [ -3, 4, 1, 1, 1, 0 ], [ -4, 4, 1, 1, 1, 0 ], [ -3, 3, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 4, 0, 1, 1, 0 ], [ -3, 4, 1, 1, 1, 0 ], [ -4, 4, 1, 1, 1, 0 ], [ -3, 3, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 4, 0, 1, 1, 0 ], [ -3, 4, 1, 1, 1, 0 ], [ -4, 4, 1, 1, 1, 0 ], [ -4, 4, 0, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 4, 1, 2, 1, 0 ], [ -3, 4, 1, 1, 1, 0 ], [ -4, 4, 1, 1, 1, 0 ], [ -4, 4, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 5, 1, 1, 1, 0 ], [ -3, 4, 1, 1, 1, 0 ], [ -4, 4, 1, 1, 1, 0 ], [ -4, 4, 0, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 5, 1, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ], [ -4, 4, 1, 1, 1, 0 ], [ -4, 4, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 4, 0, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ], [ -4, 4, 1, 1, 1, 0 ], [ -4, 4, 0, 1, 1, 0 ] ], 1 ], + [ [ [ -3, 4, 0, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ], [ -4, 4, 1, 1, 1, 0 ], [ -4, 5, 1, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 4, 0, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ], [ -4, 4, 1, 1, 1, 0 ], [ -3, 4, 1, 1, 1, 0 ] ], 1 ], + [ [ [ -3, 4, 0, 1, 1, 0 ], [ -4, 4, 0, 1, 1, 0 ], [ -4, 4, 1, 1, 1, 0 ], [ -3, 4, 1, 1, 1, 0 ] ], 1 ], + [ [ [ -3, 4, 0, 1, 1, 0 ], [ -4, 4, 0, 1, 1, 0 ], [ -3, 3, 0, 1, 1, 0 ], [ -3, 4, 1, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 4, 0, 1, 1, 0 ], [ -4, 4, 0, 1, 1, 0 ], [ -3, 3, 0, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ] ], 1 ], + [ [ [ -5, 4, 0, 1, 1, 0 ], [ -4, 4, 0, 1, 1, 0 ], [ -3, 3, 0, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 4, 0, 1, 1, 0 ], [ -4, 4, 0, 1, 1, 0 ], [ -3, 3, 0, 1, 1, 0 ], [ -3, 4, 0, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 3, 0, 1, 1, 0 ], [ -4, 4, 0, 1, 1, 0 ], [ -3, 3, 0, 1, 1, 0 ], [ -3, 4, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 3, 0, 1, 1, 0 ], [ -4, 4, 0, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ], [ -3, 4, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 3, 0, 1, 1, 0 ], [ -3, 3, -1, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ], [ -3, 4, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -6, 5, 0, 1, 1, 0 ], [ -3, 3, -1, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ], [ -3, 4, 0, 1, 1, 0 ] ], 1 ], + [ [ [ -6, 5, 0, 1, 1, 0 ], [ -3, 3, -1, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ], [ -4, 6, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -6, 5, 0, 1, 1, 0 ], [ -3, 3, -1, 1, 1, 0 ], [ -5, 6, -1, 1, 1, 0 ], [ -4, 6, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -6, 6, -1, 1, 1, 0 ], [ -3, 3, -1, 1, 1, 0 ], [ -5, 6, -1, 1, 1, 0 ], [ -4, 6, 0, 1, 1, 0 ] ], 1 ], + [ [ [ -6, 6, -1, 1, 1, 0 ], [ -3, 3, -1, 1, 1, 0 ], [ -5, 6, 0, 1, 1, 0 ], [ -4, 6, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -6, 6, -1, 1, 1, 0 ], [ -5, 6, -1, 1, 1, 0 ], [ -5, 6, 0, 1, 1, 0 ], [ -4, 6, 0, 1, 1, 0 ] ], 1 ], + [ [ [ -6, 6, 0, 1, 1, 0 ], [ -5, 6, -1, 1, 1, 0 ], [ -5, 6, 0, 1, 1, 0 ], [ -4, 6, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 0, 1, 1, 0 ], [ -5, 6, -1, 1, 1, 0 ], [ -5, 6, 0, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ] ], 1 ], + [ [ [ -6, 6, 0, 1, 1, 0 ], [ -5, 6, -1, 1, 1, 0 ], [ -4, 6, -1, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 5, -1, 1, 1, 0 ], [ -5, 6, -1, 1, 1, 0 ], [ -4, 6, -1, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ] ], 1 ], + [ [ [ -5, 5, -1, 1, 1, 0 ], [ -4, 5, -1, 1, 1, 0 ], [ -4, 6, -1, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 5, -1, 1, 1, 0 ], [ -4, 5, -1, 1, 1, 0 ], [ -3, 5, -2, 1, 1, 0 ], [ -4, 5, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 5, -1, 1, 1, 0 ], [ -4, 5, -1, 1, 1, 0 ], [ -3, 5, -2, 1, 1, 0 ], [ -3, 5, -1, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 5, -2, 1, 1, 0 ], [ -4, 5, -1, 1, 1, 0 ], [ -3, 5, -2, 1, 1, 0 ], [ -3, 5, -1, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 5, -2, 1, 1, 0 ], [ -4, 5, -1, 1, 1, 0 ], [ -3, 5, -2, 1, 1, 0 ], [ -4, 6, -1, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 5, -2, 1, 1, 0 ], [ -4, 5, -1, 1, 1, 0 ], [ -4, 5, -1, 2, 1, 0 ], [ -4, 6, -1, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 5, -2, 1, 1, 0 ], [ -4, 5, -1, 1, 1, 0 ], [ -4, 5, -1, 2, 1, 0 ], [ -3, 5, -2, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 5, -2, 1, 1, 0 ], [ -4, 5, -1, 1, 1, 0 ], [ -3, 5, -1, 1, 1, 0 ], [ -3, 5, -2, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 5, -2, 1, 1, 0 ], [ -4, 5, -1, 1, 1, 0 ], [ -2, 4, -2, 1, 1, 0 ], [ -3, 5, -2, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 5, -2, 1, 1, 0 ], [ -4, 5, -1, 1, 1, 0 ], [ -2, 4, -2, 1, 1, 0 ], [ -3, 4, -2, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 5, -2, 1, 1, 0 ], [ -3, 5, -2, 1, 1, 0 ], [ -2, 4, -2, 1, 1, 0 ], [ -3, 4, -2, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 5, -2, 1, 1, 0 ], [ -2, 5, -2, 0, 1, 0 ], [ -2, 4, -2, 1, 1, 0 ], [ -3, 4, -2, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 5, -2, 1, 1, 0 ], [ -2, 5, -2, 0, 1, 0 ], [ -2, 4, -2, 1, 1, 0 ], [ -4, 6, -2, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 5, -2, 1, 1, 0 ], [ -2, 5, -2, 0, 1, 0 ], [ -3, 5, -2, 1, 1, 0 ], [ -4, 6, -2, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 5, -2, 1, 1, 0 ], [ -2, 5, -2, 0, 1, 0 ], [ -3, 5, -2, 1, 1, 0 ], [ -4, 6, -2, 1, 1, 0 ] ], 1 ], + [ [ [ -5, 5, -2, 1, 1, 0 ], [ -4, 5, -2, 1, 1, 0 ], [ -3, 5, -2, 1, 1, 0 ], [ -4, 6, -2, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 5, -2, 1, 1, 0 ], [ -4, 5, -2, 1, 1, 0 ], [ -4, 7, -2, 1, 1, 0 ], [ -4, 6, -2, 1, 1, 0 ] ], 1 ], + [ [ [ -5, 5, -2, 1, 1, 0 ], [ -5, 6, -2, 1, 1, 0 ], [ -4, 7, -2, 1, 1, 0 ], [ -4, 6, -2, 1, 1, 0 ] ], 1 ], + [ [ [ -6, 6, -2, 1, 1, 0 ], [ -5, 6, -2, 1, 1, 0 ], [ -4, 7, -2, 1, 1, 0 ], [ -4, 6, -2, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -6, 7, -2, 1, 1, 0 ], [ -5, 6, -2, 1, 1, 0 ], [ -4, 7, -2, 1, 1, 0 ], [ -4, 6, -2, 1, 1, 0 ] ], 1 ], + [ [ [ -6, 7, -2, 1, 1, 0 ], [ -5, 6, -2, 1, 1, 0 ], [ -5, 7, -2, 1, 1, 0 ], [ -4, 6, -2, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -6, 7, -2, 1, 1, 0 ], [ -5, 6, -2, 1, 1, 0 ], [ -5, 7, -2, 1, 1, 0 ], [ -4, 7, -2, 1, 1, 0 ] ], 1 ], + [ [ [ -6, 7, -2, 1, 1, 0 ], [ "Rest" ], [ -5, 7, -2, 1, 1, 0 ], [ -4, 7, -2, 1, 1, 0 ] ], 1 ], + [ [ [ -6, 7, -2, 1, 1, 0 ], [ "Rest" ], [ -5, 7, -2, 1, 1, 0 ], [ "Rest" ] ], 1 ], + [ [ [ -6, 7, -2, 1, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -5, 5, -2, 1, 1, 0 ], [ -5, 6, -2, 1, 1, 0 ], [ -4, 7, -2, 1, 1, 0 ], [ -4, 6, -2, 1, 1, 0 ] ], + [ [ -6, 6, -2, 1, 1, 0 ], [ -5, 6, -2, 1, 1, 0 ], [ -4, 7, -2, 1, 1, 0 ], [ -4, 6, -2, 1, 1, 0 ] ], + [ [ -6, 7, -2, 1, 1, 0 ], [ -5, 6, -2, 1, 1, 0 ], [ -4, 7, -2, 1, 1, 0 ], [ -4, 6, -2, 1, 1, 0 ] ], + [ [ -6, 7, -2, 1, 1, 0 ], [ -5, 6, -2, 1, 1, 0 ], [ -5, 7, -2, 1, 1, 0 ], [ -4, 6, -2, 1, 1, 0 ] ], + [ [ -6, 7, -2, 1, 1, 0 ], [ -5, 6, -2, 1, 1, 0 ], [ -5, 7, -2, 1, 1, 0 ], [ -4, 7, -2, 1, 1, 0 ] ] +], +"cur_uid": "7e230015", +"ref_uid": "nil", +"order_seed": 389930, +"dur_seed": 736878, +"motifs_seed": 479673, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ -1200, 1200, 0.0020576131687243, 0.068181818181818, 0.074074074074074, 0.0625, 0.20576131687243, 0.0625, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.78600823045268, 0.068181818181818, 0.98971193415638, 0.0625 ], +"passages_weights": [ 1, 1, 0.48, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 0, 3 ], [ 2, 1 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ], + [ [ 2 ], [ 1, 3, 0 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 0, 3 ], [ 1, 2 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 2 ], [ 0, 3, 1 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 2, 0 ], [ 3, 1 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 2 ], [ 0, 3, 1 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0, 3 ], [ 2, 1 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 2, 0 ], [ 3, 1 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 0, 2 ], [ 1, 3 ], [ ] ], + [ [ 0, 2 ], [ 3, 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 3, 0 ], [ 1, 2 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 2 ], [ 0, 3, 1 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0, 3 ], [ 1, 2 ], [ ] ], + [ [ 2, 0 ], [ 1, 3 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 1, 2 ], [ 0, 3 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 2, 1 ], [ 0, 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 100, 100 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3/tmp/tmp_mus_model.json b/resources/string_quartet_3/tmp/tmp_mus_model.json new file mode 100644 index 0000000..487fc67 --- /dev/null +++ b/resources/string_quartet_3/tmp/tmp_mus_model.json @@ -0,0 +1,532 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 1 ], + [ [ [ "Rest" ], [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 2.375 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 3.5 ] + ], + [ + [ [ [ 0, 0, 0, -1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, -1, 0, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 2.5 ] + ], + [ + [ [ [ 0, 0, 0, -1, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1.375 ] + ], + [ + [ [ [ 0, 0, 0, -1, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, -1, 1, 0, 0, 0 ], [ 0, 0, 1, -1, 0, 0 ] ], 1.875 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ], [ 0, 0, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 1, -1, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ], [ 0, 0, 1, -1, 0, 0 ] ], 3.375 ] + ], + [ + [ [ [ 1, -1, 1, 0, 0, 0 ], [ 2, -1, 1, -1, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ], [ 0, 0, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 1, -1, 1, 0, 0, 0 ], [ 2, -1, 1, -1, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ], [ 1, -1, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 2, 0, 1, -1, 0, 0 ], [ 2, -1, 1, -1, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ], [ 1, -1, 1, -1, 0, 0 ] ], 1.5 ] + ], + [ + [ [ [ 2, 0, 1, -1, 0, 0 ], [ 2, -1, 1, -1, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ], [ 2, -1, 1, -2, 0, 0 ] ], 1 ], + [ [ [ 3, 0, 1, -2, 0, 0 ], [ 2, -1, 1, -1, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ], [ 2, -1, 1, -2, 0, 0 ] ], 1.75 ] + ], + [ + [ [ [ 3, 0, 1, -2, 0, 0 ], [ 1, 1, 1, -2, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ], [ 2, -1, 1, -2, 0, 0 ] ], 1 ], + [ [ [ 3, 0, 1, -2, 0, 0 ], [ 1, 1, 1, -2, 0, 0 ], [ 1, 0, 1, -2, 0, 0 ], [ 2, -1, 1, -2, 0, 0 ] ], 3.625 ] + ], + [ + [ [ [ 3, 0, 1, -2, 0, 0 ], [ 1, 1, 1, -2, 0, 0 ], [ 1, 0, 1, -2, 0, 0 ], [ 0, 1, 1, -2, 0, 0 ] ], 1.5 ] + ], + [ + [ [ [ 3, 0, 1, -2, 0, 0 ], [ 1, 1, 1, -2, 0, 0 ], [ 0, 2, 1, -2, 0, 0 ], [ 0, 1, 1, -2, 0, 0 ] ], 3 ] + ], + [ + [ [ [ 2, 1, 1, -2, 0, 0 ], [ 1, 1, 1, -2, 0, 0 ], [ 0, 2, 1, -2, 0, 0 ], [ 0, 1, 1, -2, 0, 0 ] ], 1 ], + [ [ [ 2, 1, 1, -2, 0, 0 ], [ 1, 1, 1, -2, 0, 0 ], [ 0, 2, 1, -2, 0, 0 ], [ -1, 3, 1, -2, 0, 0 ] ], 1 ], + [ [ [ 2, 1, 1, -2, 0, 0 ], [ 1, 2, 1, -2, 0, 0 ], [ 0, 2, 1, -2, 0, 0 ], [ -1, 3, 1, -2, 0, 0 ] ], 1.75 ] + ], + [ + [ [ [ 1, 3, 1, -2, 0, 0 ], [ 1, 2, 1, -2, 0, 0 ], [ 0, 2, 1, -2, 0, 0 ], [ -1, 3, 1, -2, 0, 0 ] ], 1 ], + [ [ [ 1, 3, 1, -2, 0, 0 ], [ 0, 3, 1, -2, 0, 0 ], [ 0, 2, 1, -2, 0, 0 ], [ -1, 3, 1, -2, 0, 0 ] ], 2.5 ] + ], + [ + [ [ [ 1, 3, 1, -2, 0, 0 ], [ 0, 3, 1, -2, 0, 0 ], [ 0, 2, 1, -2, 0, 0 ], [ 0, 2, 0, -2, 0, 0 ] ], 1.75 ] + ], + [ + [ [ [ 1, 3, 1, -2, 0, 0 ], [ 0, 3, 1, -2, 0, 0 ], [ 0, 2, 1, -2, 0, 0 ], [ 0, 1, 1, -2, 0, 0 ] ], 1.125 ] + ], + [ + [ [ [ 1, 3, 1, -2, 0, 0 ], [ 0, 3, 1, -2, 0, 0 ], [ -1, 1, 1, -1, 0, 0 ], [ 0, 1, 1, -2, 0, 0 ] ], 1 ], + [ [ [ 1, 3, 1, -2, 0, 0 ], [ 0, 2, 1, -2, 0, 0 ], [ -1, 1, 1, -1, 0, 0 ], [ 0, 1, 1, -2, 0, 0 ] ], 1 ], + [ [ [ 1, 2, 1, -2, 0, 0 ], [ 0, 2, 1, -2, 0, 0 ], [ -1, 1, 1, -1, 0, 0 ], [ 0, 1, 1, -2, 0, 0 ] ], 1.125 ] + ], + [ + [ [ [ 1, 1, 1, -2, 0, 1 ], [ 0, 2, 1, -2, 0, 0 ], [ -1, 1, 1, -1, 0, 0 ], [ 0, 1, 1, -2, 0, 0 ] ], 3.25 ] + ], + [ + [ [ [ 1, 1, 1, -2, 0, 1 ], [ 0, 1, 1, -2, 0, 1 ], [ -1, 1, 1, -1, 0, 0 ], [ 0, 1, 1, -2, 0, 0 ] ], 1.875 ] + ], + [ + [ [ [ 1, 1, 1, -2, 0, 1 ], [ 0, 1, 1, -2, 0, 1 ], [ -1, 1, 1, -1, 0, 0 ], [ -1, 1, 1, -1, 0, 1 ] ], 1 ], + [ [ [ 1, 1, 1, -1, 0, 0 ], [ 0, 1, 1, -2, 0, 1 ], [ -1, 1, 1, -1, 0, 0 ], [ -1, 1, 1, -1, 0, 1 ] ], 1 ], + [ [ [ 1, 1, 1, -1, 0, 0 ], [ 0, 1, 1, -1, 0, 0 ], [ -1, 1, 1, -1, 0, 0 ], [ -1, 1, 1, -1, 0, 1 ] ], 2.25 ] + ], + [ + [ [ [ 1, 1, 1, -1, 0, 0 ], [ 0, 1, 1, -1, 0, 0 ], [ -1, 1, 1, -1, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 2, 1, -1, 0, 0 ], [ 0, 1, 1, -1, 0, 0 ], [ -1, 1, 1, -1, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 2, 1, -1, 0, 0 ], [ 0, 1, 1, -1, 0, 0 ], [ -1, 2, 1, -1, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ] ], 2.625 ] + ], + [ + [ [ [ 0, 2, 1, -1, 0, 0 ], [ 0, 1, 1, -1, 0, 0 ], [ -1, 2, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 2, 1, -1, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ -1, 2, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ] ], 2.75 ] + ], + [ + [ [ [ 0, 2, 1, -1, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ] ], 3.5 ] + ], + [ + [ [ [ 0, 2, 1, -1, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ], [ -1, 3, 0, -1, 0, 0 ] ], 1 ], + [ [ [ -1, 3, 1, -1, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ], [ -1, 3, 0, -1, 0, 0 ] ], 1 ], + [ [ [ -1, 3, 1, -1, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ -3, 3, 1, 0, 0, 0 ], [ -1, 3, 0, -1, 0, 0 ] ], 1.875 ] + ], + [ + [ [ [ -1, 3, 1, -1, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ -3, 3, 1, 0, 0, 0 ], [ -2, 3, 1, 0, 0, 0 ] ], 2.75 ] + ], + [ + [ [ [ -1, 3, 1, -1, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ -3, 3, 1, 0, 0, 0 ], [ -2, 3, 2, -1, 0, 0 ] ], 2.375 ] + ], + [ + [ [ [ -2, 3, 2, 0, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ -3, 3, 1, 0, 0, 0 ], [ -2, 3, 2, -1, 0, 0 ] ], 1 ], + [ [ [ -2, 3, 2, 0, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ -3, 3, 1, 0, 0, 0 ], [ -2, 3, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 3, 2, 0, 0, 0 ], [ -3, 3, 2, 0, 0, 0 ], [ -3, 3, 1, 0, 0, 0 ], [ -2, 3, 0, 0, 0, 0 ] ], 3.25 ] + ], + [ + [ [ [ -2, 3, 2, 0, 0, 0 ], [ -3, 3, 2, 0, 0, 0 ], [ -3, 3, 1, 0, 0, 0 ], [ -2, 3, 2, 0, -1, 0 ] ], 1 ], + [ [ [ -2, 3, 2, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -3, 3, 1, 0, 0, 0 ], [ -2, 3, 2, 0, -1, 0 ] ], 1 ], + [ [ [ -2, 3, 2, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -4, 3, 2, 1, 0, 0 ], [ -2, 3, 2, 0, -1, 0 ] ], 1.125 ] + ], + [ + [ [ [ -2, 3, 2, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -4, 3, 2, 1, 0, 0 ], [ -3, 2, 2, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 4, 1, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -4, 3, 2, 1, 0, 0 ], [ -3, 2, 2, 1, 0, 0 ] ], 1.25 ] + ], + [ + [ [ [ -3, 3, 2, 1, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -4, 3, 2, 1, 0, 0 ], [ -3, 2, 2, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 3, 2, 1, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -3, 3, 2, 0, 0, 0 ], [ -3, 2, 2, 1, 0, 0 ] ], 2.375 ] + ], + [ + [ [ [ -3, 4, 3, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -3, 3, 2, 0, 0, 0 ], [ -3, 2, 2, 1, 0, 0 ] ], 3 ] + ], + [ + [ [ [ -3, 4, 3, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -3, 4, 3, -1, 0, 0 ], [ -3, 2, 2, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 4, 3, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -3, 4, 3, -1, 0, 0 ], [ -3, 3, 3, 0, 0, 0 ] ], 1 ], + [ [ [ -3, 4, 3, 0, 0, 0 ], [ -2, 3, 3, 0, 0, 0 ], [ -3, 4, 3, -1, 0, 0 ], [ -3, 3, 3, 0, 0, 0 ] ], 2.75 ] + ], + [ + [ [ [ -3, 4, 3, 0, 0, 0 ], [ -2, 3, 3, 0, 0, 0 ], [ -4, 4, 3, 0, 0, 0 ], [ -3, 3, 3, 0, 0, 0 ] ], 1 ], + [ [ [ -3, 4, 3, 0, 0, 0 ], [ -3, 5, 3, 0, 0, 0 ], [ -4, 4, 3, 0, 0, 0 ], [ -3, 3, 3, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 4, 3, 0, 0, 0 ], [ -3, 5, 3, 0, 0, 0 ], [ -4, 4, 3, 0, 0, 0 ], [ -4, 5, 3, 0, 0, 0 ] ], 3.5 ] + ], + [ + [ [ [ -3, 4, 3, 0, 0, 0 ], [ -3, 5, 3, 0, 0, 0 ], [ -4, 4, 3, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -3, 4, 3, 0, 0, 0 ], [ -3, 5, 3, 0, 0, 0 ], [ -3, 3, 3, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -3, 4, 3, 0, 0, 0 ], [ -2, 4, 2, 0, 0, 0 ], [ -3, 3, 3, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ] ], 1.875 ] + ], + [ + [ [ [ -3, 4, 3, 0, 0, 0 ], [ -2, 3, 3, 0, 0, 0 ], [ -3, 3, 3, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 3, 2, 0, 0, 0 ], [ -2, 3, 3, 0, 0, 0 ], [ -3, 3, 3, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ] ], 2.5 ] + ], + [ + [ [ [ -2, 4, 3, 0, 0, 0 ], [ -2, 3, 3, 0, 0, 0 ], [ -3, 3, 3, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 4, 3, 0, 0, 0 ], [ -2, 3, 3, 0, 0, 0 ], [ -3, 4, 3, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ] ], 3.25 ] + ], + [ + [ [ [ -1, 3, 2, 0, 0, 0 ], [ -2, 3, 3, 0, 0, 0 ], [ -3, 4, 3, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 3, 2, 0, 0, 0 ], [ -3, 5, 3, 0, 0, 0 ], [ -3, 4, 3, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ] ], 2.625 ] + ], + [ + [ [ [ -1, 3, 2, 0, 0, 0 ], [ -2, 4, 2, 0, 0, 0 ], [ -3, 4, 3, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ] ], 1.125 ] + ], + [ + [ [ [ -1, 3, 2, 0, 0, 0 ], [ -2, 4, 2, 0, 0, 0 ], [ -3, 4, 3, 0, 0, 0 ], [ -3, 3, 2, 0, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ -1, 3, 2, 0, 0, 0 ], [ -2, 4, 2, 0, 0, 0 ], [ -2, 3, 2, 0, 0, 0 ], [ -3, 3, 2, 0, 0, 0 ] ], 2.75 ] + ], + [ + [ [ [ -1, 3, 2, 0, 0, 0 ], [ -2, 4, 2, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -3, 3, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 3, 2, 0, 0, 0 ], [ -2, 4, 2, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -4, 5, 2, 0, 0, 0 ] ], 2 ] + ], + [ + [ [ [ -2, 5, 2, 0, 0, 0 ], [ -2, 4, 2, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -4, 5, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 5, 2, 0, 0, 0 ], [ -3, 5, 2, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -4, 5, 2, 0, 0, 0 ] ], 3.25 ] + ], + [ + [ [ [ -2, 5, 2, 0, 0, 0 ], [ -3, 5, 2, 0, 0, 0 ], [ -4, 6, 2, 0, 0, 0 ], [ -4, 5, 2, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 5, 2, 0, 0, 0 ], [ -3, 5, 2, 0, 0, 0 ], [ -3, 5, 1, 0, 0, 0 ], [ -4, 5, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 5, 2, 0, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -3, 5, 1, 0, 0, 0 ], [ -4, 5, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 5, 2, -1, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -3, 5, 1, 0, 0, 0 ], [ -4, 5, 2, 0, 0, 0 ] ], 1.375 ] + ], + [ + [ [ [ -1, 5, 2, -1, 0, 0 ], [ -3, 4, 2, 0, 0, 0 ], [ -3, 5, 1, 0, 0, 0 ], [ -3, 5, 2, -1, 0, 0 ] ], 1 ], + [ [ [ -1, 5, 2, -1, 0, 0 ], [ -3, 5, 1, -1, 0, 0 ], [ -3, 5, 1, 0, 0, 0 ], [ -3, 5, 2, -1, 0, 0 ] ], 1.875 ] + ], + [ + [ [ [ -1, 5, 2, -1, 0, 0 ], [ -3, 5, 1, -1, 0, 0 ], [ -3, 5, 1, 0, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ] ], 2.875 ] + ], + [ + [ [ [ -1, 5, 2, -1, 0, 0 ], [ -3, 5, 1, -1, 0, 0 ], [ -2, 5, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ] ], 1 ], + [ [ [ -1, 5, 2, -1, 0, 0 ], [ -2, 4, 2, -1, 0, 0 ], [ -2, 5, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -2, 4, 2, -1, 0, 0 ], [ -2, 5, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ] ], 3.25 ] + ], + [ + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ], [ -2, 5, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ], [ -2, 5, 1, -1, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ], [ -2, 5, 1, -1, 0, 0 ], [ -3, 5, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ], [ -2, 5, 1, -1, 0, 0 ], [ -3, 5, 1, -1, 0, 0 ] ], 2.375 ] + ], + [ + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -3, 6, 1, -1, 0, 0 ], [ -2, 5, 1, -1, 0, 0 ], [ -3, 5, 1, -1, 0, 0 ] ], 2.375 ] + ], + [ + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -3, 6, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ], [ -3, 5, 1, -1, 0, 0 ] ], 3.125 ] + ], + [ + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -3, 6, 1, -1, 0, 0 ], [ -1, 3, 0, -1, 0, 0 ], [ -3, 5, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -1, 4, 1, -1, 0, 0 ], [ -1, 3, 0, -1, 0, 0 ], [ -3, 5, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -1, 4, 1, -1, 0, 0 ], [ -1, 3, 0, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ] ], 3.25 ] + ], + [ + [ [ [ 1, 2, 1, -1, 0, 0 ], [ -1, 4, 1, -1, 0, 0 ], [ -1, 3, 0, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 1, 2, 1, -1, 0, 0 ], [ 0, 3, 0, -1, 0, 0 ], [ -1, 3, 0, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ] ], 1.75 ] + ], + [ + [ [ [ 0, 4, 1, -1, 0, 0 ], [ 0, 3, 0, -1, 0, 0 ], [ -1, 3, 0, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 4, 1, -1, 0, 0 ], [ 0, 2, 1, -1, 0, 0 ], [ -1, 3, 0, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 4, 1, -1, 0, 0 ], [ 0, 2, 1, -1, 0, 0 ], [ -1, 2, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ] ], 1.75 ] + ], + [ + [ [ [ 0, 4, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ], [ -1, 2, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 4, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ] ], 2.375 ] + ], + [ + [ [ [ 0, 4, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ 0, 3, 1, -2, 0, 0 ] ], 1 ], + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ 0, 3, 1, -2, 0, 0 ] ], 1 ], + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ 0, 3, 1, -2, 0, 0 ] ], 3.25 ] + ], + [ + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -1, 3, 1, -2, 0, 0 ], [ -2, 3, 1, -1, 0, 0 ], [ 0, 3, 1, -2, 0, 0 ] ], 1.375 ] + ], + [ + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, -1 ], [ -2, 3, 1, -1, 0, 0 ], [ 0, 3, 1, -2, 0, 0 ] ], 1 ], + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, -1 ], [ -2, 3, 1, -1, 0, 0 ], [ 0, 3, 1, -1, 0, -1 ] ], 1 ], + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, -1 ], [ -2, 4, 1, -1, 0, 0 ], [ 0, 3, 1, -1, 0, -1 ] ], 1.25 ] + ], + [ + [ [ [ 0, 3, 1, -1, 0, 0 ], [ -1, 2, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ], [ 0, 3, 1, -1, 0, -1 ] ], 2.75 ] + ], + [ + [ [ [ -1, 3, 1, 0, 0, -1 ], [ -1, 2, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ], [ 0, 3, 1, -1, 0, -1 ] ], 1 ], + [ [ [ -1, 3, 1, 0, 0, -1 ], [ -1, 3, 1, -1, 0, 0 ], [ -2, 4, 1, -1, 0, 0 ], [ 0, 3, 1, -1, 0, -1 ] ], 1 ], + [ [ [ -1, 3, 1, 0, 0, -1 ], [ -1, 3, 1, -1, 0, 0 ], [ -1, 3, 1, -1, 0, -1 ], [ 0, 3, 1, -1, 0, -1 ] ], 1.5 ] + ], + [ + [ [ [ -1, 3, 1, 0, 0, -1 ], [ -2, 3, 1, 0, 0, -1 ], [ -1, 3, 1, -1, 0, -1 ], [ 0, 3, 1, -1, 0, -1 ] ], 1.625 ] + ], + [ + [ [ [ 0, 4, 1, -1, 0, -1 ], [ -2, 3, 1, 0, 0, -1 ], [ -1, 3, 1, -1, 0, -1 ], [ 0, 3, 1, -1, 0, -1 ] ], 1 ], + [ [ [ 0, 4, 1, -1, 0, -1 ], [ -2, 3, 1, 0, 0, -1 ], [ -1, 3, 1, -1, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ] ], 1 ], + [ [ [ 0, 4, 1, -1, 0, -1 ], [ -2, 4, 1, -1, 0, -1 ], [ -1, 3, 1, -1, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ] ], 3 ] + ], + [ + [ [ [ 0, 4, 1, -1, 0, -1 ], [ -2, 4, 1, -1, 0, -1 ], [ -1, 3, 1, -1, 0, -1 ], [ -2, 4, 1, 0, 0, -1 ] ], 1 ], + [ [ [ 1, 3, 0, -1, 0, -1 ], [ -2, 4, 1, -1, 0, -1 ], [ -1, 3, 1, -1, 0, -1 ], [ -2, 4, 1, 0, 0, -1 ] ], 2.625 ] + ], + [ + [ [ [ 1, 3, 0, -1, 0, -1 ], [ -2, 4, 1, -1, 0, -1 ], [ -1, 3, 1, -1, 0, -1 ], [ -1, 4, 2, -1, 0, -1 ] ], 1 ], + [ [ [ 0, 3, 1, -1, 0, -1 ], [ -2, 4, 1, -1, 0, -1 ], [ -1, 3, 1, -1, 0, -1 ], [ -1, 4, 2, -1, 0, -1 ] ], 1 ], + [ [ [ 0, 3, 1, -1, 0, -1 ], [ -2, 4, 1, -1, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ], [ -1, 4, 2, -1, 0, -1 ] ], 2.75 ] + ], + [ + [ [ [ 0, 3, 1, -1, 0, -1 ], [ -3, 4, 2, 0, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ], [ -1, 4, 2, -1, 0, -1 ] ], 2.375 ] + ], + [ + [ [ [ 0, 3, 1, -1, 0, -1 ], [ -3, 4, 2, 0, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ], [ -1, 4, 2, 0, 0, -2 ] ], 3.375 ] + ], + [ + [ [ [ 0, 3, 1, -1, 0, -1 ], [ -3, 4, 2, 0, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ], [ -1, 3, 2, -1, 0, -1 ] ], 2.875 ] + ], + [ + [ [ [ -1, 5, 1, -1, 0, -1 ], [ -3, 4, 2, 0, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ], [ -1, 3, 2, -1, 0, -1 ] ], 1 ], + [ [ [ -1, 5, 1, -1, 0, -1 ], [ -2, 4, 2, -1, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ], [ -1, 3, 2, -1, 0, -1 ] ], 1 ], + [ [ [ -1, 5, 1, -1, 0, -1 ], [ -2, 4, 2, -1, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ], [ -1, 3, 1, -1, 0, -1 ] ], 1.875 ] + ], + [ + [ [ [ -1, 5, 1, -1, 0, -1 ], [ -2, 4, 1, -1, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ], [ -1, 3, 1, -1, 0, -1 ] ], 3.25 ] + ], + [ + [ [ [ -1, 5, 1, -1, 0, -1 ], [ -2, 5, 1, -1, -1, -1 ], [ -1, 4, 1, -1, 0, -1 ], [ -1, 3, 1, -1, 0, -1 ] ], 1 ], + [ [ [ -1, 5, 1, -1, 0, -1 ], [ -2, 5, 1, -1, -1, -1 ], [ -2, 5, 1, -1, 0, -1 ], [ -1, 3, 1, -1, 0, -1 ] ], 3.625 ] + ], + [ + [ [ [ -1, 5, 1, -1, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ], [ -2, 5, 1, -1, 0, -1 ], [ -1, 3, 1, -1, 0, -1 ] ], 1 ], + [ [ [ -1, 5, 1, -1, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ], [ -2, 5, 1, -1, 0, -1 ], [ -2, 4, 1, -1, 0, -1 ] ], 3.25 ] + ], + [ + [ [ [ -1, 5, 1, -1, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ], [ -2, 5, 1, -1, 0, -1 ], [ -3, 6, 1, -1, 0, -1 ] ], 1.375 ] + ], + [ + [ [ [ -1, 5, 1, -1, 0, -1 ], [ -1, 4, 1, -1, 0, -1 ], [ -3, 7, 1, -1, 0, -1 ], [ -3, 6, 1, -1, 0, -1 ] ], 1.125 ] + ], + [ + [ [ [ -1, 5, 1, -1, 0, -1 ], [ -2, 6, 1, -1, 0, -1 ], [ -3, 7, 1, -1, 0, -1 ], [ -3, 6, 1, -1, 0, -1 ] ], 1.5 ] + ], + [ + [ [ [ -2, 7, 1, -1, 0, -1 ], [ -2, 6, 1, -1, 0, -1 ], [ -3, 7, 1, -1, 0, -1 ], [ -3, 6, 1, -1, 0, -1 ] ], 1 ], + [ [ [ -2, 7, 1, -1, 0, -1 ], [ -2, 6, 1, -1, 0, -1 ], [ -2, 6, 0, -1, 0, -1 ], [ -3, 6, 1, -1, 0, -1 ] ], 2.375 ] + ], + [ + [ [ [ -1, 6, 0, -1, 0, -1 ], [ -2, 6, 1, -1, 0, -1 ], [ -2, 6, 0, -1, 0, -1 ], [ -3, 6, 1, -1, 0, -1 ] ], 3.625 ] + ], + [ + [ [ [ -1, 6, 0, -1, 0, -1 ], [ -2, 6, 1, -1, 0, -1 ], [ -2, 6, 0, -1, 0, -1 ], [ -2, 5, 0, -1, 0, -1 ] ], 1 ], + [ [ [ -4, 6, 1, 0, 0, -1 ], [ -2, 6, 1, -1, 0, -1 ], [ -2, 6, 0, -1, 0, -1 ], [ -2, 5, 0, -1, 0, -1 ] ], 2.125 ] + ], + [ + [ [ [ -3, 6, 1, -1, 0, -1 ], [ -2, 6, 1, -1, 0, -1 ], [ -2, 6, 0, -1, 0, -1 ], [ -2, 5, 0, -1, 0, -1 ] ], 1 ], + [ [ [ -3, 6, 1, -1, 0, -1 ], [ -1, 5, 0, -1, 0, -1 ], [ -2, 6, 0, -1, 0, -1 ], [ -2, 5, 0, -1, 0, -1 ] ], 3.375 ] + ], + [ + [ [ [ -3, 6, 0, -1, 0, -1 ], [ -1, 5, 0, -1, 0, -1 ], [ -2, 6, 0, -1, 0, -1 ], [ -2, 5, 0, -1, 0, -1 ] ], 1 ], + [ [ [ -3, 6, 0, -1, 0, -1 ], [ -1, 5, 0, -1, 0, -1 ], [ -2, 6, 0, -1, 0, -1 ], [ -3, 7, 0, -1, 0, -1 ] ], 1.625 ] + ], + [ + [ [ [ -3, 6, 0, -1, 0, -1 ], [ -1, 5, 0, -1, 0, -1 ], [ -2, 6, 0, -1, 0, -1 ], [ -2, 6, -1, -1, 0, -1 ] ], 1 ], + [ [ [ -3, 6, 0, -1, 0, -1 ], [ -2, 7, 0, -1, 0, -1 ], [ -2, 6, 0, -1, 0, -1 ], [ -2, 6, -1, -1, 0, -1 ] ], 1 ], + [ [ [ -3, 6, 0, -1, 0, -1 ], [ -2, 7, 0, -1, 0, -1 ], [ -2, 6, 0, -1, 1, -1 ], [ -2, 6, -1, -1, 0, -1 ] ], 1.75 ] + ], + [ + [ [ [ -3, 6, 0, -1, 0, -1 ], [ -2, 7, 0, -1, 0, -1 ], [ -2, 6, 0, -1, 1, -1 ], [ -2, 6, 0, -1, 0, -1 ] ], 1 ], + [ [ [ -3, 7, 0, -1, 0, -1 ], [ -2, 7, 0, -1, 0, -1 ], [ -2, 6, 0, -1, 1, -1 ], [ -2, 6, 0, -1, 0, -1 ] ], 2.625 ] + ], + [ + [ [ [ -3, 7, 0, -1, 0, -1 ], [ -2, 7, 0, -1, 0, -1 ], [ -2, 6, 0, -1, 1, -1 ], [ -3, 6, 0, -1, 1, -1 ] ], 1 ], + [ [ [ -2, 6, 0, -1, 1, -2 ], [ -2, 7, 0, -1, 0, -1 ], [ -2, 6, 0, -1, 1, -1 ], [ -3, 6, 0, -1, 1, -1 ] ], 2.25 ] + ], + [ + [ [ [ -2, 6, 0, -1, 1, -2 ], [ -2, 7, 0, -1, 0, -1 ], [ -3, 7, 0, -1, 1, -1 ], [ -3, 6, 0, -1, 1, -1 ] ], 2.125 ] + ], + [ + [ [ [ -2, 6, 0, -1, 1, -2 ], [ -2, 7, -1, -1, 1, -1 ], [ -3, 7, 0, -1, 1, -1 ], [ -3, 6, 0, -1, 1, -1 ] ], 1.375 ] + ], + [ + [ [ [ -3, 6, -1, -1, 1, -1 ], [ -2, 7, -1, -1, 1, -1 ], [ -3, 7, 0, -1, 1, -1 ], [ -3, 6, 0, -1, 1, -1 ] ], 1 ], + [ [ [ -3, 6, -1, -1, 1, -1 ], [ -2, 7, -1, -1, 1, -1 ], [ -3, 7, 0, -1, 1, -1 ], [ -3, 7, -1, -1, 1, -1 ] ], 1.5 ] + ], + [ + [ [ [ -3, 6, -1, -1, 1, -1 ], [ -2, 7, -1, -1, 1, -1 ], [ -2, 6, -1, -1, 1, -1 ], [ -3, 7, -1, -1, 1, -1 ] ], 3.125 ] + ], + [ + [ [ [ -1, 7, -1, -1, 1, -1 ], [ -2, 7, -1, -1, 1, -1 ], [ -2, 6, -1, -1, 1, -1 ], [ -3, 7, -1, -1, 1, -1 ] ], 1 ], + [ [ [ -1, 7, -1, -1, 1, -1 ], [ -2, 7, -1, -1, 1, -1 ], [ -3, 8, -1, -1, 1, -1 ], [ -3, 7, -1, -1, 1, -1 ] ], 2.625 ] + ], + [ + [ [ [ -1, 7, -1, -1, 1, -1 ], [ -2, 7, -1, -2, 1, -1 ], [ -3, 8, -1, -1, 1, -1 ], [ -3, 7, -1, -1, 1, -1 ] ], 1 ], + [ [ [ -2, 9, -1, -1, 1, -1 ], [ -2, 7, -1, -2, 1, -1 ], [ -3, 8, -1, -1, 1, -1 ], [ -3, 7, -1, -1, 1, -1 ] ], 2.625 ] + ], + [ + [ [ [ -2, 9, -1, -1, 1, -1 ], [ -2, 7, -1, -2, 1, -1 ], [ -3, 8, -1, -1, 1, -1 ], [ -4, 9, -1, -1, 1, -1 ] ], 1 ], + [ [ [ -2, 9, -1, -1, 1, -1 ], [ -2, 7, -1, -2, 1, -1 ], [ -4, 10, -1, -1, 1, -1 ], [ -4, 9, -1, -1, 1, -1 ] ], 3.5 ] + ], + [ + [ [ [ -2, 9, -1, -2, 1, -1 ], [ -2, 7, -1, -2, 1, -1 ], [ -4, 10, -1, -1, 1, -1 ], [ -4, 9, -1, -1, 1, -1 ] ], 1 ], + [ [ [ -2, 9, -1, -2, 1, -1 ], [ -4, 8, -1, -1, 1, -1 ], [ -4, 10, -1, -1, 1, -1 ], [ -4, 9, -1, -1, 1, -1 ] ], 3.125 ] + ], + [ + [ [ [ -2, 9, -1, -2, 1, -1 ], [ -4, 8, -1, -1, 1, -1 ], [ -3, 9, -2, -1, 1, -1 ], [ -4, 9, -1, -1, 1, -1 ] ], 1.25 ] + ], + [ + [ [ [ -2, 9, -1, -2, 1, -1 ], [ -4, 8, -1, -1, 1, -1 ], [ -3, 9, -2, -1, 1, -1 ], [ -3, 9, -1, -2, 1, -1 ] ], 3.125 ] + ], + [ + [ [ [ -2, 8, -1, -2, 1, -1 ], [ -4, 8, -1, -1, 1, -1 ], [ -3, 9, -2, -1, 1, -1 ], [ -3, 9, -1, -2, 1, -1 ] ], 1 ], + [ [ [ -2, 8, -1, -2, 1, -1 ], [ -4, 8, -1, -1, 1, -1 ], [ -3, 9, -2, -1, 1, -1 ], [ -3, 8, -1, -2, 1, -1 ] ], 1 ], + [ [ [ -2, 8, -1, -2, 1, -1 ], [ -4, 8, -1, -1, 1, -1 ], [ -3, 8, -1, -1, 1, -1 ], [ -3, 8, -1, -2, 1, -1 ] ], 2.375 ] + ], + [ + [ [ [ -2, 8, -1, -2, 1, -1 ], [ -4, 8, -1, -1, 1, -1 ], [ -3, 8, -1, -1, 1, -1 ], [ -4, 8, 0, -1, 1, -1 ] ], 1 ], + [ [ [ -2, 8, -1, -2, 1, -1 ], [ -4, 8, -1, -1, 1, -1 ], [ -2, 8, -1, -2, 0, -1 ], [ -4, 8, 0, -1, 1, -1 ] ], 2.875 ] + ], + [ + [ [ [ -2, 8, -1, -2, 1, -1 ], [ -4, 8, -1, -1, 1, -1 ], [ -3, 8, -1, -2, 1, 0 ], [ -4, 8, 0, -1, 1, -1 ] ], 1 ], + [ [ [ -2, 8, -1, -2, 1, -1 ], [ -4, 8, -1, -1, 1, -1 ], [ -3, 8, -1, -2, 1, 0 ], [ -3, 8, 0, -2, 1, -1 ] ], 1 ], + [ [ [ -2, 8, -1, -2, 1, -1 ], [ -3, 8, -1, -2, 1, -1 ], [ -3, 8, -1, -2, 1, 0 ], [ -3, 8, 0, -2, 1, -1 ] ], 1.625 ] + ], + [ + [ [ [ -2, 8, -1, -2, 1, -1 ], [ -4, 8, -1, -2, 1, 0 ], [ -3, 8, -1, -2, 1, 0 ], [ -3, 8, 0, -2, 1, -1 ] ], 1 ], + [ [ [ -2, 8, -1, -2, 1, -1 ], [ -4, 8, -1, -2, 1, 0 ], [ -3, 8, -1, -2, 1, 0 ], [ -3, 8, -1, -2, 0, -1 ] ], 1 ], + [ [ [ -2, 8, -1, -2, 1, -1 ], [ -4, 8, -1, -2, 1, 0 ], [ -3, 8, -1, -2, 1, -1 ], [ -3, 8, -1, -2, 0, -1 ] ], 1.875 ] + ], + [ + [ [ [ -2, 7, -1, -2, 1, -1 ], [ -4, 8, -1, -2, 1, 0 ], [ -3, 8, -1, -2, 1, -1 ], [ -3, 8, -1, -2, 0, -1 ] ], 1 ], + [ [ [ -2, 7, -1, -2, 1, -1 ], [ -4, 8, -1, -1, 1, -1 ], [ -3, 8, -1, -2, 1, -1 ], [ -3, 8, -1, -2, 0, -1 ] ], 2.125 ] + ], + [ + [ [ [ -2, 7, -1, -2, 1, -1 ], [ -4, 8, -1, -1, 1, -1 ], [ -2, 8, 0, -2, 0, -1 ], [ -3, 8, -1, -2, 0, -1 ] ], 1 ], + [ [ [ -2, 7, -1, -2, 1, -1 ], [ -3, 8, -1, -2, 1, -1 ], [ -2, 8, 0, -2, 0, -1 ], [ -3, 8, -1, -2, 0, -1 ] ], 1 ], + [ [ [ -2, 8, -1, -2, 0, -1 ], [ -3, 8, -1, -2, 1, -1 ], [ -2, 8, 0, -2, 0, -1 ], [ -3, 8, -1, -2, 0, -1 ] ], 1.75 ] + ], + [ + [ [ [ -3, 8, -1, -2, 1, 0 ], [ -3, 8, -1, -2, 1, -1 ], [ -2, 8, 0, -2, 0, -1 ], [ -3, 8, -1, -2, 0, -1 ] ], 1 ], + [ [ [ -3, 8, -1, -2, 1, 0 ], [ -3, 8, -1, -2, 1, -1 ], [ -2, 8, -1, -2, 1, -1 ], [ -3, 8, -1, -2, 0, -1 ] ], 3.375 ] + ], + [ + [ [ [ -3, 8, -1, -2, 1, 0 ], [ -3, 8, -1, -2, 1, -1 ], [ -2, 8, -1, -2, 1, -1 ], [ -3, 8, -2, -2, 1, -1 ] ], 2.125 ], + [ [ [ -3, 8, -1, -2, 1, 0 ], [ "Rest" ], [ -2, 8, -1, -2, 1, -1 ], [ -3, 8, -2, -2, 1, -1 ] ], 1 ], + [ [ [ -3, 8, -1, -2, 1, 0 ], [ "Rest" ], [ -2, 8, -1, -2, 1, -1 ], [ "Rest" ] ], 1 ], + [ [ [ -3, 8, -1, -2, 1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.25 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 7, -1, -2, 1, -1 ], [ -3, 8, -1, -2, 1, -1 ], [ -2, 8, 0, -2, 0, -1 ], [ -3, 8, -1, -2, 0, -1 ] ], + [ [ -2, 8, -1, -2, 0, -1 ], [ -3, 8, -1, -2, 1, -1 ], [ -2, 8, 0, -2, 0, -1 ], [ -3, 8, -1, -2, 0, -1 ] ], + [ [ -3, 8, -1, -2, 1, 0 ], [ -3, 8, -1, -2, 1, -1 ], [ -2, 8, 0, -2, 0, -1 ], [ -3, 8, -1, -2, 0, -1 ] ], + [ [ -3, 8, -1, -2, 1, 0 ], [ -3, 8, -1, -2, 1, -1 ], [ -2, 8, -1, -2, 1, -1 ], [ -3, 8, -1, -2, 0, -1 ] ], + [ [ -3, 8, -1, -2, 1, 0 ], [ -3, 8, -1, -2, 1, -1 ], [ -2, 8, -1, -2, 1, -1 ], [ -3, 8, -2, -2, 1, -1 ] ] +], +"cur_uid": "tmp", +"ref_uid": "nil", +"order_seed": 389930, +"dur_seed": 362766, +"motifs_seed": 479673, +"entrances_probs_vals": [ 0, 0, 2.9365079365079, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 2.7380952380952, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 2.7380952380952, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ -1200, 1200, 0.0020576131687243, 0.068181818181818, 0.074074074074074, 0.0625, 0.20576131687243, 0.0625, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.78600823045268, 0.068181818181818, 0.98971193415638, 0.0625 ], +"passages_weights": [ 1, 1, 0.48, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 0, 3 ], [ 2, 1 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ], + [ [ 2 ], [ 1, 3, 0 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 0, 3 ], [ 1, 2 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 2 ], [ 0, 3, 1 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 2, 0 ], [ 3, 1 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 2 ], [ 0, 3, 1 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0, 3 ], [ 2, 1 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 2, 0 ], [ 3, 1 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 0, 2 ], [ 1, 3 ], [ ] ], + [ [ 0, 2 ], [ 3, 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 3, 0 ], [ 1, 2 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 2 ], [ 0, 3, 1 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0, 3 ], [ 1, 2 ], [ ] ], + [ [ 2, 0 ], [ 1, 3 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 1, 2 ], [ 0, 3 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 2, 1 ], [ 0, 3 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 100, 100 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise.json b/resources/string_quartet_3_rise.json new file mode 100644 index 0000000..8607c63 --- /dev/null +++ b/resources/string_quartet_3_rise.json @@ -0,0 +1,19 @@ +{ +"ledger": +[ + "4874dd07", + "44490863", + "4bf1af12", + "6522664c", + "7ede7adb", + "521654f8", + "6db2efcc", + "4b40ed47", + "4e9f1dcc", + "78a94ed1", + "531df78c", + "7276dc78", + "43214be8", + "7c30c182" +] +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise.json_bak b/resources/string_quartet_3_rise.json_bak new file mode 100644 index 0000000..d09baa2 --- /dev/null +++ b/resources/string_quartet_3_rise.json_bak @@ -0,0 +1,18 @@ +{ +"ledger": +[ + "4874dd07", + "44490863", + "4bf1af12", + "6522664c", + "7ede7adb", + "521654f8", + "6db2efcc", + "4b40ed47", + "4e9f1dcc", + "78a94ed1", + "531df78c", + "7276dc78", + "43214be8" +] +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/43214be8/43214be8_code.scd b/resources/string_quartet_3_rise/43214be8/43214be8_code.scd new file mode 100644 index 0000000..42b64ff --- /dev/null +++ b/resources/string_quartet_3_rise/43214be8/43214be8_code.scd @@ -0,0 +1,981 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/43214be8/43214be8_mus_model.json b/resources/string_quartet_3_rise/43214be8/43214be8_mus_model.json new file mode 100644 index 0000000..ec6964f --- /dev/null +++ b/resources/string_quartet_3_rise/43214be8/43214be8_mus_model.json @@ -0,0 +1,158 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -7, 6, 4, -1, 1, 2 ], [ -5, 5, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 1, 2 ] ], 1 ], + [ [ [ -6, 6, 4, -1, 0, 2 ], [ -5, 5, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 1, 2 ] ], 1 ], + [ [ [ -6, 6, 4, -1, 0, 2 ], [ -5, 5, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -7, 6, 5, 0, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 4, -1, 0, 2 ], [ -5, 6, 3, 0, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -7, 6, 5, 0, 0, 2 ] ], 1 ], + [ [ [ -6, 6, 4, -1, 0, 2 ], [ -5, 6, 3, 0, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -7, 6, 4, 1, 0, 2 ] ], 1 ], + [ [ [ -6, 6, 3, 0, 0, 2 ], [ -5, 6, 3, 0, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -7, 6, 4, 1, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 3, 0, 0, 2 ], [ -5, 6, 3, 0, 0, 2 ], [ -5, 6, 2, 0, 0, 2 ], [ -7, 6, 4, 1, 0, 2 ] ], 1 ], + [ [ [ -6, 6, 3, 0, 0, 2 ], [ -5, 6, 3, 0, 0, 2 ], [ -5, 6, 2, 0, 0, 2 ], [ -6, 7, 3, 0, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 3, 0, 0, 2 ], [ -5, 6, 2, -1, 0, 2 ], [ -5, 6, 2, 0, 0, 2 ], [ -6, 7, 3, 0, 0, 2 ] ], 1 ], + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -5, 6, 2, -1, 0, 2 ], [ -5, 6, 2, 0, 0, 2 ], [ -6, 7, 3, 0, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -5, 6, 2, -1, 0, 2 ], [ -6, 7, 2, 1, 0, 2 ], [ -6, 7, 3, 0, 0, 2 ] ], 1 ], + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -6, 7, 2, 1, 0, 2 ], [ -6, 7, 3, 0, 0, 2 ] ], 1 ], + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -6, 7, 2, 1, 0, 2 ], [ -5, 6, 2, 1, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -5, 6, 2, 1, -1, 1 ], [ -5, 6, 2, 1, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -6, 6, 2, 2, -1, 2 ], [ -5, 6, 2, 1, -1, 2 ] ], 1 ], + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -6, 6, 2, 2, -1, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -7, 5, 2, 3, 0, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ], + [ [ [ -8, 6, 2, 2, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -7, 5, 2, 3, 0, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ], + [ [ [ -8, 6, 2, 2, 0, 2 ], [ -7, 5, 2, 2, -1, 2 ], [ -7, 5, 2, 3, 0, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 2, 2, -1, 2 ], [ -7, 5, 2, 2, -1, 2 ], [ -7, 5, 2, 3, 0, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 2, 2, -1, 2 ], [ -7, 5, 2, 2, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ], + [ [ [ -7, 6, 2, 2, -1, 2 ], [ -6, 6, 2, 2, -2, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 2, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 2, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 2, 2, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 2, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 6, 2, 2, -1, 2 ] ], 1 ], + [ [ [ -6, 5, 1, 2, 0, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 6, 2, 2, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 5, 1, 2, 0, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 5, 0, 2, 0, 2 ], [ -6, 6, 2, 2, -1, 2 ] ], 1 ], + [ [ [ -6, 5, 1, 2, 0, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 5, 0, 2, 0, 2 ], [ -6, 6, 1, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 5, 1, 2, 0, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -6, 6, 1, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 5, 1, 3, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -6, 6, 1, 2, 0, 2 ] ], 1 ], + [ [ [ -6, 5, 1, 3, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 5, 1, 2, -2, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 5, 1, 2, -2, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ], + [ [ [ -7, 5, 1, 2, -2, 2 ], [ -7, 5, 1, 3, -2, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ], + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -7, 5, 1, 3, -2, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -7, 5, 1, 3, -2, 2 ], [ -5, 5, 1, 1, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ], + [ [ [ -6, 5, 1, 2, -2, 2 ], [ -7, 5, 1, 3, -2, 2 ], [ -5, 5, 1, 1, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ], + [ [ [ -6, 5, 1, 2, -2, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -5, 5, 1, 1, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -8, 5, 0, 3, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -5, 5, 1, 1, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ], + [ [ [ -8, 5, 0, 3, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 1, 2, -2, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 1, 2, -2, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -7, 6, 1, 3, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -8, 4, 1, 3, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -7, 6, 1, 3, -1, 2 ] ], 1 ], + [ [ [ -8, 4, 1, 3, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 3, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -9, 5, 1, 4, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 3, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -9, 5, 1, 4, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 4, -2, 2 ] ], 1 ], + [ [ [ -9, 5, 1, 4, -1, 2 ], [ -7, 5, 1, 4, -2, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 4, -2, 2 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -9, 8, 0, 5, -2, 2 ], [ -8, 7, 0, 5, -2, 2 ], [ -9, 7, 1, 5, -2, 2 ], [ -8, 7, 1, 5, -2, 2 ] ], + [ [ -9, 8, 0, 5, -2, 2 ], [ -8, 7, 0, 5, -2, 2 ], [ -9, 7, 1, 5, -2, 2 ], [ -9, 7, 0, 6, -2, 2 ] ], + [ [ -9, 8, 0, 5, -2, 2 ], [ -8, 7, 0, 5, -2, 2 ], [ -9, 7, 0, 5, -2, 2 ], [ -9, 7, 0, 6, -2, 2 ] ], + [ [ -9, 8, 0, 5, -2, 2 ], [ -8, 7, 0, 5, -2, 2 ], [ -9, 7, 0, 5, -2, 2 ], [ -7, 7, 0, 4, -2, 2 ] ], + [ [ -8, 7, -1, 5, -2, 2 ], [ -8, 7, 0, 5, -2, 2 ], [ -9, 7, 0, 5, -2, 2 ], [ -7, 7, 0, 4, -2, 2 ] ] +], +"cur_uid": "43214be8", +"ref_uid": "7276dc78", +"order_seed": 553436, +"dur_seed": 804010, +"motifs_seed": 337121, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0061728395061728, 0.10227272727273, 0.074074074074074, 0.10227272727273, 0.2037037037037, 0.090909090909091, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.7880658436214, 0.034090909090909, 1, 0.034090909090909 ], +"passages_weights": [ 0.08, 0.47, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 1 ], [ 3, 2, 0 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 1 ], [ 2, 3, 0 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 0, 3, 1 ], [ 2 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 3 ], [ 0, 2, 1 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 2 ], [ 0, 3, 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 1 ], [ 2, 3, 0 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 25, 25.244897959184 ], +"passages_size": [ 0, 0 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/43214be8/lilypond/part_I.ly b/resources/string_quartet_3_rise/43214be8/lilypond/part_I.ly new file mode 100644 index 0000000..e9eae1a --- /dev/null +++ b/resources/string_quartet_3_rise/43214be8/lilypond/part_I.ly @@ -0,0 +1,48 @@ +{ + { gis'1^\markup { \pad-markup #0.2 "-11"} } + \bar "|" + { fis1^\markup { \pad-markup #0.2 "+24"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }} } + \bar "|" + { c'1^\markup { \pad-markup #0.2 "+7"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }} ~ } + \bar "|" + { c'2 fis'2^\markup { \pad-markup #0.2 "-46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 } + \bar "|" + { b'1^\markup { \pad-markup #0.2 "-17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} ~ } + \bar "|" + { b'2 g2^\markup { \pad-markup #0.2 "+1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g2 cis'2^\markup { \pad-markup #0.2 "+50"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { a'2 ais'2^\markup { \pad-markup #0.2 "+17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 } + \bar "|" + { ais1^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais2 d'2^\markup { \pad-markup #0.2 "+34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { d'2 g'2^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g'2 b'2^\markup { \pad-markup #0.2 "+50"}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/43214be8/lilypond/part_II.ly b/resources/string_quartet_3_rise/43214be8/lilypond/part_II.ly new file mode 100644 index 0000000..bd2901a --- /dev/null +++ b/resources/string_quartet_3_rise/43214be8/lilypond/part_II.ly @@ -0,0 +1,48 @@ +{ + { d'1^\markup { \pad-markup #0.2 "+38"} ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 } + \bar "|" + { g'1^\markup { \pad-markup #0.2 "-35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { g'1 } + \bar "|" + { b'1^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { b'2 d'2^\markup { \pad-markup #0.2 "+42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↓" }} } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }} } + \bar "|" + { f'1^\markup { \pad-markup #0.2 "-30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }} ~ } + \bar "|" + { f'1 } + \bar "|" + { f'1^\markup { \pad-markup #0.2 "-35"} ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 } + \bar "|" + { b'1^\markup { \pad-markup #0.2 "+28"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} } + \bar "|" + { d'1^\markup { \pad-markup #0.2 "+3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }} } + \bar "|" + { g1^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g2 c'2^\markup { \pad-markup #0.2 "-5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'2 f'2^\markup { \pad-markup #0.2 "-35"} ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/43214be8/lilypond/part_III.ly b/resources/string_quartet_3_rise/43214be8/lilypond/part_III.ly new file mode 100644 index 0000000..192a5db --- /dev/null +++ b/resources/string_quartet_3_rise/43214be8/lilypond/part_III.ly @@ -0,0 +1,48 @@ +{ + { g'1^\markup { \pad-markup #0.2 "+36"} ~ } + \bar "|" + { g'2 b'2^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }} ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'1 } + \bar "|" + { a1^\markup { \pad-markup #0.2 "-3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }} ~ } + \bar "|" + { a2 d'2^\markup { \pad-markup #0.2 "+3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 } + \bar "|" + { cis1^\markup { \pad-markup #0.2 "+50"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }} ~ } + \bar "|" + { cis2 dis'2^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} } + \bar "|" + { ais'1^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }} ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'2 d2^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d2 g2^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }} ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/43214be8/lilypond/part_IV.ly b/resources/string_quartet_3_rise/43214be8/lilypond/part_IV.ly new file mode 100644 index 0000000..534e787 --- /dev/null +++ b/resources/string_quartet_3_rise/43214be8/lilypond/part_IV.ly @@ -0,0 +1,48 @@ +{ + { ais,2^\markup { \pad-markup #0.2 "+21"} f2^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }} ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f2 b2^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2 e'2^\markup { \pad-markup #0.2 "+34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }} ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'2 d2^\markup { \pad-markup #0.2 "+3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { d2 a2^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a2 dis'2^\markup { \pad-markup #0.2 "+15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↑" }} ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'2 g'2^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }} ~ } + \bar "|" + { g'2 e,2^\markup { \pad-markup #0.2 "+12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }} ~ } + \bar "|" + { e,1 } + \bar "|" + { ais,1^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} } + \bar "|" + { e1^\markup { \pad-markup #0.2 "+12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }} } + \bar "|" + { dis,1^\markup { \pad-markup #0.2 "+46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }} } + \bar "|" + { b,1^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↓" }} } + \bar "|" + { c,1^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} } + \bar "|" + { f,1^\markup { \pad-markup #0.2 "+1"}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/44490863/44490863_code.scd b/resources/string_quartet_3_rise/44490863/44490863_code.scd new file mode 100644 index 0000000..23ccdf1 --- /dev/null +++ b/resources/string_quartet_3_rise/44490863/44490863_code.scd @@ -0,0 +1,981 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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/string_quartet_3_rise/44490863/44490863_mus_model.json b/resources/string_quartet_3_rise/44490863/44490863_mus_model.json new file mode 100644 index 0000000..b1d0e34 --- /dev/null +++ b/resources/string_quartet_3_rise/44490863/44490863_mus_model.json @@ -0,0 +1,154 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -1, 2, 0, 0, 1, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 1, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 1, 1, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 1, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 2, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ], [ 1, 0, 1, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ], [ 0, 0, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 2, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ], [ 0, 0, 2, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 1, 1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 1, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ], [ -2, 2, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -2, 3, 1, 0, 0, 0 ], [ -2, 2, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 2, 0, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -2, 3, 1, 0, 0, 0 ], [ -2, 2, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 2, 0, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -2, 3, 1, 0, 0, 0 ], [ -2, 2, 2, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 0, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ -2, 2, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 2, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ -2, 2, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 2, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ -2, 3, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 4, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ -2, 3, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -3, 4, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -2, 3, 2, 0, 0, 0 ], [ -2, 3, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 4, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -2, 3, 2, 0, 0, 0 ], [ -2, 4, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 2, 2, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -2, 3, 2, 0, 0, 0 ], [ -2, 4, 2, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 2, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 1, 0 ], [ -2, 4, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 3, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 1, 0 ], [ -2, 4, 2, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 3, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 1, 0 ], [ -2, 2, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 1, 0, 0, 0 ], [ -2, 3, 1, 0, 1, 0 ], [ -1, 2, 1, 0, 1, 0 ], [ -2, 2, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 3, 1, 0, 0, 0 ], [ -2, 3, 1, 0, 1, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -2, 2, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 2, 0, 1, 0 ], [ -2, 3, 1, 0, 1, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -2, 2, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 3, 2, 0, 1, 0 ], [ -2, 3, 1, 0, 1, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -2, 3, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 2, 0, 1, 0 ], [ -2, 3, 1, 0, 1, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 1, 0 ] ], 1 ], + [ [ [ -2, 2, 1, 0, 1, 0 ], [ -2, 3, 1, 0, 1, 0 ], [ -1, 2, 1, 0, 0, 0 ], [ -1, 2, 1, 0, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 1, 0, 1, 0 ], [ -2, 3, 1, 0, 1, 0 ], [ -1, 2, 0, 0, 1, 0 ], [ -1, 2, 1, 0, 1, 0 ] ], 1 ], + [ [ [ -2, 2, 1, 0, 1, 0 ], [ -2, 2, 1, 1, 1, 0 ], [ -1, 2, 0, 0, 1, 0 ], [ -1, 2, 1, 0, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 1, 0, 1, 0 ], [ -2, 2, 1, 1, 1, 0 ], [ -1, 2, 0, 0, 1, 0 ], [ -3, 2, 1, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 1, 0, 1, 0 ], [ -1, 2, 1, 0, 1, 0 ], [ -1, 2, 0, 0, 1, 0 ], [ -3, 2, 1, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 1, 0, 1, 0 ], [ -1, 2, 1, 0, 1, 0 ], [ -2, 2, 1, 1, 1, 0 ], [ -3, 2, 1, 1, 1, 0 ] ], 1 ], + [ [ [ -2, 1, 1, 1, 1, 0 ], [ -1, 2, 1, 0, 1, 0 ], [ -2, 2, 1, 1, 1, 0 ], [ -3, 2, 1, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 1, 1, 1, 0 ], [ -1, 2, 1, 0, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 2, 1, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 1, 1, 1, 0 ], [ -1, 2, 1, 0, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -2, 1, 1, 1, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 1, 1, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -2, 0, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 1, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 1, 1, 1, 1, 0 ], [ -1, 2, 1, 0, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], + [ [ -2, 1, 1, 1, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], + [ [ -2, 1, 1, 1, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], + [ [ -2, 0, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], + [ [ -5, 1, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ] +], +"cur_uid": "44490863", +"ref_uid": "4874dd07", +"order_seed": 903957, +"dur_seed": 360025, +"motifs_seed": 692248, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0061728395061728, 0.10227272727273, 0.074074074074074, 0.10227272727273, 0.2037037037037, 0.090909090909091, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.7880658436214, 0.034090909090909, 1, 0.034090909090909 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 1, 2 ], [ 0, 3 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 0, 2 ], [ 1, 3 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 3, 0 ], [ 1, 2 ], [ ] ], + [ [ 1, 2 ], [ 0, 3 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 0, 3 ], [ 2, 1 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 2, 0, 3 ], [ 1 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 2, 0 ], [ 3, 1 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 5.0408163265306, 25.244897959184 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/44490863/lilypond/part_I.ly b/resources/string_quartet_3_rise/44490863/lilypond/part_I.ly new file mode 100644 index 0000000..a351af0 --- /dev/null +++ b/resources/string_quartet_3_rise/44490863/lilypond/part_I.ly @@ -0,0 +1,48 @@ +{ + { b1^\markup { \pad-markup #0.2 "-12"} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2 fis'2^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} } + \bar "|" + { fis'2^\markup { \pad-markup #0.2 "+17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }} gis'2^\markup { \pad-markup #0.2 "-27"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }} ~ } + \bar "|" + { gis'2 b'2^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { b'1 } + \bar "|" + { fis1^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { fis2 ais2^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }} ~ } + \bar "|" + { ais1 } + \bar "|" + { cis'1^\markup { \pad-markup #0.2 "-8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { cis'2 c''2^\markup { \pad-markup #0.2 "-20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''2 fis2^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis2 cis'2^\markup { \pad-markup #0.2 "-8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }} } + \bar "|" + { b'1^\markup { \pad-markup #0.2 "+42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { b'1 } + \bar "|" + { a1^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a2 e'2^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }} ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/44490863/lilypond/part_II.ly b/resources/string_quartet_3_rise/44490863/lilypond/part_II.ly new file mode 100644 index 0000000..93d3bf8 --- /dev/null +++ b/resources/string_quartet_3_rise/44490863/lilypond/part_II.ly @@ -0,0 +1,48 @@ +{ + { g'1^\markup { \pad-markup #0.2 "+2"} ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'2 b'2^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'1 } + \bar "|" + { b1^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2 cis'2^\markup { \pad-markup #0.2 "-8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { cis'1 } + \bar "|" + { d'1^\markup { \pad-markup #0.2 "+4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { d'1 } + \bar "|" + { f'1^\markup { \pad-markup #0.2 "-22"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }} ~ } + \bar "|" + { f'2 b'2^\markup { \pad-markup #0.2 "+42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↑" }} ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'2 fis'2^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 } + \bar "|" + { gis'1^\markup { \pad-markup #0.2 "-45"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { gis'1 } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} } + \bar "|" + { c''1^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} ~ } + \bar "|" + { c''2 b'2^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }} ~ } + \bar "|" + { b'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/44490863/lilypond/part_III.ly b/resources/string_quartet_3_rise/44490863/lilypond/part_III.ly new file mode 100644 index 0000000..606adf0 --- /dev/null +++ b/resources/string_quartet_3_rise/44490863/lilypond/part_III.ly @@ -0,0 +1,48 @@ +{ + { fis'1^\markup { \pad-markup #0.2 "-10"} } + \bar "|" + { g2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} e'2^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'2 fis'2^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 } + \bar "|" + { fis'1^\markup { \pad-markup #0.2 "+43"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'2 a'2^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} ~ } + \bar "|" + { a'2 b'2^\markup { \pad-markup #0.2 "+42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'1 } + \bar "|" + { g1^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }} ~ } + \bar "|" + { g1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/44490863/lilypond/part_IV.ly b/resources/string_quartet_3_rise/44490863/lilypond/part_IV.ly new file mode 100644 index 0000000..b3eb4c8 --- /dev/null +++ b/resources/string_quartet_3_rise/44490863/lilypond/part_IV.ly @@ -0,0 +1,48 @@ +{ + { gis'2^\markup { \pad-markup #0.2 "-45"} b'2^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { b'1 } + \bar "|" + { e1^\markup { \pad-markup #0.2 "-14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} } + \bar "|" + { fis1^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }} ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 } + \bar "|" + { b,1^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { b,1 } + \bar "|" + { d1^\markup { \pad-markup #0.2 "+4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }} ~ } + \bar "|" + { d2 fis2^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { fis2 gis2^\markup { \pad-markup #0.2 "-6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }} ~ } + \bar "|" + { gis1 } + \bar "|" + { ais1^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }} } + \bar "|" + { cis'1^\markup { \pad-markup #0.2 "-8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { cis'1 } + \bar "|" + { ais'1^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }} ~ } + \bar "|" + { ais'2 b2^\markup { \pad-markup #0.2 "+42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2 d'2^\markup { \pad-markup #0.2 "+8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }} ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 } + \bar "|" + { a'2^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} e,2^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4874dd07/4874dd07_code.scd b/resources/string_quartet_3_rise/4874dd07/4874dd07_code.scd new file mode 100644 index 0000000..c4e3d1b --- /dev/null +++ b/resources/string_quartet_3_rise/4874dd07/4874dd07_code.scd @@ -0,0 +1,979 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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/string_quartet_3_rise/4874dd07/4874dd07_mus_model.json b/resources/string_quartet_3_rise/4874dd07/4874dd07_mus_model.json new file mode 100644 index 0000000..74fb11d --- /dev/null +++ b/resources/string_quartet_3_rise/4874dd07/4874dd07_mus_model.json @@ -0,0 +1,121 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "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 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 2, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 2, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 2, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -2, 3, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 2, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ -1, 3, 0, 0, 0, 0 ], [ -2, 3, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 2, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ -1, 3, 0, 0, 0, 0 ], [ -1, 0, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -1, 2, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, -1, 0, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, -1, 0, 0, 0, 0 ], [ 0, 0, -1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, -1, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ] ], 1 ], + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ -1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, -1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, -1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 2, 1, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 2, 0, 0, 1, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ 0, -1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], + [ [ -1, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], + [ [ -1, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], + [ [ -1, 2, 1, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], + [ [ -1, 2, 0, 0, 1, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ] +], +"cur_uid": "4874dd07", +"ref_uid": "nil", +"order_seed": 701987, +"dur_seed": 150121, +"motifs_seed": 970221, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0061728395061728, 0.10227272727273, 0.074074074074074, 0.10227272727273, 0.2037037037037, 0.090909090909091, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.7880658436214, 0.034090909090909, 1, 0.034090909090909 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 0, 3 ], [ 1, 2 ], [ ] ], + [ [ 2, 1 ], [ 0, 3 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 3 ], [ 0, 2, 1 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 1 ], [ 3, 2, 0 ], [ ] ], + [ [ 2, 0 ], [ 1, 3 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 4.8265306122449, 25 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4874dd07/lilypond/part_I.ly b/resources/string_quartet_3_rise/4874dd07/lilypond/part_I.ly new file mode 100644 index 0000000..63f4a0e --- /dev/null +++ b/resources/string_quartet_3_rise/4874dd07/lilypond/part_I.ly @@ -0,0 +1,38 @@ +{ + { r2 c'2^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'2 g2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} } + \bar "|" + { a1^\markup { \pad-markup #0.2 "+6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} } + \bar "|" + { ais1^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }} ~ } + \bar "|" + { ais1 } + \bar "|" + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'2 f'2^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 } + \bar "|" + { g'1^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 } + \bar "|" + { ais'1^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} ~ } + \bar "|" + { ais'1 } + \bar "|" + { c''1^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c''1 } + \bar "|" + { b1^\markup { \pad-markup #0.2 "-12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4874dd07/lilypond/part_II.ly b/resources/string_quartet_3_rise/4874dd07/lilypond/part_II.ly new file mode 100644 index 0000000..8b4aa3d --- /dev/null +++ b/resources/string_quartet_3_rise/4874dd07/lilypond/part_II.ly @@ -0,0 +1,38 @@ +{ + { r1 } + \bar "|" + { r2 g'2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'2 a'2^\markup { \pad-markup #0.2 "+6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { a'2 c''2^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''2 g'2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'2 c''2^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''1 } + \bar "|" + { f1^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} } + \bar "|" + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'2 g'2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4874dd07/lilypond/part_III.ly b/resources/string_quartet_3_rise/4874dd07/lilypond/part_III.ly new file mode 100644 index 0000000..f3dc391 --- /dev/null +++ b/resources/string_quartet_3_rise/4874dd07/lilypond/part_III.ly @@ -0,0 +1,38 @@ +{ + { r1 } + \bar "|" + { c1^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 } + \bar "|" + { g1^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { g1 } + \bar "|" + { gis1^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { gis1 } + \bar "|" + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'2 c2^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c2 g2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g1 } + \bar "|" + { d'1^\markup { \pad-markup #0.2 "+4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }} ~ } + \bar "|" + { d'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4874dd07/lilypond/part_IV.ly b/resources/string_quartet_3_rise/4874dd07/lilypond/part_IV.ly new file mode 100644 index 0000000..c4f4ab6 --- /dev/null +++ b/resources/string_quartet_3_rise/4874dd07/lilypond/part_IV.ly @@ -0,0 +1,38 @@ +{ + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'1 } + \bar "|" + { d'1^\markup { \pad-markup #0.2 "+4"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }} ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'2 c2^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c1 ~ } + \bar "|" + { c1 } + \bar "|" + { f1^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { f2 g2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { g2 f'2^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'2 c''2^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c''1 } + \bar "|" + { c'1^\markup { \pad-markup #0.2 "+0"} ~ } + \bar "|" + { c'2 f2^\markup { \pad-markup #0.2 "-2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} ~ } + \bar "|" + { f2 g2^\markup { \pad-markup #0.2 "+2"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g2 fis'2^\markup { \pad-markup #0.2 "-10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4b40ed47/4b40ed47_code.scd b/resources/string_quartet_3_rise/4b40ed47/4b40ed47_code.scd new file mode 100644 index 0000000..42b64ff --- /dev/null +++ b/resources/string_quartet_3_rise/4b40ed47/4b40ed47_code.scd @@ -0,0 +1,981 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/4b40ed47/4b40ed47_mus_model.json b/resources/string_quartet_3_rise/4b40ed47/4b40ed47_mus_model.json new file mode 100644 index 0000000..85dcea4 --- /dev/null +++ b/resources/string_quartet_3_rise/4b40ed47/4b40ed47_mus_model.json @@ -0,0 +1,86 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -4, 2, 4, 2, 1, 0 ], [ -3, 2, 3, 2, 1, -1 ], [ -4, 2, 3, 2, 1, 0 ], [ -3, 2, 3, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 2, 4, 2, 1, 0 ], [ -3, 2, 3, 2, 1, -1 ], [ -5, 2, 4, 2, 1, 1 ], [ -3, 2, 3, 2, 0, 0 ] ], 1 ], + [ [ [ -4, 2, 4, 2, 1, 0 ], [ -3, 2, 3, 2, 1, -1 ], [ -5, 2, 4, 2, 1, 1 ], [ -3, 2, 3, 2, 2, -1 ] ], 1 ] + ], + [ + [ [ [ -4, 2, 4, 2, 1, 0 ], [ -4, 1, 4, 2, 1, 1 ], [ -5, 2, 4, 2, 1, 1 ], [ -3, 2, 3, 2, 2, -1 ] ], 1 ], + [ [ [ -5, 2, 5, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 1 ], [ -5, 2, 4, 2, 1, 1 ], [ -3, 2, 3, 2, 2, -1 ] ], 1 ], + [ [ [ -5, 2, 5, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 1 ], [ -5, 2, 4, 2, 1, 1 ], [ -5, 1, 4, 2, 1, 1 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 5, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 1 ], [ -3, 1, 4, 2, 1, 0 ], [ -5, 1, 4, 2, 1, 1 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 5, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 1 ], [ -3, 1, 4, 2, 1, 0 ], [ -5, 1, 4, 3, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 5, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 1 ], [ -4, 1, 5, 2, 1, 1 ], [ -5, 1, 4, 3, 1, 0 ] ], 1 ], + [ [ [ -5, 1, 4, 4, 1, 0 ], [ -4, 1, 4, 2, 1, 1 ], [ -4, 1, 5, 2, 1, 1 ], [ -5, 1, 4, 3, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 1, 4, 4, 1, 0 ], [ -4, 1, 4, 2, 1, 1 ], [ -6, 1, 4, 3, 1, 1 ], [ -5, 1, 4, 3, 1, 0 ] ], 1 ], + [ [ [ -5, 1, 4, 4, 1, 0 ], [ -4, 1, 4, 2, 1, 1 ], [ -6, 1, 4, 3, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], 1 ], + [ [ [ -5, 2, 4, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 1 ], [ -6, 1, 4, 3, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 4, 2, 1, 1 ], [ -4, 1, 4, 3, 1, 0 ], [ -6, 1, 4, 3, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 1, 5, 2, 1, 0 ], [ -4, 1, 4, 3, 1, 0 ], [ -6, 1, 4, 3, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 0, 4, 3, 1, 1 ], [ -4, 1, 4, 3, 1, 0 ], [ -6, 1, 4, 3, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 0, 4, 3, 1, 1 ], [ -4, 1, 4, 3, 0, 1 ], [ -6, 1, 4, 3, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 0, 4, 3, 1, 1 ], [ -4, 1, 4, 3, 0, 1 ], [ -5, 1, 4, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 0, 4, 3, 1, 1 ], [ -3, 1, 4, 1, 1, 0 ], [ -5, 1, 4, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], 1 ], + [ [ [ -3, 1, 4, 2, 1, 0 ], [ -3, 1, 4, 1, 1, 0 ], [ -5, 1, 4, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -4, 0, 4, 3, 1, 1 ], [ -4, 1, 4, 3, 1, 0 ], [ -6, 1, 4, 3, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], + [ [ -4, 0, 4, 3, 1, 1 ], [ -4, 1, 4, 3, 0, 1 ], [ -6, 1, 4, 3, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], + [ [ -4, 0, 4, 3, 1, 1 ], [ -4, 1, 4, 3, 0, 1 ], [ -5, 1, 4, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], + [ [ -4, 0, 4, 3, 1, 1 ], [ -3, 1, 4, 1, 1, 0 ], [ -5, 1, 4, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], + [ [ -3, 1, 4, 2, 1, 0 ], [ -3, 1, 4, 1, 1, 0 ], [ -5, 1, 4, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ] +], +"cur_uid": "4b40ed47", +"ref_uid": "6db2efcc", +"order_seed": 401316, +"dur_seed": 325935, +"motifs_seed": 749229, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0061728395061728, 0.10227272727273, 0.074074074074074, 0.10227272727273, 0.2037037037037, 0.090909090909091, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.7880658436214, 0.034090909090909, 1, 0.034090909090909 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 0.4 ], +"hd_exp": 4, +"hd_invert": 0, +"order": +[ + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 1 ], [ 2, 3, 0 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 10.091836734694, 10.091836734694 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4b40ed47/lilypond/part_I.ly b/resources/string_quartet_3_rise/4b40ed47/lilypond/part_I.ly new file mode 100644 index 0000000..669e421 --- /dev/null +++ b/resources/string_quartet_3_rise/4b40ed47/lilypond/part_I.ly @@ -0,0 +1,22 @@ +{ + { a'1^\markup { \pad-markup #0.2 "+1"} } + \bar "|" + { c''1^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↑" }} ~ } + \bar "|" + { c''2 gis2^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { gis2 a2^\markup { \pad-markup #0.2 "+5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }} ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a2 b2^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4b40ed47/lilypond/part_II.ly b/resources/string_quartet_3_rise/4b40ed47/lilypond/part_II.ly new file mode 100644 index 0000000..1c18623 --- /dev/null +++ b/resources/string_quartet_3_rise/4b40ed47/lilypond/part_II.ly @@ -0,0 +1,22 @@ +{ + { dis'2^\markup { \pad-markup #0.2 "-48"} dis'2^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }} ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 } + \bar "|" + { b'1^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }} } + \bar "|" + { c''1^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} } + \bar "|" + { f1^\markup { \pad-markup #0.2 "+46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }} ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f2 gis2^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }} ~ } + \bar "|" + { gis1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4b40ed47/lilypond/part_III.ly b/resources/string_quartet_3_rise/4b40ed47/lilypond/part_III.ly new file mode 100644 index 0000000..370f202 --- /dev/null +++ b/resources/string_quartet_3_rise/4b40ed47/lilypond/part_III.ly @@ -0,0 +1,22 @@ +{ + { fis'1^\markup { \pad-markup #0.2 "+11"} ~ } + \bar "|" + { fis'2 gis'2^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }} ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'2 a'2^\markup { \pad-markup #0.2 "+5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↓" }} ~ } + \bar "|" + { a'1 } + \bar "|" + { c''1^\markup { \pad-markup #0.2 "-6"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↓" }} } + \bar "|" + { d'1^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4b40ed47/lilypond/part_IV.ly b/resources/string_quartet_3_rise/4b40ed47/lilypond/part_IV.ly new file mode 100644 index 0000000..34d5f50 --- /dev/null +++ b/resources/string_quartet_3_rise/4b40ed47/lilypond/part_IV.ly @@ -0,0 +1,22 @@ +{ + { fis'1^\markup { \pad-markup #0.2 "+38"} ~ } + \bar "|" + { fis'1 } + \bar "|" + { g'1^\markup { \pad-markup #0.2 "-35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }} ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'2 g'2^\markup { \pad-markup #0.2 "-26"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }} ~ } + \bar "|" + { g'1 } + \bar "|" + { dis'1^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} } + \bar "|" + { dis'2^\markup { \pad-markup #0.2 "+22"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }} ais'2^\markup { \pad-markup #0.2 "+44"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }} ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'2 b'2^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4bf1af12/4bf1af12_code.scd b/resources/string_quartet_3_rise/4bf1af12/4bf1af12_code.scd new file mode 100644 index 0000000..42b64ff --- /dev/null +++ b/resources/string_quartet_3_rise/4bf1af12/4bf1af12_code.scd @@ -0,0 +1,981 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/4bf1af12/4bf1af12_mus_model.json b/resources/string_quartet_3_rise/4bf1af12/4bf1af12_mus_model.json new file mode 100644 index 0000000..8d43276 --- /dev/null +++ b/resources/string_quartet_3_rise/4bf1af12/4bf1af12_mus_model.json @@ -0,0 +1,87 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -5, 1, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 1, 2, 2, 1, 0 ], [ -3, 0, 2, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 1, 2, 2, 1, 0 ], [ -3, 0, 2, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 1, 2, 2, 1, 0 ], [ -3, 0, 2, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -2, 0, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 0, 2, 2, 1, 0 ], [ -3, 0, 2, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -2, 0, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 0, 2, 2, 1, 0 ], [ -3, 0, 2, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 0, 2, 2, 1, 0 ], [ -3, 0, 2, 2, 1, 0 ], [ -2, 0, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 1, 2, 2, 1, 0 ], [ -3, 0, 2, 2, 1, 0 ], [ -2, 0, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 1, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -2, 0, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 1, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 1, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -3, 1, 3, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 1, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ], [ -3, 1, 3, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 1, 2, 2, 1, 0 ], [ -4, 1, 2, 3, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ], [ -3, 1, 3, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 1, 2, 2, 1, 0 ], [ -4, 1, 2, 3, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -3, 1, 3, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 1, 2, 2, 1, 0 ], [ -4, 1, 2, 3, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -2, 0, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 1, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -2, 0, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -2, 0, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 2, 2, 2, 1, 0 ], [ -4, 1, 2, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -2, 0, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 2, 2, 2, 1, 0 ], [ -4, 1, 2, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 2, 2, 2, 1, 0 ], [ -4, 1, 2, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 2, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -4, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -2, 0, 2, 2, 1, 0 ] ], + [ [ -4, 2, 2, 2, 1, 0 ], [ -4, 1, 2, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -2, 0, 2, 2, 1, 0 ] ], + [ [ -4, 2, 2, 2, 1, 0 ], [ -4, 1, 2, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], + [ [ -4, 2, 2, 2, 1, 0 ], [ -4, 1, 2, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], + [ [ -4, 2, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ] +], +"cur_uid": "4bf1af12", +"ref_uid": 44490863, +"order_seed": 556699, +"dur_seed": 515215, +"motifs_seed": 916157, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0061728395061728, 0.10227272727273, 0.074074074074074, 0.10227272727273, 0.2037037037037, 0.090909090909091, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.7880658436214, 0.034090909090909, 1, 0.034090909090909 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 0.9 ], +"hd_exp": 9, +"hd_invert": 0, +"order": +[ + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 1 ], [ 3, 2, 0 ], [ ] ], + [ [ 0 ], [ 1, 2, 3 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 0, 3 ], [ 2, 1 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 10, 10.091836734694 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4bf1af12/lilypond/part_I.ly b/resources/string_quartet_3_rise/4bf1af12/lilypond/part_I.ly new file mode 100644 index 0000000..28e0f1c --- /dev/null +++ b/resources/string_quartet_3_rise/4bf1af12/lilypond/part_I.ly @@ -0,0 +1,22 @@ +{ + { g'1^\markup { \pad-markup #0.2 "-21"} ~ } + \bar "|" + { g'2 a'2^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { a'2 e'2^\markup { \pad-markup #0.2 "-36"} ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 } + \bar "|" + { g'1^\markup { \pad-markup #0.2 "+50"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { g'1 } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { a'1 } + \bar "|" + { e'1^\markup { \pad-markup #0.2 "-36"}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4bf1af12/lilypond/part_II.ly b/resources/string_quartet_3_rise/4bf1af12/lilypond/part_II.ly new file mode 100644 index 0000000..e6c2955 --- /dev/null +++ b/resources/string_quartet_3_rise/4bf1af12/lilypond/part_II.ly @@ -0,0 +1,22 @@ +{ + { b'1^\markup { \pad-markup #0.2 "-34"} } + \bar "|" + { c''1^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { c''1 } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { a'2 b'2^\markup { \pad-markup #0.2 "-34"} ~ } + \bar "|" + { b'2 e'2^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { e'2 b'2^\markup { \pad-markup #0.2 "-34"} ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'2 c''2^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4bf1af12/lilypond/part_III.ly b/resources/string_quartet_3_rise/4bf1af12/lilypond/part_III.ly new file mode 100644 index 0000000..9111a1e --- /dev/null +++ b/resources/string_quartet_3_rise/4bf1af12/lilypond/part_III.ly @@ -0,0 +1,22 @@ +{ + { g2^\markup { \pad-markup #0.2 "-21"} a2^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 } + \bar "|" + { b1^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { b1 } + \bar "|" + { cis'1^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} ~ } + \bar "|" + { cis'2 e'2^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { e'2 e2^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { e1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4bf1af12/lilypond/part_IV.ly b/resources/string_quartet_3_rise/4bf1af12/lilypond/part_IV.ly new file mode 100644 index 0000000..5c0d977 --- /dev/null +++ b/resources/string_quartet_3_rise/4bf1af12/lilypond/part_IV.ly @@ -0,0 +1,22 @@ +{ + { e,1^\markup { \pad-markup #0.2 "-36"} ~ } + \bar "|" + { e,1 } + \bar "|" + { a,1^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { a,2 e2^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 } + \bar "|" + { b1^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { b1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4c745666/4c745666_code.scd b/resources/string_quartet_3_rise/4c745666/4c745666_code.scd new file mode 100644 index 0000000..0b55c37 --- /dev/null +++ b/resources/string_quartet_3_rise/4c745666/4c745666_code.scd @@ -0,0 +1,977 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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/string_quartet_3_rise/4c745666/4c745666_mus_model.json b/resources/string_quartet_3_rise/4c745666/4c745666_mus_model.json new file mode 100644 index 0000000..06875fc --- /dev/null +++ b/resources/string_quartet_3_rise/4c745666/4c745666_mus_model.json @@ -0,0 +1,166 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 1 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, -1, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, 1, 0, 0, 0, -1 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, -1, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, 1, 0, 0, 0, -1 ], [ -1, 1, 0, 1, 0, 0 ], [ 0, 1, 0, 0, -1, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 1, 0, 0, 0, -1 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ -1, 1, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 1 ], + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 2, 0, 1, -1, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 1 ], + [ [ [ -2, 2, 0, 2, 0, 0 ], [ -1, 2, 0, 1, -1, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 0, 2, 0, 0 ], [ -1, 2, 0, 1, -1, 0 ], [ -3, 3, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 0, 2, 0, 0 ], [ -1, 2, 0, 1, -1, 0 ], [ -3, 3, 0, 1, 1, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -2, 2, 0, 2, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ], [ -3, 3, 0, 1, 1, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 0, 2, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ], [ -2, 2, 1, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ], [ -2, 2, 1, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -2, 2, 1, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -3, 2, 0, 1, 1, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -3, 2, 0, 1, 1, 0 ], [ -1, 2, 0, 1, 0, -1 ] ], 1 ], + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -2, 2, 0, 1, 0, 1 ], [ -3, 2, 0, 1, 1, 0 ], [ -1, 2, 0, 1, 0, -1 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -1, 2, 0, 1, 1, -1 ], [ -3, 2, 0, 1, 1, 0 ], [ -1, 2, 0, 1, 0, -1 ] ], 1 ], + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -1, 2, 0, 1, 1, -1 ], [ -3, 2, 0, 1, 1, 0 ], [ -1, 2, 0, 0, 1, 0 ] ], 1 ], + [ [ [ -2, 2, 0, 1, 1, -1 ], [ -1, 2, 0, 1, 1, -1 ], [ -3, 2, 0, 1, 1, 0 ], [ -1, 2, 0, 0, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 0, 1, 1, -1 ], [ -1, 2, 0, 1, 1, -1 ], [ -3, 2, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ], + [ [ [ -2, 2, 0, 1, 1, -1 ], [ -1, 2, 0, 1, 0, 0 ], [ -3, 2, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 0, 1, 2, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ -3, 2, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ], + [ [ [ -2, 1, 0, 1, 2, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ], + [ [ [ -2, 1, 0, 1, 2, 0 ], [ -2, 2, 0, 0, 2, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 0, 0, 2, 0 ], [ -2, 2, 0, 0, 2, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 0, 0, 2, 0 ], [ -2, 3, 0, 0, 2, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ], + [ [ [ -3, 3, 0, 0, 2, 0 ], [ -2, 3, 0, 0, 2, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 3, 0, 0, 2, 0 ], [ -2, 3, 0, 0, 2, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ], + [ [ [ -3, 3, 0, 0, 2, 0 ], [ -2, 3, 1, 0, 2, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ], + [ [ [ -3, 3, 0, 0, 2, 0 ], [ -2, 3, 1, 0, 2, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ] + ], + [ + [ [ [ -3, 3, 0, 0, 2, 0 ], [ -4, 3, 0, 1, 1, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ], + [ [ [ -3, 3, 0, 0, 1, 1 ], [ -4, 3, 0, 1, 1, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ] + ], + [ + [ [ [ -3, 3, 0, 0, 1, 1 ], [ -2, 3, 0, 0, 0, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ], + [ [ [ -3, 3, 0, 1, 1, 0 ], [ -2, 3, 0, 0, 0, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ], + [ [ [ -3, 3, 0, 1, 1, 0 ], [ -2, 3, 0, 0, 0, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -2, 3, 0, 0, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 0, 1, 0, 0 ], [ -2, 3, 0, 0, 0, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -2, 3, 0, 0, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 0, 1, 0, 0 ], [ -2, 3, 0, 0, 0, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -1, 3, 0, 1, 0, -1 ] ], 1 ], + [ [ [ -2, 3, 0, 1, 0, 0 ], [ -2, 3, 0, 0, 0, 0 ], [ -1, 3, 0, 0, 0, 0 ], [ -1, 3, 0, 1, 0, -1 ] ], 1 ], + [ [ [ -2, 3, 0, 1, 0, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -1, 3, 0, 0, 0, 0 ], [ -1, 3, 0, 1, 0, -1 ] ], 1 ] + ], + [ + [ [ [ -1, 3, -1, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -1, 3, 0, 0, 0, 0 ], [ -1, 3, 0, 1, 0, -1 ] ], 1 ], + [ [ [ -1, 3, -1, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 1 ], [ -1, 3, 0, 1, 0, -1 ] ], 1 ] + ], + [ + [ [ [ -1, 3, -1, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 1 ], [ -2, 3, 0, 2, -1, 0 ] ], 1 ], + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 1 ], [ -2, 3, 0, 2, -1, 0 ] ], 1 ], + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -3, 4, 0, 1, -1, 0 ], [ -2, 3, 0, 2, -1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 4, 0, 1, -1, 0 ], [ -3, 4, 0, 1, -1, 0 ], [ -2, 3, 0, 2, -1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 4, 0, 1, -1, 0 ], [ -3, 4, 0, 1, -1, 0 ], [ 0, 3, -1, 0, -1, 0 ] ], 1 ], + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 4, 0, 1, -1, 0 ], [ -2, 3, -1, 1, 0, 0 ], [ 0, 3, -1, 0, -1, 0 ] ], 1 ], + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 4, 0, 1, -1, 0 ], [ "Rest" ], [ 0, 3, -1, 0, -1, 0 ] ], 1 ], + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 4, 0, 1, -1, 0 ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ -2, 3, -1, 1, -1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3 ] + ] + ] +], +"last_changes": +[ + [ [ 0, 0, -1, 0, -1, 2 ], [ 0, -1, -1, 1, -1, 2 ], [ 1, 0, -1, 0, -1, 1 ], [ 0, -1, -1, 1, -1, 1 ] ], + [ [ 0, 0, -1, 1, -1, 1 ], [ 0, -1, -1, 1, -1, 2 ], [ 1, 0, -1, 0, -1, 1 ], [ 0, -1, -1, 1, -1, 1 ] ], + [ [ 0, 0, -1, 1, -1, 1 ], [ 0, -1, -1, 1, -1, 2 ], [ 0, -1, -1, 2, -1, 1 ], [ 0, -1, -1, 1, -1, 1 ] ], + [ [ 0, 0, -1, 1, -1, 1 ], [ 1, -1, -1, 0, -1, 1 ], [ 0, -1, -1, 2, -1, 1 ], [ 0, -1, -1, 1, -1, 1 ] ], + [ [ 0, 0, -1, 1, -1, 1 ], [ 1, 0, -1, 0, -1, 1 ], [ 0, -1, -1, 2, -1, 1 ], [ 0, -1, -1, 1, -1, 1 ] ] +], +"cur_uid": "4c745666", +"ref_uid": "nil", +"order_seed": 885120, +"dur_seed": 331456, +"motifs_seed": 712350, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0020576131687243, 0.068181818181818, 0.074074074074074, 0.0625, 0.20576131687243, 0.0625, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.78600823045268, 0.068181818181818, 0.98971193415638, 0.0625 ], +"passages_weights": [ 1, 1, 1, 1, 0.1 ], +"hd_exp": 1, +"hd_invert": 0, +"order": +[ + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 3, 0 ], [ 1, 2 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 0, 3 ], [ 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 2, 1 ], [ 0, 3 ], [ ] ], + [ [ 0 ], [ 1, 2, 3 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 2, 0 ], [ 1, 3 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0, 3 ], [ 2, 1 ], [ ] ], + [ [ 0, 3, 2 ], [ 1 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 24.724489795918, 25 ], +"passages_size": [ 0, 0 ], +"motif_edited": "true", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4e9f1dcc/4e9f1dcc_code.scd b/resources/string_quartet_3_rise/4e9f1dcc/4e9f1dcc_code.scd new file mode 100644 index 0000000..42b64ff --- /dev/null +++ b/resources/string_quartet_3_rise/4e9f1dcc/4e9f1dcc_code.scd @@ -0,0 +1,981 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/4e9f1dcc/4e9f1dcc_mus_model.json b/resources/string_quartet_3_rise/4e9f1dcc/4e9f1dcc_mus_model.json new file mode 100644 index 0000000..38e73bd --- /dev/null +++ b/resources/string_quartet_3_rise/4e9f1dcc/4e9f1dcc_mus_model.json @@ -0,0 +1,86 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -3, 1, 4, 2, 1, 0 ], [ -4, 1, 5, 2, 1, 1 ], [ -5, 1, 4, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -6, 1, 4, 2, 1, 2 ], [ -4, 1, 5, 2, 1, 1 ], [ -5, 1, 4, 2, 1, 1 ], [ -4, 1, 4, 2, 1, 0 ] ], 1 ], + [ [ [ -6, 1, 4, 2, 1, 2 ], [ -4, 1, 5, 2, 1, 1 ], [ -5, 1, 4, 2, 1, 1 ], [ -4, 1, 5, 2, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -6, 1, 4, 2, 1, 2 ], [ -4, 1, 4, 2, 0, 1 ], [ -5, 1, 4, 2, 1, 1 ], [ -4, 1, 5, 2, 0, 1 ] ], 1 ], + [ [ [ -6, 1, 4, 2, 1, 2 ], [ -4, 1, 4, 2, 0, 1 ], [ -5, 1, 5, 2, 0, 2 ], [ -4, 1, 5, 2, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -6, 1, 4, 2, 1, 2 ], [ -4, 1, 5, 2, -1, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -4, 1, 5, 2, 0, 1 ] ], 1 ], + [ [ [ -5, 1, 4, 2, 0, 2 ], [ -4, 1, 5, 2, -1, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -4, 1, 5, 2, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -4, 0, 5, 2, 0, 1 ], [ -4, 1, 5, 2, -1, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -4, 1, 5, 2, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -4, 0, 5, 2, 0, 1 ], [ -3, 1, 5, 2, 0, 0 ], [ -5, 1, 5, 2, 0, 2 ], [ -4, 1, 5, 2, 0, 1 ] ], 1 ], + [ [ [ -5, 2, 5, 2, 0, 1 ], [ -3, 1, 5, 2, 0, 0 ], [ -5, 1, 5, 2, 0, 2 ], [ -4, 1, 5, 2, 0, 1 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 5, 2, 0, 1 ], [ -3, 1, 5, 2, 0, 0 ], [ -5, 1, 5, 2, 0, 2 ], [ -4, 1, 5, 2, -1, 2 ] ], 1 ], + [ [ [ -5, 2, 5, 2, 0, 1 ], [ -5, 1, 5, 2, 0, 3 ], [ -5, 1, 5, 2, 0, 2 ], [ -4, 1, 5, 2, -1, 2 ] ], 1 ], + [ [ [ -4, 1, 5, 1, 0, 2 ], [ -5, 1, 5, 2, 0, 3 ], [ -5, 1, 5, 2, 0, 2 ], [ -4, 1, 5, 2, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -4, 1, 5, 1, 0, 2 ], [ -6, 1, 5, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -4, 1, 5, 2, -1, 2 ] ], 1 ], + [ [ [ -4, 1, 5, 1, 0, 2 ], [ -6, 1, 5, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -5, 2, 5, 2, 0, 2 ] ], 1 ], + [ [ [ -4, 0, 5, 2, 0, 2 ], [ -6, 1, 5, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -5, 2, 5, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -4, 0, 5, 2, 0, 2 ], [ -6, 2, 5, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -5, 2, 5, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -4, 0, 5, 2, 0, 2 ], [ -5, 1, 4, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -5, 2, 5, 2, 0, 2 ] ], 1 ], + [ [ [ -6, 1, 5, 1, 0, 2 ], [ -5, 1, 4, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -5, 2, 5, 2, 0, 2 ] ], 1 ], + [ [ [ -6, 1, 5, 1, 0, 2 ], [ -5, 1, 4, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 3 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -4, 0, 5, 2, 0, 2 ], [ -6, 1, 5, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -5, 2, 5, 2, 0, 2 ] ], + [ [ -4, 0, 5, 2, 0, 2 ], [ -6, 2, 5, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -5, 2, 5, 2, 0, 2 ] ], + [ [ -4, 0, 5, 2, 0, 2 ], [ -5, 1, 4, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -5, 2, 5, 2, 0, 2 ] ], + [ [ -6, 1, 5, 1, 0, 2 ], [ -5, 1, 4, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -5, 2, 5, 2, 0, 2 ] ], + [ [ -6, 1, 5, 1, 0, 2 ], [ -5, 1, 4, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 3 ] ] +], +"cur_uid": "4e9f1dcc", +"ref_uid": "4b40ed47", +"order_seed": 207247, +"dur_seed": 435476, +"motifs_seed": 299289, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0061728395061728, 0.10227272727273, 0.074074074074074, 0.10227272727273, 0.2037037037037, 0.090909090909091, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.7880658436214, 0.034090909090909, 1, 0.034090909090909 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 0.3 ], +"hd_exp": 3, +"hd_invert": 0, +"order": +[ + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 1, 2 ], [ 0, 3 ], [ ] ], + [ [ 0, 3 ], [ 1, 2 ], [ ] ], + [ [ 3, 2 ], [ 1, 0 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 2 ], [ 3, 1, 0 ], [ ] ], + [ [ 2 ], [ 1, 3, 0 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 10.091836734694, 10.091836734694 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_I.ly b/resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_I.ly new file mode 100644 index 0000000..bca81dd --- /dev/null +++ b/resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_I.ly @@ -0,0 +1,22 @@ +{ + { b1^\markup { \pad-markup #0.2 "+36"} } + \bar "|" + { fis'1^\markup { \pad-markup #0.2 "+12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "+1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↓" }} ~ } + \bar "|" + { a'1 } + \bar "|" + { ais'1^\markup { \pad-markup #0.2 "-46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'2 b'2^\markup { \pad-markup #0.2 "-7"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_II.ly b/resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_II.ly new file mode 100644 index 0000000..48f81fd --- /dev/null +++ b/resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_II.ly @@ -0,0 +1,22 @@ +{ + { gis1^\markup { \pad-markup #0.2 "-23"} ~ } + \bar "|" + { gis1 } + \bar "|" + { dis'1^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }} ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_III.ly b/resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_III.ly new file mode 100644 index 0000000..6a243ff --- /dev/null +++ b/resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_III.ly @@ -0,0 +1,22 @@ +{ + { c''1^\markup { \pad-markup #0.2 "-37"} ~ } + \bar "|" + { c''2 d'2^\markup { \pad-markup #0.2 "+25"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↓" }} ~ } + \bar "|" + { d'2 a'2^\markup { \pad-markup #0.2 "+1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↓" }} ~ } + \bar "|" + { a'1 } + \bar "|" + { ais'1^\markup { \pad-markup #0.2 "-29"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↓" }} ~ } + \bar "|" + { ais'2 b'2^\markup { \pad-markup #0.2 "-7"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }} ~ } + \bar "|" + { b'2 dis2^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { dis1 } + \bar "|" + { ais2^\markup { \pad-markup #0.2 "-46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} b2^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }} ~ } + \bar "|" + { b1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_IV.ly b/resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_IV.ly new file mode 100644 index 0000000..a370cf4 --- /dev/null +++ b/resources/string_quartet_3_rise/4e9f1dcc/lilypond/part_IV.ly @@ -0,0 +1,22 @@ +{ + { b'2^\markup { \pad-markup #0.2 "+36"} e2^\markup { \pad-markup #0.2 "+17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↑" }} ~ } + \bar "|" + { e1 ~ } + \bar "|" + { e1 } + \bar "|" + { b2^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }} b2^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} ~ } + \bar "|" + { b2 cis'2^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }} ~ } + \bar "|" + { cis'1 } + \bar "|" + { f'1^\markup { \pad-markup #0.2 "-17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }} ~ } + \bar "|" + { f'2 gis'2^\markup { \pad-markup #0.2 "-50"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { gis'1 } + \bar "|" + { f,1^\markup { \pad-markup #0.2 "-17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/521654f8/521654f8_code.scd b/resources/string_quartet_3_rise/521654f8/521654f8_code.scd new file mode 100644 index 0000000..42b64ff --- /dev/null +++ b/resources/string_quartet_3_rise/521654f8/521654f8_code.scd @@ -0,0 +1,981 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/521654f8/521654f8_mus_model.json b/resources/string_quartet_3_rise/521654f8/521654f8_mus_model.json new file mode 100644 index 0000000..6106aa7 --- /dev/null +++ b/resources/string_quartet_3_rise/521654f8/521654f8_mus_model.json @@ -0,0 +1,87 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -3, 0, 1, 2, 1, 0 ], [ -3, 1, 1, 3, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -2, 0, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 0, 1, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -2, 0, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -3, 1, 1, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -2, 0, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -3, 1, 1, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -1, 1, 0, 2, 1, -1 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 1, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -1, 1, 0, 2, 1, -1 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 1, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -3, 2, 1, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -3, 2, 1, 2, 1, 0 ], [ -3, 1, 1, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 1, 2, 1, 0 ], [ -4, 2, 3, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -5, 2, 2, 2, 1, 0 ], [ -4, 2, 3, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -5, 2, 2, 2, 1, 0 ], [ -4, 2, 3, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 2, 2, 1, 0 ], [ -4, 2, 3, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -4, 2, 3, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -4, 2, 3, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -4, 3, 3, 2, 1, 0 ] ], 1 ], + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -4, 2, 3, 2, 1, 0 ], [ -4, 2, 3, 2, 1, 1 ], [ -4, 3, 3, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -4, 2, 3, 2, 1, 0 ], [ -3, 3, 3, 1, 1, 0 ], [ -4, 3, 3, 2, 1, 0 ] ], 1 ], + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -4, 2, 2, 3, 1, 0 ], [ -3, 3, 3, 1, 1, 0 ], [ -4, 3, 3, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -4, 2, 2, 3, 1, 0 ], [ -3, 3, 3, 1, 1, 0 ], [ -5, 2, 3, 4, 1, 0 ] ], 1 ], + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -4, 2, 2, 3, 1, 0 ], [ -4, 2, 3, 3, 1, -1 ], [ -5, 2, 3, 4, 1, 0 ] ], 1 ], + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -6, 2, 4, 3, 1, 0 ], [ -4, 2, 3, 3, 1, -1 ], [ -5, 2, 3, 4, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -6, 2, 4, 3, 1, 0 ], [ -6, 3, 3, 4, 1, 0 ], [ -5, 2, 3, 4, 1, 0 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -5, 2, 3, 3, 1, 0 ], [ -4, 2, 2, 3, 1, 0 ], [ -3, 3, 3, 1, 1, 0 ], [ -4, 3, 3, 2, 1, 0 ] ], + [ [ -5, 2, 3, 3, 1, 0 ], [ -4, 2, 2, 3, 1, 0 ], [ -3, 3, 3, 1, 1, 0 ], [ -5, 2, 3, 4, 1, 0 ] ], + [ [ -5, 2, 3, 3, 1, 0 ], [ -4, 2, 2, 3, 1, 0 ], [ -4, 2, 3, 3, 1, -1 ], [ -5, 2, 3, 4, 1, 0 ] ], + [ [ -5, 2, 3, 3, 1, 0 ], [ -6, 2, 4, 3, 1, 0 ], [ -4, 2, 3, 3, 1, -1 ], [ -5, 2, 3, 4, 1, 0 ] ], + [ [ -5, 2, 3, 3, 1, 0 ], [ -6, 2, 4, 3, 1, 0 ], [ -6, 3, 3, 4, 1, 0 ], [ -5, 2, 3, 4, 1, 0 ] ] +], +"cur_uid": "521654f8", +"ref_uid": "7ede7adb", +"order_seed": 185690, +"dur_seed": 954582, +"motifs_seed": 356283, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0061728395061728, 0.10227272727273, 0.074074074074074, 0.10227272727273, 0.2037037037037, 0.090909090909091, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.7880658436214, 0.034090909090909, 1, 0.034090909090909 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 0.6 ], +"hd_exp": 6, +"hd_invert": 0, +"order": +[ + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 0, 3 ], [ 2, 1 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 10.091836734694, 10.091836734694 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/521654f8/lilypond/part_I.ly b/resources/string_quartet_3_rise/521654f8/lilypond/part_I.ly new file mode 100644 index 0000000..4b86b39 --- /dev/null +++ b/resources/string_quartet_3_rise/521654f8/lilypond/part_I.ly @@ -0,0 +1,22 @@ +{ + { f'1^\markup { \pad-markup #0.2 "-25"} ~ } + \bar "|" + { f'2 c''2^\markup { \pad-markup #0.2 "-50"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↓" }} ~ } + \bar "|" + { c''2 g2^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 } + \bar "|" + { b1^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { b2 ais'2^\markup { \pad-markup #0.2 "-46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'2 ais'2^\markup { \pad-markup #0.2 "-11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} ~ } + \bar "|" + { ais'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/521654f8/lilypond/part_II.ly b/resources/string_quartet_3_rise/521654f8/lilypond/part_II.ly new file mode 100644 index 0000000..03e091f --- /dev/null +++ b/resources/string_quartet_3_rise/521654f8/lilypond/part_II.ly @@ -0,0 +1,22 @@ +{ + { gis'1^\markup { \pad-markup #0.2 "-9"} ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 } + \bar "|" + { b'1^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'1 } + \bar "|" + { b'2^\markup { \pad-markup #0.2 "-8"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }} c''2^\markup { \pad-markup #0.2 "-15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↓" }} ~ } + \bar "|" + { c''1 } + \bar "|" + { e'1^\markup { \pad-markup #0.2 "-20"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/521654f8/lilypond/part_III.ly b/resources/string_quartet_3_rise/521654f8/lilypond/part_III.ly new file mode 100644 index 0000000..8972ef2 --- /dev/null +++ b/resources/string_quartet_3_rise/521654f8/lilypond/part_III.ly @@ -0,0 +1,22 @@ +{ + { a'2^\markup { \pad-markup #0.2 "+46"} c''2^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''2 c'2^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} } + \bar "|" + { dis'1^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }} ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'1 } + \bar "|" + { gis'1^\markup { \pad-markup #0.2 "+34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { gis'2 e2^\markup { \pad-markup #0.2 "+7"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/521654f8/lilypond/part_IV.ly b/resources/string_quartet_3_rise/521654f8/lilypond/part_IV.ly new file mode 100644 index 0000000..25ed6e8 --- /dev/null +++ b/resources/string_quartet_3_rise/521654f8/lilypond/part_IV.ly @@ -0,0 +1,22 @@ +{ + { f1^\markup { \pad-markup #0.2 "-25"} } + \bar "|" + { c'1^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} } + \bar "|" + { g'1^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'2 b,2^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { b,2 fis2^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }} } + \bar "|" + { c'1^\markup { \pad-markup #0.2 "+21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/531df78c/531df78c_code.scd b/resources/string_quartet_3_rise/531df78c/531df78c_code.scd new file mode 100644 index 0000000..42b64ff --- /dev/null +++ b/resources/string_quartet_3_rise/531df78c/531df78c_code.scd @@ -0,0 +1,981 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/531df78c/531df78c_mus_model.json b/resources/string_quartet_3_rise/531df78c/531df78c_mus_model.json new file mode 100644 index 0000000..4f78cc2 --- /dev/null +++ b/resources/string_quartet_3_rise/531df78c/531df78c_mus_model.json @@ -0,0 +1,157 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -6, 2, 4, 1, 1, 2 ], [ -5, 3, 4, 1, 1, 2 ], [ -6, 3, 4, 1, 1, 2 ], [ -6, 3, 4, 2, 1, 2 ] ], 1 ], + [ [ [ -7, 4, 4, 1, 1, 2 ], [ -5, 3, 4, 1, 1, 2 ], [ -6, 3, 4, 1, 1, 2 ], [ -6, 3, 4, 2, 1, 2 ] ], 1 ], + [ [ [ -7, 4, 4, 1, 1, 2 ], [ -5, 3, 4, 1, 1, 2 ], [ -6, 3, 4, 1, 1, 2 ], [ -5, 2, 4, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 4, 0, 1, 2 ], [ -5, 3, 4, 1, 1, 2 ], [ -6, 3, 4, 1, 1, 2 ], [ -5, 2, 4, 1, 1, 2 ] ], 1 ], + [ [ [ -5, 3, 4, 0, 1, 2 ], [ -5, 3, 4, 1, 1, 2 ], [ -6, 3, 4, 1, 1, 2 ], [ -6, 4, 4, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 4, 0, 1, 2 ], [ -4, 3, 4, 0, 1, 2 ], [ -6, 3, 4, 1, 1, 2 ], [ -6, 4, 4, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 4, 0, 1, 2 ], [ -4, 3, 4, 0, 1, 2 ], [ -6, 3, 4, 1, 1, 2 ], [ -5, 3, 3, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 4, 0, 1, 2 ], [ -5, 4, 3, 1, 1, 2 ], [ -6, 3, 4, 1, 1, 2 ], [ -5, 3, 3, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 4, 3, 1, 1, 2 ], [ -5, 4, 3, 1, 1, 2 ], [ -6, 3, 4, 1, 1, 2 ], [ -5, 3, 3, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 4, 1, 1, 2 ], [ -5, 4, 3, 1, 1, 2 ], [ -6, 3, 4, 1, 1, 2 ], [ -5, 3, 3, 1, 1, 2 ] ], 1 ], + [ [ [ -5, 3, 4, 1, 1, 2 ], [ -6, 3, 3, 1, 1, 2 ], [ -6, 3, 4, 1, 1, 2 ], [ -5, 3, 3, 1, 1, 2 ] ], 1 ], + [ [ [ -5, 3, 4, 1, 1, 2 ], [ -6, 3, 3, 1, 1, 2 ], [ -5, 2, 3, 1, 1, 2 ], [ -5, 3, 3, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 4, 1, 1, 2 ], [ -5, 2, 4, 1, 1, 2 ], [ -5, 2, 3, 1, 1, 2 ], [ -5, 3, 3, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 4, 1, 1, 2 ], [ -5, 2, 4, 1, 1, 2 ], [ -6, 4, 3, 1, 1, 2 ], [ -5, 3, 3, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 4, 1, 1, 2 ], [ -5, 2, 4, 1, 1, 2 ], [ -4, 1, 4, 1, 1, 2 ], [ -5, 3, 3, 1, 1, 2 ] ], 1 ], + [ [ [ -5, 4, 3, 1, 1, 2 ], [ -5, 2, 4, 1, 1, 2 ], [ -4, 1, 4, 1, 1, 2 ], [ -5, 3, 3, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -5, 4, 3, 1, 1, 2 ], [ -5, 2, 4, 1, 1, 2 ], [ -4, 2, 3, 1, 1, 2 ], [ -5, 3, 3, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -5, 4, 3, 1, 1, 2 ], [ -5, 2, 4, 1, 1, 2 ], [ -4, 2, 3, 1, 1, 2 ], [ -6, 5, 3, 1, 1, 2 ] ], 1 ], + [ [ [ -5, 4, 3, 1, 1, 2 ], [ -5, 4, 3, 0, 1, 2 ], [ -4, 2, 3, 1, 1, 2 ], [ -6, 5, 3, 1, 1, 2 ] ], 1 ], + [ [ [ -5, 4, 3, 1, 1, 2 ], [ -5, 4, 3, 0, 1, 2 ], [ -7, 5, 3, 1, 1, 2 ], [ -6, 5, 3, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -5, 4, 3, 1, 1, 2 ], [ -5, 4, 3, 0, 1, 2 ], [ -7, 5, 3, 1, 1, 2 ], [ -5, 5, 3, 0, 1, 2 ] ], 1 ], + [ [ [ -5, 4, 3, 1, 1, 2 ], [ -6, 5, 2, 1, 1, 2 ], [ -7, 5, 3, 1, 1, 2 ], [ -5, 5, 3, 0, 1, 2 ] ], 1 ], + [ [ [ -6, 5, 3, 1, 1, 2 ], [ -6, 5, 2, 1, 1, 2 ], [ -7, 5, 3, 1, 1, 2 ], [ -5, 5, 3, 0, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 5, 3, 1, 1, 2 ], [ -6, 5, 2, 1, 1, 2 ], [ -6, 5, 3, 0, 1, 2 ], [ -5, 5, 3, 0, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 5, 3, 1, 1, 2 ], [ -6, 5, 2, 1, 1, 2 ], [ -6, 5, 3, 0, 1, 2 ], [ -6, 5, 4, 1, 1, 2 ] ], 1 ], + [ [ [ -6, 5, 3, 1, 1, 2 ], [ -6, 6, 3, 0, 1, 2 ], [ -6, 5, 3, 0, 1, 2 ], [ -6, 5, 4, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 4, 0, 1, 2 ], [ -6, 6, 3, 0, 1, 2 ], [ -6, 5, 3, 0, 1, 2 ], [ -6, 5, 4, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 4, 0, 1, 2 ], [ -6, 6, 3, 0, 1, 2 ], [ -6, 5, 4, 0, 1, 2 ], [ -6, 5, 4, 1, 1, 2 ] ], 1 ], + [ [ [ -6, 6, 4, 0, 1, 2 ], [ -7, 5, 4, 1, 1, 3 ], [ -6, 5, 4, 0, 1, 2 ], [ -6, 5, 4, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 5, 4, 0, 1, 3 ], [ -7, 5, 4, 1, 1, 3 ], [ -6, 5, 4, 0, 1, 2 ], [ -6, 5, 4, 1, 1, 2 ] ], 1 ], + [ [ [ -6, 5, 4, 0, 1, 3 ], [ -6, 6, 4, 0, 1, 2 ], [ -6, 5, 4, 0, 1, 2 ], [ -6, 5, 4, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 5, 4, 0, 1, 3 ], [ -6, 6, 4, 0, 1, 2 ], [ -6, 5, 4, 0, 1, 2 ], [ -6, 6, 5, 0, 1, 2 ] ], 1 ], + [ [ [ -7, 5, 4, 0, 1, 2 ], [ -6, 6, 4, 0, 1, 2 ], [ -6, 5, 4, 0, 1, 2 ], [ -6, 6, 5, 0, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 5, 4, 0, 1, 2 ], [ -6, 6, 4, 0, 1, 2 ], [ -7, 7, 4, 0, 1, 2 ], [ -6, 6, 5, 0, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 5, 4, 0, 1, 2 ], [ -6, 6, 4, 0, 1, 2 ], [ -7, 7, 4, 0, 1, 2 ], [ -7, 6, 4, 0, 1, 2 ] ], 1 ], + [ [ [ -7, 5, 4, 0, 1, 2 ], [ -6, 6, 4, 0, 1, 2 ], [ -7, 6, 4, 1, 1, 2 ], [ -7, 6, 4, 0, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 5, 4, 0, 1, 2 ], [ -6, 6, 4, 0, 1, 2 ], [ -5, 6, 4, -1, 1, 2 ], [ -7, 6, 4, 0, 1, 2 ] ], 1 ], + [ [ [ -8, 7, 4, 0, 1, 2 ], [ -6, 6, 4, 0, 1, 2 ], [ -5, 6, 4, -1, 1, 2 ], [ -7, 6, 4, 0, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -8, 7, 4, 0, 1, 2 ], [ -6, 6, 4, 0, 1, 2 ], [ -5, 6, 4, -1, 1, 2 ], [ -6, 7, 4, -1, 1, 2 ] ], 1 ], + [ [ [ -7, 7, 4, -1, 1, 2 ], [ -6, 6, 4, 0, 1, 2 ], [ -5, 6, 4, -1, 1, 2 ], [ -6, 7, 4, -1, 1, 2 ] ], 1 ], + [ [ [ -7, 7, 4, -1, 1, 2 ], [ -6, 6, 4, -1, 1, 2 ], [ -5, 6, 4, -1, 1, 2 ], [ -6, 7, 4, -1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 4, -1, 1, 2 ], [ -6, 6, 4, -1, 1, 2 ], [ -5, 6, 4, -1, 1, 2 ], [ -6, 7, 4, -1, 1, 2 ] ], 1 ], + [ [ [ -7, 6, 4, -1, 1, 2 ], [ -5, 6, 4, -1, 0, 2 ], [ -5, 6, 4, -1, 1, 2 ], [ -6, 7, 4, -1, 1, 2 ] ], 1 ], + [ [ [ -7, 6, 4, -1, 1, 2 ], [ -5, 6, 4, -1, 0, 2 ], [ -5, 6, 4, -1, 1, 2 ], [ -6, 6, 4, 0, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 4, -1, 1, 2 ], [ -5, 6, 4, -1, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 1, 2 ] ], 1 ], + [ [ [ -7, 6, 4, -1, 1, 2 ], [ -6, 6, 5, 0, 1, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 1, 2 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -7, 6, 4, -1, 1, 2 ], [ -6, 6, 4, -1, 1, 2 ], [ -5, 6, 4, -1, 1, 2 ], [ -6, 7, 4, -1, 1, 2 ] ], + [ [ -7, 6, 4, -1, 1, 2 ], [ -5, 6, 4, -1, 0, 2 ], [ -5, 6, 4, -1, 1, 2 ], [ -6, 7, 4, -1, 1, 2 ] ], + [ [ -7, 6, 4, -1, 1, 2 ], [ -5, 6, 4, -1, 0, 2 ], [ -5, 6, 4, -1, 1, 2 ], [ -6, 6, 4, 0, 1, 2 ] ], + [ [ -7, 6, 4, -1, 1, 2 ], [ -5, 6, 4, -1, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 1, 2 ] ], + [ [ -7, 6, 4, -1, 1, 2 ], [ -6, 6, 5, 0, 1, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 1, 2 ] ] +], +"cur_uid": "531df78c", +"ref_uid": "78a94ed1", +"order_seed": 561112, +"dur_seed": 221568, +"motifs_seed": 960151, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0061728395061728, 0.10227272727273, 0.074074074074074, 0.10227272727273, 0.2037037037037, 0.090909090909091, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.7880658436214, 0.034090909090909, 1, 0.034090909090909 ], +"passages_weights": [ 0.08, 0.47, 0.43, 1, 1 ], +"hd_exp": 10, +"hd_invert": 0, +"order": +[ + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 2, 1 ], [ 0, 3 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 3 ], [ 0, 1, 2 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 2 ], [ 3, 1, 0 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 0, 2 ], [ 3, 1 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 3, 1 ], [ 2, 0 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 25, 25.244897959184 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/531df78c/lilypond/part_I.ly b/resources/string_quartet_3_rise/531df78c/lilypond/part_I.ly new file mode 100644 index 0000000..0acee0f --- /dev/null +++ b/resources/string_quartet_3_rise/531df78c/lilypond/part_I.ly @@ -0,0 +1,48 @@ +{ + { fis'1^\markup { \pad-markup #0.2 "+21"} } + \bar "|" + { d'1^\markup { \pad-markup #0.2 "-50"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} } + \bar "|" + { e'1^\markup { \pad-markup #0.2 "-46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} } + \bar "|" + { f'1^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }} ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'2 g'2^\markup { \pad-markup #0.2 "-30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { g'1 } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "+1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { a'1 } + \bar "|" + { b'1^\markup { \pad-markup #0.2 "-44"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'2 c''2^\markup { \pad-markup #0.2 "-24"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }} ~ } + \bar "|" + { c''1 } + \bar "|" + { gis1^\markup { \pad-markup #0.2 "-11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { gis1 } + \bar "|" + { f'1^\markup { \pad-markup #0.2 "+22"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }} ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'2 gis'2^\markup { \pad-markup #0.2 "-11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} ~ } + \bar "|" + { gis'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/531df78c/lilypond/part_II.ly b/resources/string_quartet_3_rise/531df78c/lilypond/part_II.ly new file mode 100644 index 0000000..71926a1 --- /dev/null +++ b/resources/string_quartet_3_rise/531df78c/lilypond/part_II.ly @@ -0,0 +1,48 @@ +{ + { a1^\markup { \pad-markup #0.2 "-48"} ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a2 ais2^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { ais2 c'2^\markup { \pad-markup #0.2 "-32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }} } + \bar "|" + { fis'1^\markup { \pad-markup #0.2 "+48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} } + \bar "|" + { ais'1^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }} ~ } + \bar "|" + { ais'2 g2^\markup { \pad-markup #0.2 "-30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g2 a2^\markup { \pad-markup #0.2 "+1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a2 cis'2^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'2 dis'2^\markup { \pad-markup #0.2 "-9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { dis'2 fis'2^\markup { \pad-markup #0.2 "-42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }} } + \bar "|" + { ais'1^\markup { \pad-markup #0.2 "+21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }} ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 } + \bar "|" + { d'1^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/531df78c/lilypond/part_III.ly b/resources/string_quartet_3_rise/531df78c/lilypond/part_III.ly new file mode 100644 index 0000000..95b2a0b --- /dev/null +++ b/resources/string_quartet_3_rise/531df78c/lilypond/part_III.ly @@ -0,0 +1,48 @@ +{ + { a'1^\markup { \pad-markup #0.2 "-48"} ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'2 b'2^\markup { \pad-markup #0.2 "-17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { b'2 c''2^\markup { \pad-markup #0.2 "-32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }} ~ } + \bar "|" + { c''1 } + \bar "|" + { f1^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} } + \bar "|" + { d'1^\markup { \pad-markup #0.2 "-50"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 } + \bar "|" + { d'1^\markup { \pad-markup #0.2 "-1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }} ~ } + \bar "|" + { d'2 dis'2^\markup { \pad-markup #0.2 "-16"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }} ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'2 e'2^\markup { \pad-markup #0.2 "+3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }} ~ } + \bar "|" + { e'1 } + \bar "|" + { g'1^\markup { \pad-markup #0.2 "-3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↑" }} } + \bar "|" + { gis'1^\markup { \pad-markup #0.2 "-11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }} ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 } + \bar "|" + { ais1^\markup { \pad-markup #0.2 "+21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} } + \bar "|" + { f'1^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} ~ } + \bar "|" + { f'2 c''2^\markup { \pad-markup #0.2 "-24"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/531df78c/lilypond/part_IV.ly b/resources/string_quartet_3_rise/531df78c/lilypond/part_IV.ly new file mode 100644 index 0000000..52220bd --- /dev/null +++ b/resources/string_quartet_3_rise/531df78c/lilypond/part_IV.ly @@ -0,0 +1,48 @@ +{ + { d2^\markup { \pad-markup #0.2 "-50"} e2^\markup { \pad-markup #0.2 "-46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { e2 b2^\markup { \pad-markup #0.2 "-17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 } + \bar "|" + { c'2^\markup { \pad-markup #0.2 "-32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} a'2^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'2 c''2^\markup { \pad-markup #0.2 "-32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }} ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''1 } + \bar "|" + { g'1^\markup { \pad-markup #0.2 "-30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g'1 } + \bar "|" + { gis'1^\markup { \pad-markup #0.2 "-11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }} ~ } + \bar "|" + { gis'2 a'2^\markup { \pad-markup #0.2 "+28"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }} ~ } + \bar "|" + { a'1 } + \bar "|" + { cis1^\markup { \pad-markup #0.2 "-13"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { cis1 ~ } + \bar "|" + { cis2 dis2^\markup { \pad-markup #0.2 "-9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { dis2 f2^\markup { \pad-markup #0.2 "+22"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { f2 ais,2^\markup { \pad-markup #0.2 "+21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { ais,1 ~ } + \bar "|" + { ais,1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/553fbac7/553fbac7_code.scd b/resources/string_quartet_3_rise/553fbac7/553fbac7_code.scd new file mode 100644 index 0000000..c4e3d1b --- /dev/null +++ b/resources/string_quartet_3_rise/553fbac7/553fbac7_code.scd @@ -0,0 +1,979 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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/string_quartet_3_rise/553fbac7/553fbac7_mus_model.json b/resources/string_quartet_3_rise/553fbac7/553fbac7_mus_model.json new file mode 100644 index 0000000..da17f28 --- /dev/null +++ b/resources/string_quartet_3_rise/553fbac7/553fbac7_mus_model.json @@ -0,0 +1,88 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ "Rest" ], [ 0, 1, 0, 0, 0, 0 ], [ "Rest" ] ], 1 ], + [ [ [ 0, 1, 1, 0, 0, 0 ], [ "Rest" ], [ 0, 1, 0, 0, 0, 0 ], [ "Rest" ] ], 1 ], + [ [ [ 0, 1, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ "Rest" ] ], 1 ], + [ [ [ 0, 1, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -2, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 2, 1, -1, -1, -1, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1 ], + [ [ [ -2, 1, 0, 0, 0, 0 ], [ 1, 1, -1, -1, -1, 0 ], [ 2, 1, -1, -1, -1, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1 ], + [ [ [ 0, 1, -1, -1, -1, 0 ], [ 1, 1, -1, -1, -1, 0 ], [ 2, 1, -1, -1, -1, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ] ], + [ [ -2, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 0, 1, 1, 0, 0, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], + [ [ -2, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ], [ 2, 1, -1, -1, -1, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], + [ [ -2, 1, 0, 0, 0, 0 ], [ 1, 1, -1, -1, -1, 0 ], [ 2, 1, -1, -1, -1, 0 ], [ 1, 1, -1, -1, 0, 0 ] ], + [ [ 0, 1, -1, -1, -1, 0 ], [ 1, 1, -1, -1, -1, 0 ], [ 2, 1, -1, -1, -1, 0 ], [ 1, 1, -1, -1, 0, 0 ] ] +], +"cur_uid": "553fbac7", +"ref_uid": "4874dd07", +"order_seed": 846356, +"dur_seed": 787877, +"motifs_seed": 285247, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0061728395061728, 0.10227272727273, 0.074074074074074, 0.10227272727273, 0.2037037037037, 0.090909090909091, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.7880658436214, 0.034090909090909, 1, 0.034090909090909 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 0.9 ], +"hd_exp": 9, +"hd_invert": 0, +"order": +[ + [ [ 2 ], [ 0, 1, 3 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 3 ], [ 1, 2, 0 ], [ ] ], + [ [ 0, 3 ], [ 2, 1 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 10.061224489796, 10.091836734694 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/57ef90e6/57ef90e6_code.scd b/resources/string_quartet_3_rise/57ef90e6/57ef90e6_code.scd new file mode 100644 index 0000000..0d8c24b --- /dev/null +++ b/resources/string_quartet_3_rise/57ef90e6/57ef90e6_code.scd @@ -0,0 +1,975 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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/string_quartet_3_rise/57ef90e6/57ef90e6_mus_model.json b/resources/string_quartet_3_rise/57ef90e6/57ef90e6_mus_model.json new file mode 100644 index 0000000..0f2fec3 --- /dev/null +++ b/resources/string_quartet_3_rise/57ef90e6/57ef90e6_mus_model.json @@ -0,0 +1,166 @@ +{ +"music_data": +[ + [ + [ + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ] ], 1 ], + [ [ [ "Rest" ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, -1, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, 1, 0, 0, 0, -1 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, -1, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, 1, 0, 0, 0, -1 ], [ -1, 1, 0, 1, 0, 0 ], [ 0, 1, 0, 0, -1, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 1, 0, 0, 0, -1 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ -1, 1, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 1 ], + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -1, 2, 0, 1, -1, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 1 ], + [ [ [ -2, 2, 0, 2, 0, 0 ], [ -1, 2, 0, 1, -1, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 0, 2, 0, 0 ], [ -1, 2, 0, 1, -1, 0 ], [ -3, 3, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 0, 2, 0, 0 ], [ -1, 2, 0, 1, -1, 0 ], [ -3, 3, 0, 1, 1, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -2, 2, 0, 2, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ], [ -3, 3, 0, 1, 1, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 0, 2, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ], [ -2, 2, 1, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -2, 3, 0, 1, 0, 0 ], [ -2, 2, 1, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -2, 2, 1, 1, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ 0, 2, -1, 0, 0, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -3, 2, 0, 1, 1, 0 ], [ -1, 2, 0, 1, 0, 0 ] ], 1 ], + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -1, 2, -1, 1, 0, 0 ], [ -3, 2, 0, 1, 1, 0 ], [ -1, 2, 0, 1, 0, -1 ] ], 1 ], + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -2, 2, 0, 1, 0, 1 ], [ -3, 2, 0, 1, 1, 0 ], [ -1, 2, 0, 1, 0, -1 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -1, 2, 0, 1, 1, -1 ], [ -3, 2, 0, 1, 1, 0 ], [ -1, 2, 0, 1, 0, -1 ] ], 1 ], + [ [ [ -3, 2, 0, 1, 0, 0 ], [ -1, 2, 0, 1, 1, -1 ], [ -3, 2, 0, 1, 1, 0 ], [ -1, 2, 0, 0, 1, 0 ] ], 1 ], + [ [ [ -2, 2, 0, 1, 1, -1 ], [ -1, 2, 0, 1, 1, -1 ], [ -3, 2, 0, 1, 1, 0 ], [ -1, 2, 0, 0, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 2, 0, 1, 1, -1 ], [ -1, 2, 0, 1, 1, -1 ], [ -3, 2, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ], + [ [ [ -2, 2, 0, 1, 1, -1 ], [ -1, 2, 0, 1, 0, 0 ], [ -3, 2, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 0, 1, 2, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ -3, 2, 0, 1, 1, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ], + [ [ [ -2, 1, 0, 1, 2, 0 ], [ -1, 2, 0, 1, 0, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ], + [ [ [ -2, 1, 0, 1, 2, 0 ], [ -2, 2, 0, 0, 2, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 0, 0, 2, 0 ], [ -2, 2, 0, 0, 2, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 0, 0, 2, 0 ], [ -2, 3, 0, 0, 2, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ], + [ [ [ -3, 3, 0, 0, 2, 0 ], [ -2, 3, 0, 0, 2, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -2, 2, 0, 1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 3, 0, 0, 2, 0 ], [ -2, 3, 0, 0, 2, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ], + [ [ [ -3, 3, 0, 0, 2, 0 ], [ -2, 3, 1, 0, 2, 0 ], [ -4, 3, 0, 1, 2, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ], + [ [ [ -3, 3, 0, 0, 2, 0 ], [ -2, 3, 1, 0, 2, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ] + ], + [ + [ [ [ -3, 3, 0, 0, 2, 0 ], [ -4, 3, 0, 1, 1, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ], + [ [ [ -3, 3, 0, 0, 1, 1 ], [ -4, 3, 0, 1, 1, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ] + ], + [ + [ [ [ -3, 3, 0, 0, 1, 1 ], [ -2, 3, 0, 0, 0, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ], + [ [ [ -3, 3, 0, 1, 1, 0 ], [ -2, 3, 0, 0, 0, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -1, 3, 0, 0, 2, -1 ] ], 1 ], + [ [ [ -3, 3, 0, 1, 1, 0 ], [ -2, 3, 0, 0, 0, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -2, 3, 0, 0, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 0, 1, 0, 0 ], [ -2, 3, 0, 0, 0, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -2, 3, 0, 0, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, 0, 1, 0, 0 ], [ -2, 3, 0, 0, 0, 0 ], [ -2, 3, 0, 0, 1, 0 ], [ -1, 3, 0, 1, 0, -1 ] ], 1 ], + [ [ [ -2, 3, 0, 1, 0, 0 ], [ -2, 3, 0, 0, 0, 0 ], [ -1, 3, 0, 0, 0, 0 ], [ -1, 3, 0, 1, 0, -1 ] ], 1 ], + [ [ [ -2, 3, 0, 1, 0, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -1, 3, 0, 0, 0, 0 ], [ -1, 3, 0, 1, 0, -1 ] ], 1 ] + ], + [ + [ [ [ -1, 3, -1, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -1, 3, 0, 0, 0, 0 ], [ -1, 3, 0, 1, 0, -1 ] ], 1 ], + [ [ [ -1, 3, -1, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 1 ], [ -1, 3, 0, 1, 0, -1 ] ], 1 ] + ], + [ + [ [ [ -1, 3, -1, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 1 ], [ -2, 3, 0, 2, -1, 0 ] ], 1 ], + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 1 ], [ -2, 3, 0, 2, -1, 0 ] ], 1 ], + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -3, 4, 0, 1, -1, 0 ], [ -2, 3, 0, 2, -1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 4, 0, 1, -1, 0 ], [ -3, 4, 0, 1, -1, 0 ], [ -2, 3, 0, 2, -1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 4, 0, 1, -1, 0 ], [ -3, 4, 0, 1, -1, 0 ], [ 0, 3, -1, 0, -1, 0 ] ], 1 ], + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 4, 0, 1, -1, 0 ], [ -2, 3, -1, 1, 0, 0 ], [ 0, 3, -1, 0, -1, 0 ] ], 1 ], + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 4, 0, 1, -1, 0 ], [ "Rest" ], [ 0, 3, -1, 0, -1, 0 ] ], 1 ], + [ [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 4, 0, 1, -1, 0 ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ -2, 3, -1, 1, -1, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 1 ], [ -2, 3, 0, 2, -1, 0 ] ], + [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 3, 0, 1, -1, 0 ], [ -3, 4, 0, 1, -1, 0 ], [ -2, 3, 0, 2, -1, 0 ] ], + [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 4, 0, 1, -1, 0 ], [ -3, 4, 0, 1, -1, 0 ], [ -2, 3, 0, 2, -1, 0 ] ], + [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 4, 0, 1, -1, 0 ], [ -3, 4, 0, 1, -1, 0 ], [ 0, 3, -1, 0, -1, 0 ] ], + [ [ -2, 3, -1, 1, -1, 0 ], [ -2, 4, 0, 1, -1, 0 ], [ -2, 3, -1, 1, 0, 0 ], [ 0, 3, -1, 0, -1, 0 ] ] +], +"cur_uid": "57ef90e6", +"ref_uid": "nil", +"order_seed": 486941, +"dur_seed": 852979, +"motifs_seed": 409375, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0020576131687243, 0.068181818181818, 0.074074074074074, 0.0625, 0.20576131687243, 0.0625, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.78600823045268, 0.068181818181818, 0.98971193415638, 0.0625 ], +"passages_weights": [ 1, 1, 1, 1, 0.1 ], +"hd_exp": 1, +"hd_invert": 0, +"order": +[ + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 3 ], [ 2, 0, 1 ], [ ] ], + [ [ 1, 3 ], [ 2, 0 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 2 ], [ 3, 1, 0 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 2, 0 ], [ 3, 1 ], [ ] ], + [ [ 3 ], [ 2, 0, 1 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 2 ], [ 1, 3, 0 ], [ ] ], + [ [ 2, 0 ], [ 3, 1 ], [ ] ], + [ [ 3 ], [ 0, 2, 1 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 24.896551724138, 24.896551724138 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/6522664c/6522664c_code.scd b/resources/string_quartet_3_rise/6522664c/6522664c_code.scd new file mode 100644 index 0000000..42b64ff --- /dev/null +++ b/resources/string_quartet_3_rise/6522664c/6522664c_code.scd @@ -0,0 +1,981 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/6522664c/6522664c_mus_model.json b/resources/string_quartet_3_rise/6522664c/6522664c_mus_model.json new file mode 100644 index 0000000..e6c59fc --- /dev/null +++ b/resources/string_quartet_3_rise/6522664c/6522664c_mus_model.json @@ -0,0 +1,85 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -4, 2, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 2, 2, 2, 1, 0 ], [ -3, 1, 1, 3, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -3, 1, 1, 2, 1, 0 ], [ -3, 1, 1, 3, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -3, 1, 1, 2, 1, 0 ], [ -3, 1, 1, 3, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 1, 2, 1, 0 ], [ -3, 1, 1, 3, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 1, 2, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 1, 2, 1, 0 ], [ -3, 1, 1, 3, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -3, 2, 1, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 1, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 1, 2, 1, 0 ], [ -3, 1, 3, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -2, 0, 2, 2, 1, 0 ], [ -3, 1, 3, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -2, 0, 2, 2, 1, 0 ], [ -3, 1, 3, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 2, 2, 1, 0 ], [ -3, 1, 3, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -3, 2, 2, 2, 1, 0 ], [ -3, 1, 3, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -4, 3, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 2, 2, 1, 0 ], [ -5, 3, 1, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -4, 3, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 2, 2, 1, 0 ], [ -5, 3, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -4, 3, 2, 2, 1, 0 ] ], 1 ], + [ [ [ -3, 2, 2, 2, 1, 0 ], [ -5, 3, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 2, 1, 2, 1, 0 ], [ -5, 3, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 3, 1, 2, 1, 0 ], [ -5, 3, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -3, 2, 2, 2, 1, 0 ], [ -5, 3, 1, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -4, 3, 2, 2, 1, 0 ] ], + [ [ -3, 2, 2, 2, 1, 0 ], [ -5, 3, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -4, 3, 2, 2, 1, 0 ] ], + [ [ -3, 2, 2, 2, 1, 0 ], [ -5, 3, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], + [ [ -4, 2, 1, 2, 1, 0 ], [ -5, 3, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], + [ [ -4, 3, 1, 2, 1, 0 ], [ -5, 3, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ] +], +"cur_uid": "6522664c", +"ref_uid": "4bf1af12", +"order_seed": 347999, +"dur_seed": 441379, +"motifs_seed": 667646, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0061728395061728, 0.10227272727273, 0.074074074074074, 0.10227272727273, 0.2037037037037, 0.090909090909091, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.7880658436214, 0.034090909090909, 1, 0.034090909090909 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 0.8 ], +"hd_exp": 8, +"hd_invert": 0, +"order": +[ + [ [ 2, 0 ], [ 3, 1 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ], + [ [ 2, 0 ], [ 3, 1 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 3 ], [ 1, 0, 2 ], [ ] ], + [ [ 2, 1 ], [ 0, 3 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 2 ], [ 1, 3, 0 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 10.091836734694, 10.091836734694 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/6522664c/lilypond/part_I.ly b/resources/string_quartet_3_rise/6522664c/lilypond/part_I.ly new file mode 100644 index 0000000..aa8c464 --- /dev/null +++ b/resources/string_quartet_3_rise/6522664c/lilypond/part_I.ly @@ -0,0 +1,20 @@ +{ + { g'1^\markup { \pad-markup #0.2 "-21"} ~ } + \bar "|" + { g'1 } + \bar "|" + { fis1^\markup { \pad-markup #0.2 "+26"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} } + \bar "|" + { c'1^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} } + \bar "|" + { e'1^\markup { \pad-markup #0.2 "-36"} ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'2 fis'2^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { fis'1 } + \bar "|" + { g'1^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/6522664c/lilypond/part_II.ly b/resources/string_quartet_3_rise/6522664c/lilypond/part_II.ly new file mode 100644 index 0000000..9007135 --- /dev/null +++ b/resources/string_quartet_3_rise/6522664c/lilypond/part_II.ly @@ -0,0 +1,20 @@ +{ + { c''1^\markup { \pad-markup #0.2 "-23"} ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''2 b2^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/6522664c/lilypond/part_III.ly b/resources/string_quartet_3_rise/6522664c/lilypond/part_III.ly new file mode 100644 index 0000000..77f3a11 --- /dev/null +++ b/resources/string_quartet_3_rise/6522664c/lilypond/part_III.ly @@ -0,0 +1,20 @@ +{ + { g2^\markup { \pad-markup #0.2 "-21"} e'2^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "+46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }} ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'2 b'2^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { b'2 g'2^\markup { \pad-markup #0.2 "+50"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }} ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 } + \bar "|" + { d2^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }} fis2^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { fis1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/6522664c/lilypond/part_IV.ly b/resources/string_quartet_3_rise/6522664c/lilypond/part_IV.ly new file mode 100644 index 0000000..49e5ac8 --- /dev/null +++ b/resources/string_quartet_3_rise/6522664c/lilypond/part_IV.ly @@ -0,0 +1,20 @@ +{ + { b1^\markup { \pad-markup #0.2 "-34"} ~ } + \bar "|" + { b2 c'2^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c'2 g'2^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↑" }} ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "-38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} } + \bar "|" + { b'1^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'2 g2^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/6db2efcc/6db2efcc_code.scd b/resources/string_quartet_3_rise/6db2efcc/6db2efcc_code.scd new file mode 100644 index 0000000..42b64ff --- /dev/null +++ b/resources/string_quartet_3_rise/6db2efcc/6db2efcc_code.scd @@ -0,0 +1,981 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/6db2efcc/6db2efcc_mus_model.json b/resources/string_quartet_3_rise/6db2efcc/6db2efcc_mus_model.json new file mode 100644 index 0000000..d66bfdd --- /dev/null +++ b/resources/string_quartet_3_rise/6db2efcc/6db2efcc_mus_model.json @@ -0,0 +1,86 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -6, 2, 4, 3, 1, 0 ], [ -6, 3, 3, 4, 1, 0 ], [ -6, 2, 3, 4, 1, 0 ] ], 1 ], + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -6, 2, 3, 3, 2, 0 ], [ -6, 3, 3, 4, 1, 0 ], [ -6, 2, 3, 4, 1, 0 ] ], 1 ], + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -6, 2, 3, 3, 2, 0 ], [ -4, 1, 3, 3, 1, 0 ], [ -6, 2, 3, 4, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -6, 2, 3, 3, 2, 0 ], [ -4, 1, 3, 3, 1, 0 ], [ -4, 2, 3, 3, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 3, 3, 1, 0 ], [ -6, 2, 3, 3, 2, 0 ], [ -4, 1, 3, 3, 1, 0 ], [ -4, 2, 3, 2, 2, 0 ] ], 1 ], + [ [ [ -6, 2, 3, 4, 2, 0 ], [ -6, 2, 3, 3, 2, 0 ], [ -4, 1, 3, 3, 1, 0 ], [ -4, 2, 3, 2, 2, 0 ] ], 1 ], + [ [ [ -6, 2, 3, 4, 2, 0 ], [ -6, 2, 3, 3, 2, 0 ], [ -5, 2, 3, 3, 2, 0 ], [ -4, 2, 3, 2, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -6, 2, 3, 4, 2, 0 ], [ -6, 2, 3, 3, 2, 0 ], [ -4, 1, 3, 2, 2, 0 ], [ -4, 2, 3, 2, 2, 0 ] ], 1 ], + [ [ [ -4, 2, 2, 2, 2, 0 ], [ -6, 2, 3, 3, 2, 0 ], [ -4, 1, 3, 2, 2, 0 ], [ -4, 2, 3, 2, 2, 0 ] ], 1 ], + [ [ [ -4, 2, 2, 2, 2, 0 ], [ -5, 2, 3, 2, 2, 0 ], [ -4, 1, 3, 2, 2, 0 ], [ -4, 2, 3, 2, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 3, 2, 2, 1 ], [ -5, 2, 3, 2, 2, 0 ], [ -4, 1, 3, 2, 2, 0 ], [ -4, 2, 3, 2, 2, 0 ] ], 1 ], + [ [ [ -5, 2, 3, 2, 2, 1 ], [ -5, 2, 3, 2, 2, 0 ], [ -3, 2, 3, 1, 2, 0 ], [ -4, 2, 3, 2, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 3, 1, 1, 0 ], [ -5, 2, 3, 2, 2, 0 ], [ -3, 2, 3, 1, 2, 0 ], [ -4, 2, 3, 2, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 3, 1, 1, 0 ], [ -5, 2, 3, 2, 2, 0 ], [ -3, 2, 3, 2, 2, -1 ], [ -4, 2, 3, 2, 2, 0 ] ], 1 ], + [ [ [ -3, 2, 3, 1, 1, 0 ], [ -5, 2, 3, 2, 2, 0 ], [ -3, 2, 3, 2, 2, -1 ], [ -3, 2, 4, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -3, 2, 3, 1, 1, 0 ], [ -5, 2, 3, 2, 2, 0 ], [ -5, 2, 4, 2, 1, 0 ], [ -3, 2, 4, 1, 1, 0 ] ], 1 ], + [ [ [ -3, 2, 3, 1, 1, 0 ], [ -3, 2, 4, 0, 1, 0 ], [ -5, 2, 4, 2, 1, 0 ], [ -3, 2, 4, 1, 1, 0 ] ], 1 ], + [ [ [ -4, 2, 4, 2, 1, 0 ], [ -3, 2, 4, 0, 1, 0 ], [ -5, 2, 4, 2, 1, 0 ], [ -3, 2, 4, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 2, 4, 2, 1, 0 ], [ -3, 2, 4, 0, 1, 0 ], [ -4, 2, 3, 2, 1, 0 ], [ -3, 2, 4, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -4, 2, 4, 2, 1, 0 ], [ -3, 2, 3, 2, 1, -1 ], [ -4, 2, 3, 2, 1, 0 ], [ -3, 2, 4, 1, 1, 0 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -3, 2, 3, 1, 1, 0 ], [ -5, 2, 3, 2, 2, 0 ], [ -5, 2, 4, 2, 1, 0 ], [ -3, 2, 4, 1, 1, 0 ] ], + [ [ -3, 2, 3, 1, 1, 0 ], [ -3, 2, 4, 0, 1, 0 ], [ -5, 2, 4, 2, 1, 0 ], [ -3, 2, 4, 1, 1, 0 ] ], + [ [ -4, 2, 4, 2, 1, 0 ], [ -3, 2, 4, 0, 1, 0 ], [ -5, 2, 4, 2, 1, 0 ], [ -3, 2, 4, 1, 1, 0 ] ], + [ [ -4, 2, 4, 2, 1, 0 ], [ -3, 2, 4, 0, 1, 0 ], [ -4, 2, 3, 2, 1, 0 ], [ -3, 2, 4, 1, 1, 0 ] ], + [ [ -4, 2, 4, 2, 1, 0 ], [ -3, 2, 3, 2, 1, -1 ], [ -4, 2, 3, 2, 1, 0 ], [ -3, 2, 4, 1, 1, 0 ] ] +], +"cur_uid": "6db2efcc", +"ref_uid": "521654f8", +"order_seed": 213803, +"dur_seed": 264333, +"motifs_seed": 980711, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0061728395061728, 0.10227272727273, 0.074074074074074, 0.10227272727273, 0.2037037037037, 0.090909090909091, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.7880658436214, 0.034090909090909, 1, 0.034090909090909 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 0.5 ], +"hd_exp": 5, +"hd_invert": 0, +"order": +[ + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 1 ], [ 3, 0, 2 ], [ ] ], + [ [ 3 ], [ 2, 0, 1 ], [ ] ], + [ [ 3, 1 ], [ 0, 2 ], [ ] ], + [ [ 1, 2, 3 ], [ 0 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 0, 1, 3 ], [ 2 ], [ ] ], + [ [ 3, 2, 0 ], [ 1 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 10.091836734694, 10.091836734694 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/6db2efcc/lilypond/part_I.ly b/resources/string_quartet_3_rise/6db2efcc/lilypond/part_I.ly new file mode 100644 index 0000000..90f111c --- /dev/null +++ b/resources/string_quartet_3_rise/6db2efcc/lilypond/part_I.ly @@ -0,0 +1,22 @@ +{ + { ais1^\markup { \pad-markup #0.2 "-11"} ~ } + \bar "|" + { ais2 g'2^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} } + \bar "|" + { gis'1^\markup { \pad-markup #0.2 "+3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }} ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { a'1 ~ } + \bar "|" + { a'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/6db2efcc/lilypond/part_II.ly b/resources/string_quartet_3_rise/6db2efcc/lilypond/part_II.ly new file mode 100644 index 0000000..88a941a --- /dev/null +++ b/resources/string_quartet_3_rise/6db2efcc/lilypond/part_II.ly @@ -0,0 +1,22 @@ +{ + { f'1^\markup { \pad-markup #0.2 "-9"} } + \bar "|" + { f'1^\markup { \pad-markup #0.2 "+19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { f'1 } + \bar "|" + { fis'2^\markup { \pad-markup #0.2 "-28"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} cis'2^\markup { \pad-markup #0.2 "+1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'2 ais'2^\markup { \pad-markup #0.2 "+34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }} ~ } + \bar "|" + { ais'2 c''2^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↓" }} ~ } + \bar "|" + { c''2 fis2^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }} ~ } + \bar "|" + { fis1 } + \bar "|" + { dis'1^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/6db2efcc/lilypond/part_III.ly b/resources/string_quartet_3_rise/6db2efcc/lilypond/part_III.ly new file mode 100644 index 0000000..898501b --- /dev/null +++ b/resources/string_quartet_3_rise/6db2efcc/lilypond/part_III.ly @@ -0,0 +1,22 @@ +{ + { e2^\markup { \pad-markup #0.2 "+7"} fis2^\markup { \pad-markup #0.2 "-28"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↑" }} ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis1 ~ } + \bar "|" + { fis2 gis2^\markup { \pad-markup #0.2 "+3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { gis1 ~ } + \bar "|" + { gis1 ~ } + \bar "|" + { gis1 } + \bar "|" + { b1^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↓" }} ~ } + \bar "|" + { b2 fis'2^\markup { \pad-markup #0.2 "+11"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 13↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/6db2efcc/lilypond/part_IV.ly b/resources/string_quartet_3_rise/6db2efcc/lilypond/part_IV.ly new file mode 100644 index 0000000..6098652 --- /dev/null +++ b/resources/string_quartet_3_rise/6db2efcc/lilypond/part_IV.ly @@ -0,0 +1,22 @@ +{ + { c'1^\markup { \pad-markup #0.2 "+21"} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'2 dis'2^\markup { \pad-markup #0.2 "+41"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }} ~ } + \bar "|" + { dis'1 } + \bar "|" + { e'1^\markup { \pad-markup #0.2 "+17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }} } + \bar "|" + { e'1^\markup { \pad-markup #0.2 "+44"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 13↑" }} } + \bar "|" + { f'1^\markup { \pad-markup #0.2 "-17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↓" }} ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'2 fis'2^\markup { \pad-markup #0.2 "+38"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} ~ } + \bar "|" + { fis'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/7276dc78/7276dc78_code.scd b/resources/string_quartet_3_rise/7276dc78/7276dc78_code.scd new file mode 100644 index 0000000..42b64ff --- /dev/null +++ b/resources/string_quartet_3_rise/7276dc78/7276dc78_code.scd @@ -0,0 +1,981 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/7276dc78/7276dc78_mus_model.json b/resources/string_quartet_3_rise/7276dc78/7276dc78_mus_model.json new file mode 100644 index 0000000..6d0340b --- /dev/null +++ b/resources/string_quartet_3_rise/7276dc78/7276dc78_mus_model.json @@ -0,0 +1,158 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -7, 6, 4, -1, 1, 2 ], [ -5, 5, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 1, 2 ] ], 1 ], + [ [ [ -6, 6, 4, -1, 0, 2 ], [ -5, 5, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 1, 2 ] ], 1 ], + [ [ [ -6, 6, 4, -1, 0, 2 ], [ -5, 5, 4, 0, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -7, 6, 5, 0, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 4, -1, 0, 2 ], [ -5, 6, 3, 0, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -7, 6, 5, 0, 0, 2 ] ], 1 ], + [ [ [ -6, 6, 4, -1, 0, 2 ], [ -5, 6, 3, 0, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -7, 6, 4, 1, 0, 2 ] ], 1 ], + [ [ [ -6, 6, 3, 0, 0, 2 ], [ -5, 6, 3, 0, 0, 2 ], [ -6, 6, 4, 0, 0, 2 ], [ -7, 6, 4, 1, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 3, 0, 0, 2 ], [ -5, 6, 3, 0, 0, 2 ], [ -5, 6, 2, 0, 0, 2 ], [ -7, 6, 4, 1, 0, 2 ] ], 1 ], + [ [ [ -6, 6, 3, 0, 0, 2 ], [ -5, 6, 3, 0, 0, 2 ], [ -5, 6, 2, 0, 0, 2 ], [ -6, 7, 3, 0, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 3, 0, 0, 2 ], [ -5, 6, 2, -1, 0, 2 ], [ -5, 6, 2, 0, 0, 2 ], [ -6, 7, 3, 0, 0, 2 ] ], 1 ], + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -5, 6, 2, -1, 0, 2 ], [ -5, 6, 2, 0, 0, 2 ], [ -6, 7, 3, 0, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -5, 6, 2, -1, 0, 2 ], [ -6, 7, 2, 1, 0, 2 ], [ -6, 7, 3, 0, 0, 2 ] ], 1 ], + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -6, 7, 2, 1, 0, 2 ], [ -6, 7, 3, 0, 0, 2 ] ], 1 ], + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -6, 7, 2, 1, 0, 2 ], [ -5, 6, 2, 1, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -5, 6, 2, 1, -1, 1 ], [ -5, 6, 2, 1, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -6, 6, 2, 2, -1, 2 ], [ -5, 6, 2, 1, -1, 2 ] ], 1 ], + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -6, 6, 2, 2, -1, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 6, 2, 1, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -7, 5, 2, 3, 0, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ], + [ [ [ -8, 6, 2, 2, 0, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -7, 5, 2, 3, 0, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ], + [ [ [ -8, 6, 2, 2, 0, 2 ], [ -7, 5, 2, 2, -1, 2 ], [ -7, 5, 2, 3, 0, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 2, 2, -1, 2 ], [ -7, 5, 2, 2, -1, 2 ], [ -7, 5, 2, 3, 0, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 2, 2, -1, 2 ], [ -7, 5, 2, 2, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ], + [ [ [ -7, 6, 2, 2, -1, 2 ], [ -6, 6, 2, 2, -2, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 2, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -7, 5, 2, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 2, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 2, 2, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 2, 2, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 6, 2, 2, -1, 2 ] ], 1 ], + [ [ [ -6, 5, 1, 2, 0, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 6, 2, 2, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 5, 1, 2, 0, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 5, 0, 2, 0, 2 ], [ -6, 6, 2, 2, -1, 2 ] ], 1 ], + [ [ [ -6, 5, 1, 2, 0, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -5, 5, 0, 2, 0, 2 ], [ -6, 6, 1, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 5, 1, 2, 0, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -6, 6, 1, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 5, 1, 3, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -7, 6, 2, 2, 0, 2 ], [ -6, 6, 1, 2, 0, 2 ] ], 1 ], + [ [ [ -6, 5, 1, 3, -1, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 5, 1, 2, -2, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 5, 1, 2, -2, 2 ], [ -5, 5, 1, 2, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ], + [ [ [ -7, 5, 1, 2, -2, 2 ], [ -7, 5, 1, 3, -2, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ], + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -7, 5, 1, 3, -2, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 5, 1, 2, -1, 2 ], [ -7, 5, 1, 3, -2, 2 ], [ -5, 5, 1, 1, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ], + [ [ [ -6, 5, 1, 2, -2, 2 ], [ -7, 5, 1, 3, -2, 2 ], [ -5, 5, 1, 1, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ], + [ [ [ -6, 5, 1, 2, -2, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -5, 5, 1, 1, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -8, 5, 0, 3, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -5, 5, 1, 1, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ], + [ [ [ -8, 5, 0, 3, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 1, 2, -2, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 2, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 6, 1, 2, -2, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -7, 6, 1, 3, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -8, 4, 1, 3, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -7, 6, 1, 3, -1, 2 ] ], 1 ], + [ [ [ -8, 4, 1, 3, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 3, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -9, 5, 1, 4, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 3, -1, 2 ] ], 1 ] + ], + [ + [ [ [ -9, 5, 1, 4, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 4, -2, 2 ] ], 1 ], + [ [ [ -9, 5, 1, 4, -1, 2 ], [ -7, 5, 1, 4, -2, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 4, -2, 2 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -8, 4, 1, 3, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -7, 6, 1, 3, -1, 2 ] ], + [ [ -8, 4, 1, 3, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 3, -1, 2 ] ], + [ [ -9, 5, 1, 4, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 3, -1, 2 ] ], + [ [ -9, 5, 1, 4, -1, 2 ], [ -7, 5, 1, 3, -1, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 4, -2, 2 ] ], + [ [ -9, 5, 1, 4, -1, 2 ], [ -7, 5, 1, 4, -2, 2 ], [ -6, 6, 1, 2, -1, 2 ], [ -6, 5, 1, 4, -2, 2 ] ] +], +"cur_uid": "7276dc78", +"ref_uid": "531df78c", +"order_seed": 350501, +"dur_seed": 664373, +"motifs_seed": 429600, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0, 0, 0.069958847736626, 0, 0.31481481481481, 0, 0.38271604938272, 0.13068181818182, 0.45884773662551, 0.14772727272727, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.79218106995885, 0, 1, 0 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 0.45 ], +"hd_exp": 3.87, +"hd_invert": 0, +"order": +[ + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 2 ], [ 1, 3, 0 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 3 ], [ 2, 0, 1 ], [ ] ], + [ [ 1, 3, 2 ], [ 0 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 1, 0 ], [ 2, 3 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 2 ], [ 3, 1, 0 ], [ ] ], + [ [ 3 ], [ 2, 0, 1 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 1, 2 ], [ 0, 3 ], [ ] ], + [ [ 3, 2, 1 ], [ 0 ], [ ] ], + [ [ 2, 0 ], [ 3, 1 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 25.244897959184, 25.244897959184 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/7276dc78/lilypond/part_I.ly b/resources/string_quartet_3_rise/7276dc78/lilypond/part_I.ly new file mode 100644 index 0000000..af7f26d --- /dev/null +++ b/resources/string_quartet_3_rise/7276dc78/lilypond/part_I.ly @@ -0,0 +1,48 @@ +{ + { gis'1^\markup { \pad-markup #0.2 "-11"} } + \bar "|" + { fis1^\markup { \pad-markup #0.2 "+24"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↑" }} } + \bar "|" + { c'1^\markup { \pad-markup #0.2 "+7"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }} ~ } + \bar "|" + { c'2 fis'2^\markup { \pad-markup #0.2 "-46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { fis'1 ~ } + \bar "|" + { fis'1 } + \bar "|" + { b'1^\markup { \pad-markup #0.2 "-17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} ~ } + \bar "|" + { b'2 g2^\markup { \pad-markup #0.2 "+1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g2 cis'2^\markup { \pad-markup #0.2 "+50"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { a'2 ais'2^\markup { \pad-markup #0.2 "+17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 } + \bar "|" + { ais1^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais1 ~ } + \bar "|" + { ais2 d'2^\markup { \pad-markup #0.2 "+34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { d'2 g'2^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g'2 b'2^\markup { \pad-markup #0.2 "+50"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/7276dc78/lilypond/part_II.ly b/resources/string_quartet_3_rise/7276dc78/lilypond/part_II.ly new file mode 100644 index 0000000..b3e65e4 --- /dev/null +++ b/resources/string_quartet_3_rise/7276dc78/lilypond/part_II.ly @@ -0,0 +1,48 @@ +{ + { d'1^\markup { \pad-markup #0.2 "+38"} ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 } + \bar "|" + { g'1^\markup { \pad-markup #0.2 "-35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { g'1 } + \bar "|" + { b'1^\markup { \pad-markup #0.2 "+36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { b'2 d'2^\markup { \pad-markup #0.2 "+42"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 13↓" }} } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }} } + \bar "|" + { f'1^\markup { \pad-markup #0.2 "-30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }} ~ } + \bar "|" + { f'1 } + \bar "|" + { f'1^\markup { \pad-markup #0.2 "-35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 } + \bar "|" + { b'1^\markup { \pad-markup #0.2 "+28"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} } + \bar "|" + { d'1^\markup { \pad-markup #0.2 "+3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↑" }} } + \bar "|" + { g1^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g2 c'2^\markup { \pad-markup #0.2 "-5"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↓" }} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'2 f'2^\markup { \pad-markup #0.2 "-35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↑" }} ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/7276dc78/lilypond/part_III.ly b/resources/string_quartet_3_rise/7276dc78/lilypond/part_III.ly new file mode 100644 index 0000000..192a5db --- /dev/null +++ b/resources/string_quartet_3_rise/7276dc78/lilypond/part_III.ly @@ -0,0 +1,48 @@ +{ + { g'1^\markup { \pad-markup #0.2 "+36"} ~ } + \bar "|" + { g'2 b'2^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }} ~ } + \bar "|" + { b'1 ~ } + \bar "|" + { b'1 } + \bar "|" + { a1^\markup { \pad-markup #0.2 "-3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }} ~ } + \bar "|" + { a2 d'2^\markup { \pad-markup #0.2 "+3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 ~ } + \bar "|" + { d'1 } + \bar "|" + { cis1^\markup { \pad-markup #0.2 "+50"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 11↓" }} ~ } + \bar "|" + { cis2 dis'2^\markup { \pad-markup #0.2 "+0"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 11↓" }} } + \bar "|" + { ais'1^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }} ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'1 ~ } + \bar "|" + { ais'2 d2^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d2 g2^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }} ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1 ~ } + \bar "|" + { g1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/7276dc78/lilypond/part_IV.ly b/resources/string_quartet_3_rise/7276dc78/lilypond/part_IV.ly new file mode 100644 index 0000000..5fd805f --- /dev/null +++ b/resources/string_quartet_3_rise/7276dc78/lilypond/part_IV.ly @@ -0,0 +1,48 @@ +{ + { ais,2^\markup { \pad-markup #0.2 "+21"} f2^\markup { \pad-markup #0.2 "-31"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }} ~ } + \bar "|" + { f1 ~ } + \bar "|" + { f2 b2^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2 e'2^\markup { \pad-markup #0.2 "+34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↑" }} ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'1 ~ } + \bar "|" + { e'2 d2^\markup { \pad-markup #0.2 "+3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { d2 a2^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a1 ~ } + \bar "|" + { a2 dis'2^\markup { \pad-markup #0.2 "+15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↑" }} ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'2 g'2^\markup { \pad-markup #0.2 "+32"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }} ~ } + \bar "|" + { g'2 e,2^\markup { \pad-markup #0.2 "+12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↓" }} ~ } + \bar "|" + { e,1 } + \bar "|" + { ais,1^\markup { \pad-markup #0.2 "-37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} } + \bar "|" + { e1^\markup { \pad-markup #0.2 "+12"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }} } + \bar "|" + { dis,1^\markup { \pad-markup #0.2 "+46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↓" }} } + \bar "|" + { b,1^\markup { \pad-markup #0.2 "+14"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↓" }} } + \bar "|" + { c,1^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} } + \bar "|" + { f,1^\markup { \pad-markup #0.2 "+1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/78a94ed1/78a94ed1_code.scd b/resources/string_quartet_3_rise/78a94ed1/78a94ed1_code.scd new file mode 100644 index 0000000..42b64ff --- /dev/null +++ b/resources/string_quartet_3_rise/78a94ed1/78a94ed1_code.scd @@ -0,0 +1,981 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/78a94ed1/78a94ed1_mus_model.json b/resources/string_quartet_3_rise/78a94ed1/78a94ed1_mus_model.json new file mode 100644 index 0000000..078670e --- /dev/null +++ b/resources/string_quartet_3_rise/78a94ed1/78a94ed1_mus_model.json @@ -0,0 +1,90 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -6, 1, 5, 1, 0, 2 ], [ -5, 1, 4, 2, 0, 2 ], [ -5, 1, 5, 2, 0, 2 ], [ -4, 2, 5, 1, 0, 2 ] ], 1 ], + [ [ [ -6, 1, 5, 1, 0, 2 ], [ -5, 1, 4, 2, 0, 2 ], [ -3, 0, 5, 1, 0, 2 ], [ -4, 2, 5, 1, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 1, 5, 1, 0, 2 ], [ -5, 1, 4, 2, 0, 2 ], [ -3, 0, 5, 1, 0, 2 ], [ -4, 1, 5, 1, 0, 2 ] ], 1 ], + [ [ [ -6, 1, 5, 1, 0, 2 ], [ -5, 1, 4, 2, 0, 2 ], [ -4, 2, 5, 1, 0, 2 ], [ -4, 1, 5, 1, 0, 2 ] ], 1 ], + [ [ [ -6, 1, 5, 1, 0, 2 ], [ -5, 2, 5, 1, 0, 2 ], [ -4, 2, 5, 1, 0, 2 ], [ -4, 1, 5, 1, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 1, 5, 1, 0, 2 ], [ -5, 2, 5, 1, 0, 2 ], [ -5, 1, 6, 1, 0, 2 ], [ -4, 1, 5, 1, 0, 2 ] ], 1 ], + [ [ [ -6, 1, 5, 1, 0, 2 ], [ -4, 1, 4, 1, 0, 2 ], [ -5, 1, 6, 1, 0, 2 ], [ -4, 1, 5, 1, 0, 2 ] ], 1 ], + [ [ [ -6, 1, 5, 1, 0, 2 ], [ -4, 1, 4, 1, 0, 2 ], [ -5, 1, 6, 1, 0, 2 ], [ -4, 2, 5, 1, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 1, 5, 1, 0, 2 ], [ -4, 1, 4, 1, 0, 2 ], [ -5, 2, 5, 1, 0, 2 ], [ -4, 2, 5, 1, 0, 2 ] ], 1 ], + [ [ [ -6, 1, 5, 1, 0, 2 ], [ -4, 2, 4, 1, 0, 2 ], [ -5, 2, 5, 1, 0, 2 ], [ -4, 2, 5, 1, 0, 2 ] ], 1 ], + [ [ [ -7, 2, 5, 1, 1, 2 ], [ -4, 2, 4, 1, 0, 2 ], [ -5, 2, 5, 1, 0, 2 ], [ -4, 2, 5, 1, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 2, 5, 1, 1, 2 ], [ -4, 2, 4, 1, 0, 2 ], [ -5, 2, 5, 1, 0, 2 ], [ -5, 1, 5, 1, 1, 2 ] ], 1 ], + [ [ [ -7, 2, 5, 1, 1, 2 ], [ -4, 2, 4, 1, 0, 2 ], [ -4, 1, 4, 1, 0, 2 ], [ -5, 1, 5, 1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -7, 2, 5, 1, 1, 2 ], [ -4, 2, 4, 1, 0, 2 ], [ -4, 1, 4, 1, 0, 2 ], [ -3, 1, 4, 0, 0, 2 ] ], 1 ], + [ [ [ -6, 2, 5, 1, 0, 2 ], [ -4, 2, 4, 1, 0, 2 ], [ -4, 1, 4, 1, 0, 2 ], [ -3, 1, 4, 0, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 2, 4, 1, 1, 2 ], [ -4, 2, 4, 1, 0, 2 ], [ -4, 1, 4, 1, 0, 2 ], [ -3, 1, 4, 0, 0, 2 ] ], 1 ], + [ [ [ -6, 2, 4, 1, 1, 2 ], [ -4, 2, 4, 1, 0, 2 ], [ -4, 1, 4, 1, 0, 2 ], [ -3, 2, 4, 0, 0, 2 ] ], 1 ], + [ [ [ -6, 2, 4, 1, 1, 2 ], [ -4, 2, 4, 1, 0, 2 ], [ -5, 3, 4, 1, 0, 2 ], [ -3, 2, 4, 0, 0, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 2, 4, 1, 1, 2 ], [ -4, 2, 4, 1, 0, 2 ], [ -5, 3, 4, 1, 0, 2 ], [ -5, 2, 4, 2, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 2, 4, 1, 1, 2 ], [ -4, 2, 4, 1, 0, 2 ], [ -5, 3, 4, 1, 0, 2 ], [ -6, 2, 4, 2, 1, 2 ] ], 1 ], + [ [ [ -6, 2, 4, 1, 1, 2 ], [ -5, 3, 4, 1, 1, 2 ], [ -5, 3, 4, 1, 0, 2 ], [ -6, 2, 4, 2, 1, 2 ] ], 1 ], + [ [ [ -6, 2, 4, 1, 1, 2 ], [ -5, 3, 4, 1, 1, 2 ], [ -5, 2, 4, 1, 1, 3 ], [ -6, 2, 4, 2, 1, 2 ] ], 1 ] + ], + [ + [ [ [ -6, 2, 4, 1, 1, 2 ], [ -5, 3, 4, 1, 1, 2 ], [ -5, 2, 4, 2, 1, 2 ], [ -6, 2, 4, 2, 1, 2 ] ], 1 ], + [ [ [ -6, 2, 4, 1, 1, 2 ], [ -5, 3, 4, 1, 1, 2 ], [ -5, 2, 4, 2, 1, 2 ], [ -6, 3, 4, 2, 1, 2 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -6, 2, 4, 1, 1, 2 ], [ -4, 2, 4, 1, 0, 2 ], [ -5, 3, 4, 1, 0, 2 ], [ -6, 2, 4, 2, 1, 2 ] ], + [ [ -6, 2, 4, 1, 1, 2 ], [ -5, 3, 4, 1, 1, 2 ], [ -5, 3, 4, 1, 0, 2 ], [ -6, 2, 4, 2, 1, 2 ] ], + [ [ -6, 2, 4, 1, 1, 2 ], [ -5, 3, 4, 1, 1, 2 ], [ -5, 2, 4, 1, 1, 3 ], [ -6, 2, 4, 2, 1, 2 ] ], + [ [ -6, 2, 4, 1, 1, 2 ], [ -5, 3, 4, 1, 1, 2 ], [ -5, 2, 4, 2, 1, 2 ], [ -6, 2, 4, 2, 1, 2 ] ], + [ [ -6, 2, 4, 1, 1, 2 ], [ -5, 3, 4, 1, 1, 2 ], [ -5, 2, 4, 2, 1, 2 ], [ -6, 3, 4, 2, 1, 2 ] ] +], +"cur_uid": "78a94ed1", +"ref_uid": "4e9f1dcc", +"order_seed": 418736, +"dur_seed": 167372, +"motifs_seed": 741164, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0061728395061728, 0.10227272727273, 0.074074074074074, 0.10227272727273, 0.2037037037037, 0.090909090909091, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.7880658436214, 0.034090909090909, 1, 0.034090909090909 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 0.2 ], +"hd_exp": 2, +"hd_invert": 0, +"order": +[ + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 1, 2 ], [ 3, 0 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 0 ], [ 3, 1, 2 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 10.091836734694, 10.091836734694 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/78a94ed1/lilypond/part_I.ly b/resources/string_quartet_3_rise/78a94ed1/lilypond/part_I.ly new file mode 100644 index 0000000..cf060ff --- /dev/null +++ b/resources/string_quartet_3_rise/78a94ed1/lilypond/part_I.ly @@ -0,0 +1,26 @@ +{ + { c''1^\markup { \pad-markup #0.2 "-15"} } + \bar "|" + { f'1^\markup { \pad-markup #0.2 "-17"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { f'1 ~ } + \bar "|" + { f'2 c''2^\markup { \pad-markup #0.2 "-15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { c''1 ~ } + \bar "|" + { c''2 ais2^\markup { \pad-markup #0.2 "+35"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { ais2 dis'2^\markup { \pad-markup #0.2 "+28"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 7↓" }} ~ } + \bar "|" + { dis'1 } + \bar "|" + { ais'1^\markup { \pad-markup #0.2 "+30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }} } + \bar "|" + { b'2^\markup { \pad-markup #0.2 "+19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} b2^\markup { \pad-markup #0.2 "+19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 7↑" }} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b2 fis'2^\markup { \pad-markup #0.2 "+21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/78a94ed1/lilypond/part_II.ly b/resources/string_quartet_3_rise/78a94ed1/lilypond/part_II.ly new file mode 100644 index 0000000..9712ce1 --- /dev/null +++ b/resources/string_quartet_3_rise/78a94ed1/lilypond/part_II.ly @@ -0,0 +1,26 @@ +{ + { dis'2^\markup { \pad-markup #0.2 "-48"} ais'2^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↓" }} ~ } + \bar "|" + { ais'2 c''2^\markup { \pad-markup #0.2 "-15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { c''2 a2^\markup { \pad-markup #0.2 "-30"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} ~ } + \bar "|" + { a1 } + \bar "|" + { c'1^\markup { \pad-markup #0.2 "-15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c'1 } + \bar "|" + { cis'1^\markup { \pad-markup #0.2 "-3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { cis'1 ~ } + \bar "|" + { cis'2 dis'2^\markup { \pad-markup #0.2 "+1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↑" }} ~ } + \bar "|" + { dis'1 ~ } + \bar "|" + { dis'2 ais'2^\markup { \pad-markup #0.2 "-9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 13↑" }} } + \bar "|" + { b'1^\markup { \pad-markup #0.2 "+19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/78a94ed1/lilypond/part_III.ly b/resources/string_quartet_3_rise/78a94ed1/lilypond/part_III.ly new file mode 100644 index 0000000..4950731 --- /dev/null +++ b/resources/string_quartet_3_rise/78a94ed1/lilypond/part_III.ly @@ -0,0 +1,26 @@ +{ + { b1^\markup { \pad-markup #0.2 "-34"} ~ } + \bar "|" + { b1 } + \bar "|" + { c'1^\markup { \pad-markup #0.2 "-15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} } + \bar "|" + { cis'1^\markup { \pad-markup #0.2 "-3"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↓" }} ~ } + \bar "|" + { cis'2 gis'2^\markup { \pad-markup #0.2 "-1"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 5↓" }} ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 ~ } + \bar "|" + { gis'1 } + \bar "|" + { a'1^\markup { \pad-markup #0.2 "-48"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { a'1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/78a94ed1/lilypond/part_IV.ly b/resources/string_quartet_3_rise/78a94ed1/lilypond/part_IV.ly new file mode 100644 index 0000000..f5a6564 --- /dev/null +++ b/resources/string_quartet_3_rise/78a94ed1/lilypond/part_IV.ly @@ -0,0 +1,26 @@ +{ + { f,1^\markup { \pad-markup #0.2 "-17"} ~ } + \bar "|" + { f,1 ~ } + \bar "|" + { f,1 ~ } + \bar "|" + { f,1 ~ } + \bar "|" + { f,1 } + \bar "|" + { f,1^\markup { \pad-markup #0.2 "+37"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 11↑" }} ~ } + \bar "|" + { f,1 } + \bar "|" + { c2^\markup { \pad-markup #0.2 "-15"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 5↑" }} d2^\markup { \pad-markup #0.2 "-50"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 11↑" }} ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/7c30c182/7c30c182_code.scd b/resources/string_quartet_3_rise/7c30c182/7c30c182_code.scd new file mode 100644 index 0000000..42b64ff --- /dev/null +++ b/resources/string_quartet_3_rise/7c30c182/7c30c182_code.scd @@ -0,0 +1,981 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/7c30c182/7c30c182_mus_model.json b/resources/string_quartet_3_rise/7c30c182/7c30c182_mus_model.json new file mode 100644 index 0000000..f796261 --- /dev/null +++ b/resources/string_quartet_3_rise/7c30c182/7c30c182_mus_model.json @@ -0,0 +1,271 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 1 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ -1, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, -1, -1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, -1, -1, 0, 0, 0 ], [ 1, -1, -1, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, -1, 0, -1, 0 ], [ 1, -1, -1, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, -1, 0, -1, 0 ], [ 1, -1, -1, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 0, 0, -1, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -1, 0, -1, 0 ], [ 1, -1, -1, 0, 0, 0 ], [ 2, 0, -1, 0, -2, 0 ], [ 0, 0, -1, 1, 0, 0 ] ], 1 ], + [ [ [ 0, 0, -1, 0, -1, 0 ], [ 1, 0, -1, 0, -1, 0 ], [ 2, 0, -1, 0, -2, 0 ], [ 0, 0, -1, 1, 0, 0 ] ], 1 ], + [ [ [ 0, 0, -1, 0, -1, 0 ], [ 1, 0, -1, 0, -1, 0 ], [ 2, 0, -1, 0, -2, 0 ], [ 2, -1, -1, 0, -1, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -1, 0, -1, 0 ], [ 1, 0, -1, 0, -1, 0 ], [ 2, 0, -2, 0, -1, 0 ], [ 2, -1, -1, 0, -1, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -2, 0, 0, 0 ], [ 1, 0, -1, 0, -1, 0 ], [ 2, 0, -2, 0, -1, 0 ], [ 2, -1, -1, 0, -1, 0 ] ], 1 ], + [ [ [ 0, 0, -2, 0, 0, 0 ], [ 2, -1, -2, 0, -1, 0 ], [ 2, 0, -2, 0, -1, 0 ], [ 2, -1, -1, 0, -1, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -2, 0, 0, 0 ], [ 2, -1, -2, 0, -1, 0 ], [ 3, -1, -3, 0, -1, 0 ], [ 2, -1, -1, 0, -1, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -2, 0, 0, 0 ], [ 1, 0, -2, 0, 1, 0 ], [ 3, -1, -3, 0, -1, 0 ], [ 2, -1, -1, 0, -1, 0 ] ], 1 ], + [ [ [ 0, 0, -2, 0, 0, 0 ], [ 1, 0, -2, 0, 1, 0 ], [ 3, -1, -3, 0, -1, 0 ], [ 2, -1, -2, 0, 0, 0 ] ], 1 ], + [ [ [ 0, 0, -2, 0, 0, 0 ], [ 1, 0, -2, 0, 1, 0 ], [ 0, 0, -2, 1, 0, 0 ], [ 2, -1, -2, 0, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -2, 0, 0, 0 ], [ 1, 0, -2, 0, 1, 0 ], [ 0, 0, -2, 1, 0, 0 ], [ 1, 0, -3, 1, 0, 0 ] ], 1 ], + [ [ [ 0, 0, -3, 0, 1, 0 ], [ 1, 0, -2, 0, 1, 0 ], [ 0, 0, -2, 1, 0, 0 ], [ 1, 0, -3, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -3, 0, 1, 0 ], [ 2, -1, -3, 0, 1, 0 ], [ 0, 0, -2, 1, 0, 0 ], [ 1, 0, -3, 1, 0, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -3, 0, 1, 0 ], [ 2, -1, -3, 0, 1, 0 ], [ 0, 0, -3, 1, 1, 0 ], [ 1, 0, -3, 1, 0, 0 ] ], 1 ], + [ [ [ 0, 0, -3, 0, 1, 0 ], [ 2, -1, -3, 0, 1, 0 ], [ 0, 0, -3, 1, 1, 0 ], [ 1, 0, -3, 0, 2, 0 ] ], 1 ], + [ [ [ 0, 0, -3, 0, 1, 0 ], [ 0, 0, -2, 0, 1, 0 ], [ 0, 0, -3, 1, 1, 0 ], [ 1, 0, -3, 0, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -3, 0, 1, 0 ], [ 0, 0, -2, 0, 1, 0 ], [ 0, 0, -3, 1, 1, 0 ], [ 1, 0, -3, 0, 1, 0 ] ], 1 ], + [ [ [ 0, 0, -3, 0, 1, 0 ], [ 0, 0, -2, 0, 1, 0 ], [ 1, 0, -3, 0, 2, 0 ], [ 1, 0, -3, 0, 1, 0 ] ], 1 ], + [ [ [ 0, 0, -3, 0, 1, 0 ], [ 1, -1, -3, 0, 1, 0 ], [ 1, 0, -3, 0, 2, 0 ], [ 1, 0, -3, 0, 1, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -3, 0, 1, 0 ], [ 1, -1, -3, 0, 1, 0 ], [ 2, -1, -3, 0, 1, 0 ], [ 1, 0, -3, 0, 1, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -3, 0, 1, 0 ], [ 1, -1, -3, 0, 1, 0 ], [ 2, -1, -3, 0, 1, 0 ], [ 2, -1, -4, 0, 1, 0 ] ], 1 ], + [ [ [ 0, 0, -3, 0, 1, 0 ], [ 1, -1, -3, 0, 1, 0 ], [ 1, -1, -3, 1, 1, 0 ], [ 2, -1, -4, 0, 1, 0 ] ], 1 ] + ], + [ + [ [ [ 0, 0, -3, 0, 1, 0 ], [ 2, -2, -4, 0, 1, 0 ], [ 1, -1, -3, 1, 1, 0 ], [ 2, -1, -4, 0, 1, 0 ] ], 1 ] + ], + [ + [ [ [ 1, -1, -4, 0, 1, 0 ], [ 2, -2, -4, 0, 1, 0 ], [ 1, -1, -3, 1, 1, 0 ], [ 2, -1, -4, 0, 1, 0 ] ], 1 ], + [ [ [ 1, -1, -4, 0, 1, 0 ], [ 1, -2, -3, 1, 1, 0 ], [ 1, -1, -3, 1, 1, 0 ], [ 2, -1, -4, 0, 1, 0 ] ], 1 ] + ], + [ + [ [ [ 1, -1, -4, 0, 1, 0 ], [ 2, -1, -5, 0, 1, 0 ], [ 1, -1, -3, 1, 1, 0 ], [ 2, -1, -4, 0, 1, 0 ] ], 1 ] + ], + [ + [ [ [ 1, -1, -4, 0, 1, 0 ], [ 2, -1, -5, 0, 1, 0 ], [ 1, -1, -3, 1, 1, 0 ], [ 2, -1, -5, 0, 2, 0 ] ], 1 ], + [ [ [ 1, -1, -4, 0, 1, 0 ], [ 2, -1, -5, 0, 1, 0 ], [ 3, -1, -5, 0, 0, 0 ], [ 2, -1, -5, 0, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 2, -2, -5, 0, 1, 0 ], [ 2, -1, -5, 0, 1, 0 ], [ 3, -1, -5, 0, 0, 0 ], [ 2, -1, -5, 0, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 2, -2, -5, 0, 1, 0 ], [ 2, -1, -5, 0, 1, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 2, -1, -5, 0, 2, 0 ] ], 1 ], + [ [ [ 2, -2, -5, 0, 1, 0 ], [ 2, -1, -6, 0, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 2, -1, -5, 0, 2, 0 ] ], 1 ], + [ [ [ -1, -1, -5, 0, 3, 0 ], [ 2, -1, -6, 0, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 2, -1, -5, 0, 2, 0 ] ], 1 ] + ], + [ + [ [ [ -1, -1, -5, 0, 3, 0 ], [ 2, -1, -6, 0, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 1, 0, -5, -1, 2, 0 ] ], 1 ], + [ [ [ 1, -2, -5, -1, 2, 0 ], [ 2, -1, -6, 0, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 1, 0, -5, -1, 2, 0 ] ], 1 ], + [ [ [ 1, -2, -5, -1, 2, 0 ], [ 2, 0, -5, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 1, 0, -5, -1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 1, -1, -5, -1, 1, 0 ], [ 2, 0, -5, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 1, 0, -5, -1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 1, -1, -5, -1, 1, 0 ], [ 2, 0, -5, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 1, -1, -5, -1, 2, 1 ] ], 1 ] + ], + [ + [ [ [ 1, -1, -5, -1, 1, 0 ], [ 3, -1, -6, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 1, -1, -5, -1, 2, 1 ] ], 1 ], + [ [ [ 1, -1, -6, -1, 2, 0 ], [ 3, -1, -6, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 1, -1, -5, -1, 2, 1 ] ], 1 ] + ], + [ + [ [ [ 0, -1, -5, 0, 2, 0 ], [ 3, -1, -6, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 1, -1, -5, -1, 2, 1 ] ], 1 ], + [ [ [ 0, -1, -5, 0, 2, 0 ], [ 2, -1, -5, 0, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 1, -1, -5, -1, 2, 1 ] ], 1 ] + ], + [ + [ [ [ 0, -1, -5, 0, 2, 0 ], [ 2, -1, -5, 0, 2, 0 ], [ 2, -2, -5, -1, 2, 1 ], [ 1, -1, -5, -1, 2, 1 ] ], 1 ], + [ [ [ 0, -1, -5, 0, 2, 0 ], [ 3, -1, -5, -2, 2, 1 ], [ 2, -2, -5, -1, 2, 1 ], [ 1, -1, -5, -1, 2, 1 ] ], 1 ] + ], + [ + [ [ [ 0, -1, -5, 0, 2, 0 ], [ 3, -1, -5, -2, 2, 1 ], [ 2, -1, -5, -1, 1, 1 ], [ 1, -1, -5, -1, 2, 1 ] ], 1 ], + [ [ [ 0, -1, -5, 0, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 2, -1, -5, -1, 1, 1 ], [ 1, -1, -5, -1, 2, 1 ] ], 1 ] + ], + [ + [ [ [ 0, -1, -5, 0, 2, 0 ], [ 2, -1, -5, 0, 1, 1 ], [ 2, -1, -5, -1, 1, 1 ], [ 1, -1, -5, -1, 2, 1 ] ], 1 ], + [ [ [ 0, -1, -5, 0, 2, 0 ], [ 2, -1, -5, 0, 1, 1 ], [ 2, -1, -5, -1, 1, 1 ], [ 2, -1, -5, -1, 0, 1 ] ], 1 ] + ], + [ + [ [ [ 0, -1, -5, 0, 2, 0 ], [ 3, -1, -6, -1, 1, 1 ], [ 2, -1, -5, -1, 1, 1 ], [ 2, -1, -5, -1, 0, 1 ] ], 1 ] + ], + [ + [ [ [ 0, -1, -5, 0, 2, 0 ], [ 3, -1, -6, -1, 1, 1 ], [ 2, -1, -5, -1, 1, 1 ], [ 2, -1, -6, -1, 1, 1 ] ], 1 ] + ], + [ + [ [ [ 0, -1, -5, 0, 2, 0 ], [ 3, -1, -6, -1, 1, 1 ], [ 3, -2, -6, -1, 1, 1 ], [ 2, -1, -6, -1, 1, 1 ] ], 1 ], + [ [ [ 0, -1, -5, 0, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 3, -2, -6, -1, 1, 1 ], [ 2, -1, -6, -1, 1, 1 ] ], 1 ] + ], + [ + [ [ [ 0, -1, -5, 0, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 3, -2, -6, -1, 1, 1 ], [ 2, -2, -6, -1, 1, 2 ] ], 1 ] + ], + [ + [ [ [ 1, 0, -5, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 3, -2, -6, -1, 1, 1 ], [ 2, -2, -6, -1, 1, 2 ] ], 1 ], + [ [ [ 1, 0, -5, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 3, -2, -6, -1, 1, 1 ], [ 3, -2, -5, -1, 2, 0 ] ], 1 ], + [ [ [ 1, 0, -5, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 2, -1, -5, -1, 3, 0 ], [ 3, -2, -5, -1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 1, 0, -5, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 2, 0, -5, -1, 2, 0 ], [ 3, -2, -5, -1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 2, -1, -6, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 2, 0, -5, -1, 2, 0 ], [ 3, -2, -5, -1, 2, 0 ] ], 1 ], + [ [ [ 2, -1, -6, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 2, 0, -5, -1, 2, 0 ], [ 3, -1, -5, -1, 1, 0 ] ], 1 ], + [ [ [ 2, -1, -6, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 3, -1, -6, -1, 2, 0 ], [ 3, -1, -5, -1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ 2, -1, -6, -1, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 2, -1, -5, 0, 2, 0 ], [ 3, -1, -5, -1, 1, 0 ] ], 1 ], + [ [ [ 1, -1, -5, 0, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 2, -1, -5, 0, 2, 0 ], [ 3, -1, -5, -1, 1, 0 ] ], 1 ], + [ [ [ 1, -1, -5, 0, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 2, -1, -5, 0, 2, 0 ], [ 3, -1, -6, -1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 3, -1, -6, -2, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 2, -1, -5, 0, 2, 0 ], [ 3, -1, -6, -1, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 3, -1, -6, -2, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 2, -1, -5, 0, 2, 0 ], [ 4, -1, -6, -2, 2, -1 ] ], 1 ], + [ [ [ 3, -1, -6, -2, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 4, -1, -6, -2, 2, -1 ] ], 1 ] + ], + [ + [ [ [ 3, -1, -6, -2, 2, 0 ], [ 3, -1, -5, -1, 2, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 4, -2, -6, -2, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 3, -1, -6, -2, 2, 0 ], [ 5, -1, -6, -3, 2, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 4, -2, -6, -2, 2, 0 ] ], 1 ], + [ [ [ 3, -1, -6, -2, 2, 0 ], [ 5, -1, -6, -3, 2, 0 ], [ 5, -2, -7, -2, 2, 0 ], [ 4, -2, -6, -2, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 4, -2, -7, -2, 2, 0 ], [ 5, -1, -6, -3, 2, 0 ], [ 5, -2, -7, -2, 2, 0 ], [ 4, -2, -6, -2, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 4, -2, -7, -2, 2, 0 ], [ 5, -1, -6, -3, 2, 0 ], [ 5, -2, -7, -2, 2, 0 ], [ 5, -3, -7, -2, 2, 0 ] ], 1 ], + [ [ [ 4, -1, -6, -3, 2, 0 ], [ 5, -1, -6, -3, 2, 0 ], [ 5, -2, -7, -2, 2, 0 ], [ 5, -3, -7, -2, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 4, -3, -7, -1, 2, 0 ], [ 5, -1, -6, -3, 2, 0 ], [ 5, -2, -7, -2, 2, 0 ], [ 5, -3, -7, -2, 2, 0 ] ], 1 ], + [ [ [ 4, -3, -7, -1, 2, 0 ], [ 5, -1, -6, -3, 2, 0 ], [ 5, -3, -7, -2, 2, 1 ], [ 5, -3, -7, -2, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 4, -3, -7, -1, 2, 0 ], [ 5, -1, -6, -3, 2, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 5, -3, -7, -2, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 4, -2, -6, -2, 2, 0 ], [ 5, -1, -6, -3, 2, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 5, -3, -7, -2, 2, 0 ] ], 1 ], + [ [ [ 4, -2, -6, -2, 2, 0 ], [ 5, -1, -6, -3, 2, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 3, 0, -6, -2, 2, 0 ] ], 1 ], + [ [ [ 4, -2, -6, -2, 2, 0 ], [ 3, -2, -6, -2, 2, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 3, 0, -6, -2, 2, 0 ] ], 1 ] + ], + [ + [ [ [ 4, -2, -6, -2, 2, 0 ], [ 3, -2, -6, -2, 2, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 3, -1, -6, -2, 2, 1 ] ], 1 ], + [ [ [ 4, -2, -6, -2, 2, 0 ], [ 3, -1, -6, -2, 1, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 3, -1, -6, -2, 2, 1 ] ], 1 ], + [ [ [ 4, -1, -6, -2, 1, 0 ], [ 3, -1, -6, -2, 1, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 3, -1, -6, -2, 2, 1 ] ], 1 ] + ], + [ + [ [ [ 4, -1, -7, -2, 2, 0 ], [ 3, -1, -6, -2, 1, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 3, -1, -6, -2, 2, 1 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ 4, -2, -6, -2, 2, 0 ], [ 3, -2, -6, -2, 2, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 3, 0, -6, -2, 2, 0 ] ], + [ [ 4, -2, -6, -2, 2, 0 ], [ 3, -2, -6, -2, 2, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 3, -1, -6, -2, 2, 1 ] ], + [ [ 4, -2, -6, -2, 2, 0 ], [ 3, -1, -6, -2, 1, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 3, -1, -6, -2, 2, 1 ] ], + [ [ 4, -1, -6, -2, 1, 0 ], [ 3, -1, -6, -2, 1, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 3, -1, -6, -2, 2, 1 ] ], + [ [ 4, -1, -7, -2, 2, 0 ], [ 3, -1, -6, -2, 1, 0 ], [ 4, -1, -6, -2, 2, 0 ], [ 3, -1, -6, -2, 2, 1 ] ] +], +"cur_uid": "7c30c182", +"ref_uid": "nil", +"order_seed": 993548, +"dur_seed": 828564, +"motifs_seed": 174746, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.037037037037037, 0, 0.18518518518519, 0, 0.45679012345679, 0, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58230452674897, 0, 0.61111111111111, 0, 0.7798353909465, 0, 1, 0 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 1 ], +"hd_exp": 3.22, +"hd_invert": 0, +"order": +[ + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 2 ], [ 1, 0, 3 ], [ ] ], + [ [ 0 ], [ 2, 1, 3 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 3, 1, 0 ], [ 2 ], [ ] ], + [ [ 0 ], [ 1, 3, 2 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1 ], [ ] ], + [ [ 0 ], [ 3, 2, 1 ], [ ] ], + [ [ 1, 3, 0 ], [ 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 0, 2, 3 ], [ 1 ], [ ] ], + [ [ 3, 2 ], [ 0, 1 ], [ ] ], + [ [ 2, 3, 0 ], [ 1 ], [ ] ], + [ [ 1, 0 ], [ 3, 2 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ], + [ [ 2 ], [ 3, 0, 1 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ], + [ [ 3, 0 ], [ 2, 1 ], [ ] ], + [ [ 0, 2 ], [ 1, 3 ], [ ] ], + [ [ 3, 0, 2 ], [ 1 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 0, 3 ], [ 2, 1 ], [ ] ], + [ [ 1, 2, 0 ], [ 3 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 3, 0, 1 ], [ 2 ], [ ] ], + [ [ 1 ], [ 0, 3, 2 ], [ ] ], + [ [ 1 ], [ 2, 0, 3 ], [ ] ], + [ [ 2, 1, 3 ], [ 0 ], [ ] ], + [ [ 0, 1 ], [ 3, 2 ], [ ] ], + [ [ 0, 2, 1 ], [ 3 ], [ ] ], + [ [ 3, 0 ], [ 1, 2 ], [ ] ], + [ [ 3, 1, 2 ], [ 0 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 1, 3 ], [ 0, 2 ], [ ] ], + [ [ 1, 0, 3 ], [ 2 ], [ ] ], + [ [ 2 ], [ 0, 3, 1 ], [ ] ], + [ [ 2 ], [ 3, 1, 0 ], [ ] ], + [ [ 2, 3, 1 ], [ 0 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 48.479591836735, 48.479591836735 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/7ede7adb/7ede7adb_code.scd b/resources/string_quartet_3_rise/7ede7adb/7ede7adb_code.scd new file mode 100644 index 0000000..42b64ff --- /dev/null +++ b/resources/string_quartet_3_rise/7ede7adb/7ede7adb_code.scd @@ -0,0 +1,981 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + [chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + [minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + 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.01); + + + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}) + }); + 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}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/resources/string_quartet_3_rise/7ede7adb/7ede7adb_mus_model.json b/resources/string_quartet_3_rise/7ede7adb/7ede7adb_mus_model.json new file mode 100644 index 0000000..b5aa312 --- /dev/null +++ b/resources/string_quartet_3_rise/7ede7adb/7ede7adb_mus_model.json @@ -0,0 +1,83 @@ +{ +"music_data": +[ + [ + [ + [ [ [ -4, 2, 2, 3, 1, 0 ], [ -5, 3, 2, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -4, 2, 2, 3, 1, 0 ], [ -3, 1, 1, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 1, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 1, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 2, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -2, 1, 1, 2, 1, 0 ], [ -3, 1, 1, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -5, 2, 1, 2, 1, 0 ], [ -3, 1, 1, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -4, 2, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 1, 2, 1, 0 ], [ -3, 1, 1, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -4, 3, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 2, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -4, 3, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -5, 3, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -4, 3, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -3, 1, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -4, 3, 2, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ], [ -4, 2, 2, 2, 1, 0 ], [ -2, 2, 1, 1, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ], [ -4, 3, 2, 2, 1, 0 ], [ -2, 2, 1, 1, 1, 0 ] ], 1 ], + [ [ [ -5, 3, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ], [ -4, 3, 2, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ] ], 1 ] + ], + [ + [ [ [ -5, 3, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -5, 3, 1, 2, 1, 0 ], [ -3, 1, 1, 3, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ] ], 1 ], + [ [ [ -3, 0, 1, 2, 1, 0 ], [ -3, 1, 1, 3, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ] ], 1 ] + ] + ] +], +"last_changes": +[ + [ [ -5, 3, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ], [ -4, 3, 2, 2, 1, 0 ], [ -2, 2, 1, 1, 1, 0 ] ], + [ [ -5, 3, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ], [ -4, 3, 2, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ] ], + [ [ -5, 3, 1, 2, 1, 0 ], [ -3, 2, 1, 2, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ] ], + [ [ -5, 3, 1, 2, 1, 0 ], [ -3, 1, 1, 3, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ] ], + [ [ -3, 0, 1, 2, 1, 0 ], [ -3, 1, 1, 3, 1, 0 ], [ -2, 1, 0, 2, 1, 0 ], [ -2, 1, 1, 2, 1, 0 ] ] +], +"cur_uid": "7ede7adb", +"ref_uid": "6522664c", +"order_seed": 455950, +"dur_seed": 308053, +"motifs_seed": 206233, +"entrances_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"passages_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"exits_probs_vals": [ 0, 0, 0, 1, 1, 0, 0.5, 0.5, 0.5, 1, 0.5 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ 0, 1200, 0.0061728395061728, 0.10227272727273, 0.074074074074074, 0.10227272727273, 0.2037037037037, 0.090909090909091, 0.45679012345679, 0.011363636363636, 0.53086419753086, 0, 0.54320987654321, 0.92045454545455, 0.58641975308642, 0.92613636363636, 0.61111111111111, 0, 0.7880658436214, 0.034090909090909, 1, 0.034090909090909 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 0.7 ], +"hd_exp": 7, +"hd_invert": 0, +"order": +[ + [ [ 2, 3 ], [ 0, 1 ], [ ] ], + [ [ 1, 2 ], [ 0, 3 ], [ ] ], + [ [ 2, 1 ], [ 3, 0 ], [ ] ], + [ [ 0, 1, 2 ], [ 3 ], [ ] ], + [ [ 2, 3 ], [ 1, 0 ], [ ] ], + [ [ 2, 1, 0 ], [ 3 ], [ ] ], + [ [ 1, 0, 2 ], [ 3 ], [ ] ], + [ [ 2, 0, 1 ], [ 3 ], [ ] ], + [ [ 0, 1 ], [ 2, 3 ], [ ] ], + [ [ 3 ], [ 2, 1, 0 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 10.091836734694, 10.091836734694 ], +"passages_size": [ 0, 0 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/7ede7adb/lilypond/part_I.ly b/resources/string_quartet_3_rise/7ede7adb/lilypond/part_I.ly new file mode 100644 index 0000000..41fe1e5 --- /dev/null +++ b/resources/string_quartet_3_rise/7ede7adb/lilypond/part_I.ly @@ -0,0 +1,18 @@ +{ + { g'1^\markup { \pad-markup #0.2 "-21"} ~ } + \bar "|" + { g'2 b'2^\markup { \pad-markup #0.2 "-34"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 1↑" }} } + \bar "|" + { g1^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} } + \bar "|" + { d'1^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 3↑" }} ~ } + \bar "|" + { d'2 e'2^\markup { \pad-markup #0.2 "-36"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "II"\normal-size-super " 3↓" }} } + \bar "|" + { fis'2^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} a'2^\markup { \pad-markup #0.2 "+10"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 7↓" }} ~ } + \bar "|" + { a'2 c''2^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 3↓" }} ~ } + \bar "|" + { c''1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/7ede7adb/lilypond/part_II.ly b/resources/string_quartet_3_rise/7ede7adb/lilypond/part_II.ly new file mode 100644 index 0000000..ce1994b --- /dev/null +++ b/resources/string_quartet_3_rise/7ede7adb/lilypond/part_II.ly @@ -0,0 +1,18 @@ +{ + { b1^\markup { \pad-markup #0.2 "-34"} ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 ~ } + \bar "|" + { b1 } + \bar "|" + { fis'1^\markup { \pad-markup #0.2 "-33"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 5↑" }} } + \bar "|" + { gis'1^\markup { \pad-markup #0.2 "-9"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 5↓" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/7ede7adb/lilypond/part_III.ly b/resources/string_quartet_3_rise/7ede7adb/lilypond/part_III.ly new file mode 100644 index 0000000..e5a072c --- /dev/null +++ b/resources/string_quartet_3_rise/7ede7adb/lilypond/part_III.ly @@ -0,0 +1,18 @@ +{ + { fis2^\markup { \pad-markup #0.2 "-33"} c'2^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 3↓" }} ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'1 ~ } + \bar "|" + { c'2 g'2^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "IV"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'1 ~ } + \bar "|" + { g'2 a'2^\markup { \pad-markup #0.2 "+46"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 7↑" }}} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/7ede7adb/lilypond/part_IV.ly b/resources/string_quartet_3_rise/7ede7adb/lilypond/part_IV.ly new file mode 100644 index 0000000..bf1c7e0 --- /dev/null +++ b/resources/string_quartet_3_rise/7ede7adb/lilypond/part_IV.ly @@ -0,0 +1,18 @@ +{ + { gis'1^\markup { \pad-markup #0.2 "+34"} } + \bar "|" + { c''1^\markup { \pad-markup #0.2 "-23"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "III"\normal-size-super " 1↑" }} ~ } + \bar "|" + { c''2 g,2^\markup { \pad-markup #0.2 "-21"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { g,1 } + \bar "|" + { d1^\markup { \pad-markup #0.2 "-19"}_\markup { \lower #3 \pad-markup #0.2 \concat{ "I"\normal-size-super " 1↑" }} ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1 ~ } + \bar "|" + { d1} +\bar "||" +} \ No newline at end of file diff --git a/resources/string_quartet_3_rise/tmp/tmp_mus_model.json b/resources/string_quartet_3_rise/tmp/tmp_mus_model.json new file mode 100644 index 0000000..0270086 --- /dev/null +++ b/resources/string_quartet_3_rise/tmp/tmp_mus_model.json @@ -0,0 +1,521 @@ +{ +"music_data": +[ + [ + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0.5 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0.25 ], + [ [ [ 1, 0, 0, -1, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0.625 ], + [ [ [ 1, 0, 0, -1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0.625 ], + [ [ [ 1, 0, 0, -1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ] ], 4.875 ], + [ [ [ 1, 0, 0, -1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ] ], 2.5 ], + [ [ [ 1, 0, 0, 0, 0, -1 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ] ], 0.5 ], + [ [ [ 1, 0, 0, 0, 0, -1 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 3.75 ] + ], + [ + [ [ [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 4.5 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0.625 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ] ], 4.375 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 0.75 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 0.75 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 1 ] ], 0.125 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 0 ] ], 4 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 1.625 ] + ], + [ + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 1, 1, 0, 0, 0, -1 ], [ 0, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.625 ], + [ [ [ 1, -1, 0, 0, 0, 0 ], [ 1, 1, 0, 0, 0, -1 ], [ 0, 1, 0, 0, -1, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.5 ], + [ [ [ 0, 1, 0, 0, 0, 0 ], [ 1, 1, 0, 0, 0, -1 ], [ 0, 1, 0, 0, -1, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.75 ], + [ [ [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, -1, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 4 ], + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.75 ], + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, -1, 0 ], [ 0, 1, -1, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.375 ], + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, -1, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.875 ], + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, -1, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 4 ] + ], + [ + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -2, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.375 ], + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 1, 0, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.875 ], + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 1, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.625 ], + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -2, 1, 0, 2, 0, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.5 ], + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 1 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.5 ], + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, -1, 0 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 4.75 ] + ], + [ + [ [ [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, -1, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.25 ], + [ [ [ 1, 1, 0, -1, 0, 0 ], [ -1, 1, 0, 0, -1, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 3.625 ], + [ [ [ 1, 1, 0, -1, 0, 0 ], [ -2, 1, 0, 0, 0, 1 ], [ -1, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.625 ], + [ [ [ 1, 1, 0, 0, 0, -1 ], [ -2, 1, 0, 0, 0, 1 ], [ -1, 2, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.625 ], + [ [ [ 1, 1, 0, 0, 0, -1 ], [ -2, 1, 0, 0, 0, 1 ], [ -1, 1, 0, 0, 0, 1 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.25 ], + [ [ [ 1, 1, 0, 0, 0, -1 ], [ -2, 1, 0, 0, 0, 1 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.125 ], + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -2, 1, 0, 0, 0, 1 ], [ -1, 1, 0, 1, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.125 ], + [ [ [ 1, 0, 0, 0, 0, 0 ], [ -2, 1, 0, 0, 0, 1 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 2.5 ] + ], + [ + [ [ [ -2, 1, 1, 0, 0, 0 ], [ -2, 1, 0, 0, 0, 1 ], [ 0, 1, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ] ], 0.875 ], + [ [ [ -2, 1, 1, 0, 0, 0 ], [ -2, 1, 0, 0, 0, 1 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 0.25 ], + [ [ [ -2, 1, 1, 0, 0, 0 ], [ -2, 1, 0, 1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 0.625 ], + [ [ [ -2, 1, 1, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 0.375 ], + [ [ [ -1, 0, 0, 0, 0, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 0.75 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ -1, 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 3.25 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 1, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 4.5 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 1, 1, 0, 0, 0, -1 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 3.875 ] + ], + [ + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 2, 1, 0, -2, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 0 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 0, 1, 1, -1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 0.625 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 1, 0, 0, -1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 0.25 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 1, 1, 0, -1, -1, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 0.5 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 1, 1, -1, -1, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 4 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 4.75 ] + ], + [ + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ 0, 1, 0, -1, 0, 0 ] ], 0.5 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ -1, 1, 0, 1, -1, 0 ] ], 0.625 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 0, 1, 0, 1, -1, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ -1, 1, 0, 1, -1, 0 ] ], 0.25 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 0, 1, 0, 1, -1, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 0.25 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 0, 1, 0, 1, -1, 0 ], [ 0, 1, 0, 0, -1, 1 ], [ 0, 1, 0, 0, -1, 0 ] ], 0.75 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 0, 1, 0, 1, -1, 0 ], [ -1, 1, 0, 0, -1, 1 ], [ 0, 1, 0, 0, -1, 0 ] ], 4 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 0, 1, 0, 1, -1, 0 ], [ -1, 1, 0, 1, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 3.25 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ -1, 2, 0, 0, -1, 0 ], [ -1, 1, 0, 1, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 2.75 ] + ], + [ + [ [ [ -1, 1, 0, 0, -1, 0 ], [ -1, 2, 0, 0, -1, 0 ], [ -1, 2, 1, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 0.25 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ -1, 2, 0, 0, -1, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 0.5 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ -1, 2, 0, 0, -1, 0 ], [ -1, 3, 0, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 0 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ -1, 2, 0, 0, -1, 0 ], [ 0, 1, 1, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 0.25 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ -1, 2, 0, 0, -1, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 0.5 ], + [ [ [ -1, 1, 0, 0, -1, 0 ], [ -1, 2, 0, 0, -1, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 1.5 ] + ], + [ + [ [ [ -1, 1, 0, 0, -1, 0 ], [ 0, 2, 0, -1, -1, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 3 ], + [ [ [ -2, 3, 0, 0, -1, 0 ], [ 0, 2, 0, -1, -1, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ 0, 1, 0, 0, -1, 0 ] ], 0.875 ], + [ [ [ -2, 3, 0, 0, -1, 0 ], [ 0, 2, 0, -1, -1, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ 0, 2, 0, 0, -2, 0 ] ], 0.75 ], + [ [ [ -2, 2, 0, 0, -1, 1 ], [ 0, 2, 0, -1, -1, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ 0, 2, 0, 0, -2, 0 ] ], 0.875 ], + [ [ [ -2, 2, 0, 0, -1, 1 ], [ 0, 2, 0, -1, -1, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ 0, 2, -1, 0, -1, 0 ] ], 4.25 ], + [ [ [ -2, 2, 0, 0, -1, 1 ], [ -1, 2, 1, 0, -1, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ 0, 2, -1, 0, -1, 0 ] ], 1.125 ], + [ [ [ -2, 2, 0, 0, -1, 1 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ 0, 2, -1, 0, -1, 0 ] ], 0.375 ], + [ [ [ -2, 2, 0, 1, -1, 0 ], [ -1, 2, 0, 0, 0, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ 0, 2, -1, 0, -1, 0 ] ], 1.875 ] + ], + [ + [ [ [ -2, 2, 0, 1, -1, 0 ], [ -1, 2, 0, 0, -1, 0 ], [ 0, 2, 0, 0, -1, 0 ], [ 0, 2, -1, 0, -1, 0 ] ], 0.625 ], + [ [ [ -2, 2, 0, 1, -1, 0 ], [ -1, 2, 0, 0, -1, 0 ], [ 0, 2, 0, 1, -1, -1 ], [ 0, 2, -1, 0, -1, 0 ] ], 0.625 ], + [ [ [ -2, 2, 0, 1, -1, 0 ], [ -1, 2, 0, 0, -1, 0 ], [ 0, 2, 0, 1, -1, -1 ], [ -1, 2, 0, 1, -1, 0 ] ], 0.25 ], + [ [ [ -2, 2, 0, 1, -1, 0 ], [ -1, 2, 0, 0, -1, 0 ], [ 0, 2, 0, 1, -1, -1 ], [ 0, 2, 0, 0, -1, 0 ] ], 0.375 ], + [ [ [ -2, 2, 0, 1, -1, 0 ], [ -1, 2, 0, 0, -1, 0 ], [ 0, 2, 0, 1, -1, -1 ], [ -1, 2, 1, 1, -1, 0 ] ], 0.5 ], + [ [ [ -2, 2, 0, 1, -1, 0 ], [ -1, 2, 0, 0, -1, 0 ], [ 0, 2, 0, 1, -1, -1 ], [ -1, 2, 0, 1, 0, 0 ] ], 0.125 ], + [ [ [ -2, 2, 0, 1, -1, 0 ], [ -2, 2, 1, 1, -1, 0 ], [ 0, 2, 0, 1, -1, -1 ], [ -1, 2, 0, 1, 0, 0 ] ], 3.875 ], + [ [ [ -2, 2, 0, 1, -1, 0 ], [ -2, 2, 1, 1, -1, 0 ], [ 0, 2, 0, 1, -1, -1 ], [ -1, 2, 0, 1, -1, -1 ] ], 4.125 ] + ], + [ + [ [ [ -3, 2, 1, 2, -1, 0 ], [ -2, 2, 1, 1, -1, 0 ], [ 0, 2, 0, 1, -1, -1 ], [ -1, 2, 0, 1, -1, -1 ] ], 3.375 ], + [ [ [ -3, 2, 1, 2, -1, 0 ], [ -2, 2, 1, 1, -1, 0 ], [ 0, 2, 0, 1, -1, -1 ], [ -1, 1, 1, 1, -1, 0 ] ], 0.375 ], + [ [ [ -3, 2, 1, 2, -1, 0 ], [ -2, 2, 1, 1, -1, 0 ], [ -2, 2, 1, 2, -1, 0 ], [ -1, 1, 1, 1, -1, 0 ] ], 1.125 ], + [ [ [ -3, 2, 1, 2, -1, 0 ], [ -2, 2, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -1, 1, 1, 1, -1, 0 ] ], 3.875 ], + [ [ [ -4, 2, 1, 2, -1, 0 ], [ -2, 2, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -1, 1, 1, 1, -1, 0 ] ], 4 ], + [ [ [ -4, 2, 1, 2, -1, 0 ], [ -2, 2, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -2, 0 ] ], 3.875 ], + [ [ [ -4, 2, 1, 2, -1, 0 ], [ -2, 2, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -2, 2, 1, 1, -1, 1 ] ], 0.375 ], + [ [ [ -3, 2, 1, 1, -1, 0 ], [ -2, 2, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -2, 2, 1, 1, -1, 1 ] ], 1.5 ] + ], + [ + [ [ [ -2, 2, 1, 0, -1, 0 ], [ -2, 2, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -2, 2, 1, 1, -1, 1 ] ], 1 ], + [ [ [ -2, 2, 1, 0, -1, 0 ], [ -1, 2, 1, 0, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -2, 2, 1, 1, -1, 1 ] ], 0.875 ], + [ [ [ -2, 2, 1, 1, -1, -1 ], [ -1, 2, 1, 0, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -2, 2, 1, 1, -1, 1 ] ], 0.125 ], + [ [ [ -2, 1, 1, 1, -1, 0 ], [ -1, 2, 1, 0, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -2, 2, 1, 1, -1, 1 ] ], 0.75 ], + [ [ [ -2, 1, 1, 1, -1, 0 ], [ -2, 2, 2, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -2, 2, 1, 1, -1, 1 ] ], 0.625 ], + [ [ [ -2, 1, 1, 1, -1, 0 ], [ -2, 2, 1, 1, 0, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -2, 2, 1, 1, -1, 1 ] ], 0.875 ], + [ [ [ -2, 1, 1, 1, -1, 0 ], [ -2, 3, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -2, 2, 1, 1, -1, 1 ] ], 5.125 ] + ], + [ + [ [ [ -2, 2, 1, 1, -2, 0 ], [ -2, 3, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -2, 2, 1, 1, -1, 1 ] ], 0.375 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ -2, 3, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -2, 3, 2, 1, -1, 0 ] ], 0.75 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ -2, 3, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -2, 3, 1, 1, 0, 0 ] ], 0.75 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ -2, 3, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -1, 3, 1, 1, -2, 0 ] ], 3.875 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ -2, 3, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -3, 4, 1, 1, -1, 0 ] ], 4.25 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ -2, 3, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -2, 2, 2, 1, -1, 0 ] ], 0.625 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ -2, 3, 1, 1, -1, 0 ], [ -1, 2, 1, 1, -1, 0 ], [ -1, 1, 1, 1, -1, 0 ] ], 2.75 ] + ], + [ + [ [ [ -2, 2, 1, 1, -2, 0 ], [ -2, 3, 1, 1, -1, 0 ], [ -1, 3, 1, 1, -2, 0 ], [ -1, 1, 1, 1, -1, 0 ] ], 4 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ -2, 3, 1, 1, -1, 0 ], [ -1, 3, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -2, 0 ] ], 0.375 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ 0, 2, 1, 0, -2, 0 ], [ -1, 3, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -2, 0 ] ], 4.5 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ 0, 2, 1, 0, -2, 0 ], [ -1, 3, 1, 1, -2, 0 ], [ -2, 2, 1, 1, -1, 0 ] ], 0.625 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ 0, 2, 1, 0, -2, 0 ], [ -1, 3, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 0 ] ], 0.875 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ -1, 2, 2, 1, -2, 0 ], [ -1, 3, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 0 ] ], 0.375 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 3, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 0 ] ], 0.125 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -2, 2, 1, 1, -2, 1 ], [ -1, 2, 1, 1, -3, 0 ] ], 1.25 ] + ], + [ + [ [ [ -2, 3, 1, 1, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -2, 2, 1, 1, -2, 1 ], [ -1, 2, 1, 1, -3, 0 ] ], 0.625 ], + [ [ [ -2, 3, 1, 1, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ 0, 2, 1, 1, -3, -1 ], [ -1, 2, 1, 1, -3, 0 ] ], 0.625 ], + [ [ [ -2, 3, 1, 1, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 0 ] ], 0.375 ], + [ [ [ -2, 1, 1, 2, -2, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 0 ] ], 0.625 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 0 ] ], 0.5 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 3, 1, 1, -3, 0 ], [ -1, 2, 1, 1, -3, 0 ] ], 0.75 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 0 ] ], 3.125 ] + ], + [ + [ [ [ -2, 1, 2, 1, -2, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 0 ] ], 0.375 ], + [ [ [ -2, 2, 2, 1, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 0 ] ], 0.125 ], + [ [ [ -1, 1, 1, 1, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 0 ] ], 0.5 ], + [ [ [ -2, 1, 1, 1, -2, 1 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 0 ] ], 4 ], + [ [ [ -1, 2, 0, 1, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 0 ] ], 4.125 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 0 ] ], 4.875 ] + ], + [ + [ [ [ -2, 2, 1, 2, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.5 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 2, -2, 0 ] ], 0.25 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 3, 1, 2, -3, 0 ] ], 0.5 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 2, -3, 1 ] ], 0.75 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 1, 1, 2, -2, 0 ] ], 0.625 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ 0, 2, 1, 0, -3, 1 ] ], 9.25 ] + ], + [ + [ [ [ -2, 2, 1, 2, -3, 0 ], [ 0, 1, 1, 1, -2, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 2, 2, 1, -3, 1 ] ], 0.25 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ 0, 2, 1, 1, -3, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 2, 2, 1, -3, 1 ] ], 4.125 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ 0, 2, 1, 1, -3, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 2, 1, 2, -4, 0 ] ], 4.375 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ -2, 2, 1, 1, -4, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 2, 1, 2, -4, 0 ] ], 0.125 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ -2, 2, 1, 1, -4, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 2, 0, 2, -3, 0 ] ], 0.5 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ -2, 2, 0, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -1, 2, 0, 2, -3, 0 ] ], 0.5 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ -2, 2, 0, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 0.75 ] + ], + [ + [ [ [ -2, 2, 1, 2, -3, 0 ], [ -3, 2, 1, 2, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 0.25 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ -2, 2, 1, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 0.5 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ -1, 2, 1, 0, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 0.75 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ -2, 1, 1, 3, -3, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 0.125 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ -2, 2, 1, 3, -4, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 0.75 ], + [ [ [ -2, 2, 1, 2, -3, 0 ], [ -2, 3, 1, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 4.25 ] + ], + [ + [ [ [ -2, 2, 1, 3, -3, -1 ], [ -2, 3, 1, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 0.625 ], + [ [ [ -2, 2, 1, 3, -3, -1 ], [ -1, 2, 0, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 0.75 ], + [ [ [ -2, 1, 1, 3, -3, 0 ], [ -1, 2, 0, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 0.5 ], + [ [ [ -2, 1, 1, 3, -3, 0 ], [ -4, 3, 1, 3, -3, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 0.375 ], + [ [ [ -4, 3, 1, 1, -3, 1 ], [ -4, 3, 1, 3, -3, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 0.625 ], + [ [ [ -4, 3, 1, 1, -3, 1 ], [ -4, 2, 1, 3, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 0.75 ], + [ [ [ -4, 2, 1, 1, -3, 2 ], [ -4, 2, 1, 3, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 3 ] + ], + [ + [ [ [ -4, 2, 1, 1, -3, 2 ], [ -3, 2, 1, 1, -3, 2 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 3, -3, 0 ] ], 0.5 ], + [ [ [ -4, 2, 1, 1, -3, 2 ], [ -3, 2, 1, 1, -3, 2 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 2, 1, -3, 1 ] ], 4.875 ], + [ [ [ -4, 2, 1, 2, -3, 1 ], [ -3, 2, 1, 1, -3, 2 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 2, 1, -3, 1 ] ], 0.75 ], + [ [ [ -3, 2, 1, 1, -3, 1 ], [ -3, 2, 1, 1, -3, 2 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 2, 1, -3, 1 ] ], 0.125 ], + [ [ [ -3, 2, 1, 1, -3, 1 ], [ -3, 2, 1, 2, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 2, 1, -3, 1 ] ], 3.75 ], + [ [ [ -3, 2, 1, 1, -3, 1 ], [ -3, 2, 1, 2, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.75 ], + [ [ [ -3, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.625 ], + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -2, 2, 1, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 1.125 ] + ], + [ + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -3, 2, 1, 1, -2, 2 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.625 ], + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -1, 2, 1, 1, -3, 0 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.625 ], + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -1, 1, 1, 1, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.875 ], + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -1, 2, 1, 1, -4, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.75 ], + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -1, 2, 1, 0, -2, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.625 ], + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -2, 2, 2, 1, -2, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 1.125 ] + ], + [ + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -2, 2, 2, 1, -2, 1 ], [ -1, 2, 0, 1, -2, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.125 ], + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -1, 1, 1, 1, -2, 1 ], [ -1, 2, 0, 1, -2, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 4.25 ], + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -1, 1, 1, 1, -2, 1 ], [ -2, 2, 1, 2, -2, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 4.125 ], + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ -2, 2, 1, 2, -2, 1 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.375 ], + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -1, 2, 1, 1, -3, 1 ], [ 0, 2, 1, 0, -3, 0 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.75 ], + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -1, 2, 0, 1, -2, 1 ], [ 0, 2, 1, 0, -3, 0 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.875 ], + [ [ [ -2, 2, 1, 0, -3, 1 ], [ -2, 2, 1, 2, -2, 1 ], [ 0, 2, 1, 0, -3, 0 ], [ -2, 2, 1, 1, -2, 1 ] ], 5.375 ] + ], + [ + [ [ [ -2, 2, 1, 1, -3, 0 ], [ -2, 2, 1, 2, -2, 1 ], [ 0, 2, 1, 0, -3, 0 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.5 ], + [ [ [ -3, 2, 1, 1, -2, 1 ], [ -2, 2, 1, 2, -2, 1 ], [ 0, 2, 1, 0, -3, 0 ], [ -2, 2, 1, 1, -2, 1 ] ], 0 ], + [ [ [ -4, 2, 2, 2, -2, 1 ], [ -2, 2, 1, 2, -2, 1 ], [ 0, 2, 1, 0, -3, 0 ], [ -2, 2, 1, 1, -2, 1 ] ], 4.875 ], + [ [ [ -2, 2, 1, 1, -2, 0 ], [ -2, 2, 1, 2, -2, 1 ], [ 0, 2, 1, 0, -3, 0 ], [ -2, 2, 1, 1, -2, 1 ] ], 3.875 ], + [ [ [ -2, 1, 1, 1, -2, 1 ], [ -2, 2, 1, 2, -2, 1 ], [ 0, 2, 1, 0, -3, 0 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.5 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ -2, 2, 1, 2, -2, 1 ], [ 0, 2, 1, 0, -3, 0 ], [ -2, 2, 1, 1, -2, 1 ] ], 1.625 ] + ], + [ + [ [ [ -1, 3, 1, 0, -3, 0 ], [ -2, 2, 1, 2, -2, 1 ], [ 0, 3, 1, 0, -4, 0 ], [ -2, 2, 1, 1, -2, 1 ] ], 0.625 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ 0, 3, 1, 0, -4, 0 ], [ -2, 2, 1, 1, -2, 1 ] ], 2.125 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ 0, 3, 1, 0, -4, 0 ], [ -1, 3, 1, 0, -2, 0 ] ], 0.25 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ 0, 3, 1, 0, -4, 0 ], [ -1, 4, 1, 0, -3, 0 ] ], 0.75 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ -1, 3, 1, 0, -3, 1 ], [ -1, 4, 1, 0, -3, 0 ] ], 0.75 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ -1, 3, 1, 1, -3, 0 ], [ -1, 4, 1, 0, -3, 0 ] ], 0.625 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ 0, 3, 1, 0, -3, 0 ], [ -1, 4, 1, 0, -3, 0 ] ], 3.875 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 1, -1, -3, 0 ], [ -1, 4, 1, 0, -3, 0 ] ], 3.375 ] + ], + [ + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ -1, 3, 1, 1, -3, -1 ], [ -1, 4, 1, 0, -3, 0 ] ], 0.625 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ -1, 3, 1, 1, -3, -1 ], [ -1, 3, 1, 0, -3, 1 ] ], 0.625 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ -1, 3, 1, 1, -3, -1 ], [ 1, 3, 1, 0, -4, -1 ] ], 0.375 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ -1, 3, 1, 1, -3, -1 ], [ 1, 3, 0, 0, -3, -1 ] ], 0.625 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ -1, 3, 1, 1, -3, -1 ], [ 0, 3, 1, 1, -3, -1 ] ], 0.625 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ -1, 3, 1, 1, -3, -1 ], [ -1, 4, 1, 0, -3, -1 ] ], 0.625 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ 0, 3, 1, -1, -3, 0 ], [ -1, 4, 1, 0, -3, -1 ] ], 2.875 ] + ], + [ + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ 0, 3, 1, -1, -3, 0 ], [ 0, 3, 0, 0, -3, -1 ] ], 0.875 ], + [ [ [ -1, 3, 1, 0, -3, 0 ], [ 1, 3, 1, 0, -3, -1 ], [ 0, 3, 1, 0, -3, -1 ], [ 0, 3, 0, 0, -3, -1 ] ], 4.625 ], + [ [ [ -1, 3, 1, 1, -3, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 0, 3, 1, 0, -3, -1 ], [ 0, 3, 0, 0, -3, -1 ] ], 3.5 ], + [ [ [ -1, 3, 1, 1, -3, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 0, 3, 1, 0, -3, -1 ], [ -1, 3, 1, 0, -2, -1 ] ], 4.375 ], + [ [ [ -1, 3, 1, 1, -3, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 0, 3, 1, 0, -3, -1 ], [ 0, 3, 1, 0, -4, -1 ] ], 0.625 ], + [ [ [ -1, 3, 1, 1, -3, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 0, 3, 1, 0, -3, -1 ], [ -1, 3, 1, 0, -3, 0 ] ], 0.375 ], + [ [ [ -1, 3, 1, 1, -3, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 1, -1, -3, -1 ], [ -1, 3, 1, 0, -3, 0 ] ], 0 ], + [ [ [ -1, 3, 1, 1, -3, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 1, -1, -3, -1 ], [ 0, 3, 1, 0, -3, -1 ] ], 9.375 ] + ], + [ + [ [ [ 0, 3, 1, 0, -3, -2 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 1, -1, -3, -1 ], [ 0, 3, 1, 0, -3, -1 ] ], 0.75 ], + [ [ [ 0, 3, 1, 0, -3, -2 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 1, 0, -3, -2 ], [ 0, 3, 1, 0, -3, -1 ] ], 0.75 ], + [ [ [ 0, 3, 1, 0, -3, -2 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 2, 1, 0, -3, -1 ], [ 0, 3, 1, 0, -3, -1 ] ], 0.625 ], + [ [ [ 0, 3, 1, 0, -3, -2 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 0, 3, 1, 0, -3, -1 ] ], 0.375 ], + [ [ [ 0, 2, 1, 0, -3, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 0, 3, 1, 0, -3, -1 ] ], 0.5 ], + [ [ [ 0, 2, 1, 0, -3, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 0, 0, -3, -1 ], [ 0, 3, 1, 0, -3, -1 ] ], 0.25 ], + [ [ [ 0, 3, 1, 0, -4, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 0, 0, -3, -1 ], [ 0, 3, 1, 0, -3, -1 ] ], 3.125 ] + ], + [ + [ [ [ 0, 3, 1, 0, -4, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 0, 0, -3, -1 ], [ 0, 4, 1, 0, -4, -1 ] ], 0.375 ], + [ [ [ 0, 3, 1, 0, -4, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 0, 0, -3, -1 ], [ 0, 4, 0, 0, -3, -1 ] ], 0.25 ], + [ [ [ 0, 3, 1, 0, -4, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 0, 0, -3, -1 ], [ 0, 3, 1, 1, -4, -1 ] ], 0.75 ], + [ [ [ 0, 3, 1, 0, -4, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 0, 0, -3, -1 ], [ 0, 3, 0, 1, -3, -1 ] ], 3.875 ], + [ [ [ 0, 3, 1, 0, -4, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 0, 0, -3, -1 ], [ 0, 4, 1, 0, -3, -1 ] ], 0.625 ], + [ [ [ 0, 3, 1, 0, -4, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 0, 0, -3, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 1 ] + ], + [ + [ [ [ 0, 3, 1, 0, -4, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 2, -1, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 0.5 ], + [ [ [ 1, 3, 1, -1, -4, -1 ], [ 1, 3, 1, 0, -3, -1 ], [ 1, 3, 2, -1, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 1 ], + [ [ [ 1, 3, 1, -1, -4, -1 ], [ 1, 4, 1, -1, -4, -1 ], [ 1, 3, 2, -1, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 0.5 ], + [ [ [ 1, 3, 1, -1, -4, -1 ], [ 1, 3, 1, -1, -4, 0 ], [ 1, 3, 2, -1, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 3.375 ], + [ [ [ 1, 3, 1, -1, -4, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 1, 3, 2, -1, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 0.625 ], + [ [ [ 1, 3, 1, -1, -4, -1 ], [ 2, 2, 1, -1, -4, -1 ], [ 1, 3, 2, -1, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 3.125 ], + [ [ [ 1, 3, 1, -1, -4, -1 ], [ 2, 2, 1, -1, -4, -1 ], [ 1, 3, 1, -1, -3, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 1 ], + [ [ [ 2, 3, 1, -2, -4, -1 ], [ 2, 2, 1, -1, -4, -1 ], [ 1, 3, 1, -1, -3, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 0.75 ] + ], + [ + [ [ [ 2, 3, 1, -2, -4, -1 ], [ 2, 3, 1, -1, -5, -1 ], [ 1, 3, 1, -1, -3, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 0.5 ], + [ [ [ 1, 3, 2, -1, -4, -1 ], [ 2, 3, 1, -1, -5, -1 ], [ 1, 3, 1, -1, -3, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 4.25 ], + [ [ [ 1, 3, 2, -1, -4, -1 ], [ 2, 3, 1, -1, -5, -1 ], [ 1, 4, 1, -1, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 3.375 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 2, 3, 1, -1, -5, -1 ], [ 1, 4, 1, -1, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 0.5 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 2, 3, 0, -1, -4, -1 ], [ 1, 4, 1, -1, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 4.375 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 2, 3, 0, -1, -4, -1 ], [ 1, 3, 1, -1, -4, 0 ], [ 2, 3, 1, -1, -4, -1 ] ], 0.75 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 2, 3, 0, -1, -4, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 0.5 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 0, 3, 1, -1, -3, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 1.5 ] + ], + [ + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 2, 2, 1, -2, -4, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ] ], 0.375 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 2, 2, 1, -2, -4, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 3, 2, 0, -1, -4, -1 ] ], 0.375 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 2, 2, 1, -2, -4, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.625 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 2, 2, 1, -1, -4, -2 ], [ 1, 3, 1, 0, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.125 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 2, 2, 1, -1, -4, -2 ], [ 1, 3, 1, 0, -4, -1 ], [ 2, 3, 1, 0, -4, -2 ] ], 0.75 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 1, 2, 1, -1, -3, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 2, 3, 1, 0, -4, -2 ] ], 3.75 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 1, 3, 1, -1, -4, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 2, 3, 1, 0, -4, -2 ] ], 1.5 ] + ], + [ + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 1, 3, 1, -1, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ], [ 2, 3, 1, 0, -4, -2 ] ], 3.5 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 1, 2, 1, -1, -4, 0 ], [ 2, 3, 1, -1, -4, -1 ], [ 2, 3, 1, 0, -4, -2 ] ], 0.875 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 1, 2, 1, -1, -4, 0 ], [ 2, 3, 1, -1, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.625 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 2, 3, 1, -1, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.125 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 2, 2, 1, -1, -4, 0 ], [ 2, 2, 1, 0, -4, -1 ] ], 4.25 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 2, 2, 0, -1, -4, -1 ], [ 2, 2, 1, -1, -4, 0 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.75 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 2, 1, 1, -1, -4, -1 ], [ 2, 2, 1, -1, -4, 0 ], [ 2, 2, 1, 0, -4, -1 ] ], 4.125 ], + [ [ [ 2, 2, 1, -1, -4, -1 ], [ 2, 1, 1, -1, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 2.75 ] + ], + [ + [ [ [ 1, 2, 2, 0, -4, -1 ], [ 2, 1, 1, -1, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.5 ], + [ [ [ 1, 2, 2, 0, -4, -1 ], [ 0, 2, 1, 1, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.375 ], + [ [ [ 1, 2, 2, 0, -4, -1 ], [ 0, 2, 1, 1, -4, -1 ], [ 2, 2, 1, -1, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 4.25 ], + [ [ [ 2, 1, 1, 0, -4, -1 ], [ 0, 2, 1, 1, -4, -1 ], [ 2, 2, 1, -1, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.25 ], + [ [ [ 1, 3, 1, 0, -4, -1 ], [ 0, 2, 1, 1, -4, -1 ], [ 2, 2, 1, -1, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.375 ], + [ [ [ 1, 2, 1, 0, -4, 0 ], [ 0, 2, 1, 1, -4, -1 ], [ 2, 2, 1, -1, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.375 ], + [ [ [ 1, 2, 1, 0, -4, 0 ], [ 1, 2, 1, 0, -4, -1 ], [ 2, 2, 1, -1, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.5 ], + [ [ [ 1, 2, 1, 1, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 2, 2, 1, -1, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 4.125 ] + ], + [ + [ [ [ 1, 2, 1, 1, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 2, 2, 1, 0, -4, -2 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.5 ], + [ [ [ 0, 2, 1, -1, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 2, 2, 1, 0, -4, -2 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.5 ], + [ [ [ 0, 2, 1, -1, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 2, 2, 1, 0, -4, -2 ], [ 2, 2, 0, 0, -4, -1 ] ], 0.625 ], + [ [ [ 0, 2, 1, -1, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 2, 1, 1, 0, -4, -1 ], [ 2, 2, 0, 0, -4, -1 ] ], 0.875 ], + [ [ [ 0, 2, 1, -1, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 2, 2, 0, 0, -4, -1 ] ], 0.625 ], + [ [ [ 0, 2, 1, -1, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 1, 2, 1, 1, -4, -1 ] ], 4.375 ], + [ [ [ -1, 2, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 1, 2, 1, 1, -4, -1 ] ], 0.625 ], + [ [ [ -1, 2, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ], [ 1, 2, 1, 1, -4, -1 ] ], 5.25 ] + ], + [ + [ [ [ -1, 2, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.625 ], + [ [ [ -1, 2, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 1, 2, 2, 0, -3, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.75 ], + [ [ [ -1, 2, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ] ], 0.25 ], + [ [ [ -1, 2, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 2, 2, 1, 0, -4, -2 ] ], 0.5 ], + [ [ [ -1, 2, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -3, -1 ] ], 0.625 ], + [ [ [ -1, 2, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 3, 1, 0, -4, -1 ] ], 0.75 ], + [ [ [ -1, 2, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 3.75 ] + ], + [ + [ [ [ -1, 2, 2, 0, -4, -1 ], [ 1, 3, 2, -1, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.25 ], + [ [ [ -2, 2, 1, 0, -4, 1 ], [ 1, 3, 2, -1, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.875 ], + [ [ [ -2, 2, 1, 0, -4, 1 ], [ 0, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.625 ], + [ [ [ -2, 2, 1, 0, -4, 1 ], [ 0, 2, 1, 0, -4, 1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.75 ], + [ [ [ -2, 2, 1, 1, -4, 0 ], [ 0, 2, 1, 0, -4, 1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.625 ], + [ [ [ -1, 2, 1, 0, -4, 0 ], [ 0, 2, 1, 0, -4, 1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.625 ], + [ [ [ -1, 3, 2, 0, -4, -1 ], [ 0, 2, 1, 0, -4, 1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 1.375 ] + ], + [ + [ [ [ -1, 3, 2, 0, -4, -1 ], [ 1, 3, 1, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 4 ], + [ [ [ -1, 3, 2, 0, -4, -1 ], [ 0, 3, 2, 1, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.875 ], + [ [ [ -1, 3, 2, 0, -4, -1 ], [ 2, 2, 1, -1, -4, 0 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.625 ], + [ [ [ -1, 3, 2, 0, -4, -1 ], [ 2, 2, 1, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.625 ], + [ [ [ -1, 3, 2, 0, -4, -1 ], [ 0, 1, 1, 0, -4, 0 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 3.75 ], + [ [ [ -1, 3, 2, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 1.5 ] + ], + [ + [ [ [ -1, 2, 2, 0, -4, 0 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.625 ], + [ [ [ 0, 1, 1, 0, -4, 0 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 4.625 ], + [ [ [ 0, 2, 2, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.75 ], + [ [ [ -1, 4, 2, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 3 ], + [ [ [ 0, 3, 1, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.125 ], + [ [ [ -1, 3, 2, 1, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 1.25 ] + ], + [ + [ [ [ -1, 4, 3, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 4.875 ], + [ [ [ 1, 2, 1, -1, -4, 0 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0 ], + [ [ [ 1, 2, 1, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 4.25 ], + [ [ [ 1, 1, 1, 0, -4, 0 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.625 ], + [ [ [ 0, 3, 3, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 4.5 ], + [ [ [ 0, 3, 2, 0, -3, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 3.125 ] + ], + [ + [ [ [ 0, 3, 4, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 2, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.625 ], + [ [ [ 0, 3, 4, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 0, 3, 3, 1, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 4 ], + [ [ [ 0, 3, 4, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 2, 1, 0, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 3 ], + [ [ [ 0, 3, 4, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 0, 2, 1, 0, -3, 0 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.5 ], + [ [ [ 1, 2, 3, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 0, 2, 1, 0, -3, 0 ], [ 1, 2, 1, 0, -4, 0 ] ], 4.5 ], + [ [ [ 1, 2, 3, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 2, 1, 0, -5, 0 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.25 ], + [ [ [ 1, 2, 3, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 3.25 ] + ], + [ + [ [ [ 1, 3, 3, 0, -5, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.75 ], + [ [ [ 2, 3, 3, -1, -5, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.875 ], + [ [ [ 0, 3, 3, 1, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.5 ], + [ [ [ 0, 2, 3, 0, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 4 ], + [ [ [ 1, 2, 3, -1, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 0.5 ], + [ [ [ 0, 4, 3, -1, -4, -1 ], [ -1, 3, 3, 0, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 1.375 ] + ], + [ + [ [ [ 0, 4, 3, -1, -4, -1 ], [ 0, 3, 3, -1, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 3.875 ], + [ [ [ 0, 3, 3, -1, -4, 0 ], [ 0, 3, 3, -1, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 2, 1, 0, -4, 0 ] ], 3.5 ], + [ [ [ 0, 3, 3, -1, -4, 0 ], [ 0, 3, 3, -1, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 2, 2, 3, -1, -4, -1 ] ], 0.875 ], + [ [ [ 0, 3, 3, -1, -4, 0 ], [ 0, 3, 3, -1, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 4.625 ], + [ [ [ 0, 3, 3, -1, -4, 0 ], [ 1, 3, 3, -2, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 0.75 ], + [ [ [ 0, 3, 3, 0, -4, -1 ], [ 1, 3, 3, -2, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 0.5 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 1, 3, 3, -2, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 0.75 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 3, 4, -1, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 0.75 ] + ], + [ + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 3, 4, -1, -4, -1 ], [ 0, 3, 4, 0, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 0.125 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 3, 4, -1, -4, -1 ], [ 0, 3, 4, 0, -4, -1 ], [ 1, 2, 4, -1, -4, -1 ] ], 0.5 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 3, 4, -1, -4, -1 ], [ 0, 3, 4, 0, -4, -1 ], [ 0, 4, 4, -1, -4, -1 ] ], 0.625 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 3, 4, -1, -4, -1 ], [ 1, 3, 4, -1, -4, -1 ], [ 0, 4, 4, -1, -4, -1 ] ], 0.5 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 3, 4, -1, -4, -1 ], [ 1, 3, 4, -1, -4, -1 ], [ 0, 3, 4, -1, -4, 0 ] ], 1 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 3, 4, -1, -4, -1 ], [ 1, 3, 4, -1, -4, -1 ], [ 0, 3, 4, 0, -4, -1 ] ], 0.5 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 3, 4, -1, -4, -1 ], [ 1, 3, 4, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ] ], 4.375 ] + ], + [ + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 3, 3, -1, -3, -1 ], [ 1, 3, 4, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ] ], 0.625 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 4, 3, -1, -4, -1 ], [ 1, 3, 4, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ] ], 4.25 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 3, 3, -1, -4, 0 ], [ 1, 3, 4, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ] ], 0.75 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -2 ], [ 1, 3, 4, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ] ], 0.5 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 3, 4, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ] ], 0.5 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 1, 3, 4, -1, -4, -2 ], [ 1, 3, 4, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ] ], 6.375 ] + ], + [ + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 1, 3, 4, -1, -4, -2 ], [ 1, 3, 4, -1, -4, -1 ], [ 2, 2, 3, -1, -4, -1 ] ], 0.625 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 3, 4, -1, -3, -1 ], [ 1, 3, 4, -1, -4, -1 ], [ 2, 2, 3, -1, -4, -1 ] ], 0.875 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 4, 4, -1, -4, -1 ], [ 1, 3, 4, -1, -4, -1 ], [ 2, 2, 3, -1, -4, -1 ] ], 0.625 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 0, 3, 4, -1, -4, 0 ], [ 1, 3, 4, -1, -4, -1 ], [ 2, 2, 3, -1, -4, -1 ] ], 0.875 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 2, 3, 3, -2, -4, -1 ], [ 1, 3, 4, -1, -4, -1 ], [ 2, 2, 3, -1, -4, -1 ] ], 0.75 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 2, 3, 3, -2, -4, -1 ], [ 1, 3, 4, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 0.375 ], + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ], [ 1, 3, 4, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 3.875 ] + ], + [ + [ [ [ -1, 3, 3, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ], [ 0, 4, 3, 0, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 0.5 ], + [ [ [ -2, 5, 3, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ], [ 0, 4, 3, 0, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 0.625 ], + [ [ [ -2, 5, 3, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ], [ -1, 4, 3, 0, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 0.375 ], + [ [ [ -2, 5, 3, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 0.375 ], + [ [ [ -2, 4, 3, -1, -4, 0 ], [ 2, 3, 3, -1, -4, -2 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 0.75 ], + [ [ [ -2, 4, 3, -1, -4, 0 ], [ 2, 3, 3, -1, -4, -2 ], [ 1, 3, 3, 0, -4, -2 ], [ 1, 4, 3, -1, -4, -1 ] ], 0.375 ], + [ [ [ -2, 4, 3, -1, -4, 0 ], [ 2, 3, 3, -1, -4, -2 ], [ 1, 4, 2, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], 2.375 ] + ] + ] +], +"last_changes": +[ + [ [ -2, 5, 3, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ], [ -1, 4, 3, 0, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], + [ [ -2, 5, 3, -1, -4, -1 ], [ 2, 3, 3, -1, -4, -2 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], + [ [ -2, 4, 3, -1, -4, 0 ], [ 2, 3, 3, -1, -4, -2 ], [ 1, 3, 3, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ], + [ [ -2, 4, 3, -1, -4, 0 ], [ 2, 3, 3, -1, -4, -2 ], [ 1, 3, 3, 0, -4, -2 ], [ 1, 4, 3, -1, -4, -1 ] ], + [ [ -2, 4, 3, -1, -4, 0 ], [ 2, 3, 3, -1, -4, -2 ], [ 1, 4, 2, -1, -4, -1 ], [ 1, 4, 3, -1, -4, -1 ] ] +], +"cur_uid": "tmp", +"ref_uid": "nil", +"order_seed": 668999, +"dur_seed": 772296, +"motifs_seed": 584044, +"entrances_probs_vals": [ 0, 0, 5.3174603174603, 0, 5, 0, 0, 0, 0.013513513513513, 0.12745098039216, 0.59459459459459, 0.2156862745098, 0, 0.29738562091503, 0.0033783783783784, 0.55555555555556, 0, 0.8202614379085, 0.074324324324325, 1, 0 ], +"passages_probs_vals": [ 0, 0, 5.3174603174603, 0, 5, 0, 0, 0, 0.013513513513513, 0.12745098039216, 0.59459459459459, 0.2156862745098, 0, 0.29738562091503, 0.0033783783783784, 0.55555555555556, 0, 0.8202614379085, 0.074324324324325, 1, 0 ], +"exits_probs_vals": [ 0, 0, 5.3174603174603, 0, 5, 0, 0, 0, 0.013513513513513, 0.12745098039216, 0.59459459459459, 0.2156862745098, 0, 0.29738562091503, 0.0033783783783784, 0.55555555555556, 0, 0.8202614379085, 0.074324324324325, 1, 0 ], +"ranges": [ [ -2400, 1200 ], [ -1200, 1200 ], [ -702, 1200 ], [ -702, 1200 ] ], +"step_probs_vals": [ -1200, 1200, 0, 0, 0.037037037037037, 0, 0.18518518518519, 0, 0.40740740740741, 0, 0.44444444444444, 0, 0.48765432098765, 0, 0.53086419753086, 0, 0.53292181069959, 0, 0.55555555555556, 0.91477272727273, 0.60905349794239, 0, 0.61111111111111, 0, 0.7798353909465, 0, 1, 0 ], +"passages_weights": [ 1, 0.47, 0.43, 1, 0.84 ], +"hd_exp": 2.17, +"hd_invert": 0, +"order": +[ + [ [ 2 ], [ 3, 1, 0, 1, 3, 3, 0, 3 ], [ ] ], + [ [ 2 ], [ 1, 0, 3, 3, 1, 3, 3, 3 ], [ ] ], + [ [ 3 ], [ 1, 2, 0, 1, 1, 2, 1, 2 ], [ ] ], + [ [ 2, 3, 0 ], [ 1, 1, 1, 1, 1, 1 ], [ ] ], + [ [ 3 ], [ 2, 0, 1, 0, 2, 2, 0, 2 ], [ ] ], + [ [ 2 ], [ 0, 3, 1, 1, 0, 0, 1, 1 ], [ ] ], + [ [ 0, 2, 3 ], [ 1, 1, 1, 1, 1, 1 ], [ ] ], + [ [ 0 ], [ 2, 3, 1, 3, 2, 2, 2, 1 ], [ ] ], + [ [ 0, 1, 3 ], [ 2, 2, 2, 2, 2, 2 ], [ ] ], + [ [ 2 ], [ 1, 0, 3, 0, 3, 1, 1, 0 ], [ ] ], + [ [ 0 ], [ 1, 2, 3, 3, 3, 3, 1, 3 ], [ ] ], + [ [ 1 ], [ 0, 3, 2, 2, 0, 3, 3, 0 ], [ ] ], + [ [ 3, 2 ], [ 0, 1, 0, 0, 1, 1, 1 ], [ ] ], + [ [ 2, 1 ], [ 0, 3, 3, 3, 3, 3, 3 ], [ ] ], + [ [ 0 ], [ 2, 3, 1, 3, 3, 1, 1, 2 ], [ ] ], + [ [ 1, 3 ], [ 0, 2, 2, 0, 0, 2, 2 ], [ ] ], + [ [ 1, 3, 2 ], [ 0, 0, 0, 0, 0, 0 ], [ ] ], + [ [ 0, 1, 2 ], [ 3, 3, 3, 3, 3, 3 ], [ ] ], + [ [ 2, 0 ], [ 3, 1, 3, 1, 3, 1, 3 ], [ ] ], + [ [ 2, 3, 0 ], [ 1, 1, 1, 1, 1, 1 ], [ ] ], + [ [ 2, 3 ], [ 0, 1, 0, 1, 0, 1, 0 ], [ ] ], + [ [ 2 ], [ 1, 3, 0, 0, 1, 3, 1, 0 ], [ ] ], + [ [ 3, 2, 0 ], [ 1, 1, 1, 1, 1, 1 ], [ ] ], + [ [ 3, 0 ], [ 2, 1, 2, 1, 2, 1, 1 ], [ ] ], + [ [ 2, 3, 1 ], [ 0, 0, 0, 0, 0, 0 ], [ ] ], + [ [ 0 ], [ 2, 1, 3, 3, 2, 2, 2, 2 ], [ ] ], + [ [ 0, 1 ], [ 2, 3, 3, 3, 3, 3, 2 ], [ ] ], + [ [ 1 ], [ 3, 2, 0, 3, 3, 3, 2, 3 ], [ ] ], + [ [ 1, 3 ], [ 0, 2, 2, 2, 0, 2, 0 ], [ ] ], + [ [ 1, 0, 2 ], [ 3, 3, 3, 3, 3, 3 ], [ ] ], + [ [ 3 ], [ 2, 0, 1, 1, 1, 1, 2, 0 ], [ ] ], + [ [ 3 ], [ 1, 0, 2, 0, 1, 2, 2, 1 ], [ ] ], + [ [ 2, 0 ], [ 1, 3, 3, 1, 3, 1, 1 ], [ ] ], + [ [ 0 ], [ 2, 1, 3, 1, 2, 1, 1, 2 ], [ ] ], + [ [ 3 ], [ 0, 1, 2, 0, 0, 0, 1, 0 ], [ ] ], + [ [ 1 ], [ 2, 0, 3, 2, 2, 3, 0, 2 ], [ ] ], + [ [ 1, 0 ], [ 3, 2, 2, 3, 3, 3, 3 ], [ ] ], + [ [ 2, 3 ], [ 1, 0, 1, 1, 0, 0, 0 ], [ ] ], + [ [ 0, 2, 3 ], [ 1, 1, 1, 1, 1, 1 ], [ ] ], + [ [ 2, 1, 3 ], [ 0, 0, 0, 0, 0, 0 ], [ ] ], + [ [ 2, 1, 3 ], [ 0, 0, 0, 0, 0, 0 ], [ ] ], + [ [ 3, 1 ], [ 0, 2, 2, 2, 0, 2, 2 ], [ ] ], + [ [ 2, 3, 1 ], [ 0, 0, 0, 0, 0, 0 ], [ ] ], + [ [ 2 ], [ 1, 0, 3, 3, 1, 0, 0, 1 ], [ ] ], + [ [ 0, 1 ], [ 2, 3, 3, 2, 3, 3, 3 ], [ ] ], + [ [ 0, 2, 3 ], [ 1, 1, 1, 1, 1, 1 ], [ ] ], + [ [ 2, 0 ], [ 3, 1, 1, 1, 1, 3, 1 ], [ ] ], + [ [ 1, 3 ], [ 2, 0, 2, 2, 0, 2, 2 ], [ ] ] +], +"sus_weights": [ 0.35, 0.37, 0.38 ], +"order_size": [ 48, 48.479591836735 ], +"passages_size": [ 5, 5 ], +"motif_edited": "false", +"order_edited": "false" +} \ No newline at end of file diff --git a/supercollider/material_tweak.scd b/supercollider/material_tweak.scd index e56785b..dba0f4c 100644 --- a/supercollider/material_tweak.scd +++ b/supercollider/material_tweak.scd @@ -57,33 +57,30 @@ swap = {arg data, swaplist; c = [ [ [ - [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0.625 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 0, 1, 0, 0, 0, 0 ] ], 0 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 1, 0, 0, 0, 0 ] ], 1.75 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, -1, 0 ] ], 1.875 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 0, 0, 1, 0 ] ], 1.5 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], 0.75 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, -1, 0, 0 ] ], 1.25 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ], [ -1, 0, 0, 1, 0, 0 ] ], 1.625 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 1, 0, 0, 0, 0 ] ], 1.875 ] + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 3.875 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 0, 0, 1, 0, 0, 0 ], [ 1, 0, 0, 0, 0, 0 ] ], 4.875 ] ], [ - [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 1, 0, 0, 0, 0 ] ], 1 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 1, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1.125 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 1, -1, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 0.75 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, -1, 0 ], [ 0, 1, 0, 0, 0, 0 ] ], 1.5 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, -1, 0, 0, 0, 1 ], [ 0, 1, 0, 0, 0, 0 ] ], 1.5 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, -1 ], [ 0, 1, 0, 0, 0, 0 ] ], 1.75 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ "Rest" ], [ 0, 1, 0, 0, 0, 0 ] ], 1.125 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0.5 ], - [ [ [ 0, 0, 0, 0, 0, 0 ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 0 ], - [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 1.25 ] + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, -1, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 1, 0, 0, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ], [ 0, 0, 1, 0, 0, 0 ] ], 4.75 ] + ], + [ + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 0, 0, 1, 0, 0, 0 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ 1, -1, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0, -1 ] ], 4.125 ], + [ [ [ 0, 0, 0, 0, 0, 0 ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ "Rest" ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ 1, 0, 0, 0, 0, -1 ] ], 0 ], + [ [ [ "Rest" ], [ 1, 0, -1, 0, 0, 0 ], [ "Rest" ], [ "Rest" ] ], 0 ], + [ [ [ "Rest" ], [ "Rest" ], [ "Rest" ], [ "Rest" ] ], 9.625 ] ] ] ]; //c = c.collect({arg x; x.collect({arg y; y.collect({arg z; [z[0].collect({arg item; if(item == ["Rest"], {item}, {item + [ 1, -1, 0, 0, -1, 0 ] })}), z[1]]})})}); -c = transpose.value(c, [ 1, -1, 0, -1, 1, 0 ]); +c = transpose.value(c, [ 0, 2, -1, 0, 0, 0 ]); //c = swap.value(c, [[2, 3]]); f = File("tweak.txt", "w"); diff --git a/supercollider/morph_quickproto.scd b/supercollider/morph_quickproto.scd new file mode 100644 index 0000000..5aa1e6a --- /dev/null +++ b/supercollider/morph_quickproto.scd @@ -0,0 +1,14 @@ +( +20.do({ + var sus, rep; + rep = rrand(1, 2); + sus = 0; + rep.do({ + ([[sus], [0, 1, 2, 3].removeEvery([sus]).scramble, []].asString + ",").postln + + }) +}) +) + + +[1, 2, 3].removeEvery([1]) \ No newline at end of file diff --git a/supercollider/seeds_and_ledgers_backend.scd b/supercollider/seeds_and_ledgers_backend.scd index c194eef..57e638d 100644 --- a/supercollider/seeds_and_ledgers_backend.scd +++ b/supercollider/seeds_and_ledgers_backend.scd @@ -56,7 +56,7 @@ hsArrayToCents = { pDist = { arg array1, array2, signed = false; var pDistance; - pDistance = hsArrayToCents.value(array1) - hsArrayToCents.value(array2); + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); if(signed, {pDistance}, {abs(pDistance)}) }; @@ -119,7 +119,7 @@ rangeScore = { }; intervalScore = { - arg hsArray1, hsArray2, mean, sd, signed = false; + arg hsArray1, hsArray2, mean, sd, signed = true; var pDistance; pDistance = pDist.value(hsArray1, hsArray2, signed); //pDistance.gaussCurve(1, mean, sd) @@ -188,7 +188,7 @@ genStepFunc = {arg minStep, maxStep, envData, seed; }; genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; - ((maxMotifLength - minMotifLength).rand + minMotifLength).collect({ + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); noProgIns = (popSize - noSusIns).rand + 1; @@ -196,7 +196,7 @@ genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxP # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); - prog = (prog.scramble ++ ((maxProgLength - minProgLength).rand + minProgLength).collect({prog.choose}).scramble); + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); if(silent == nil, {silent = []}); [sus.scramble, prog, silent.scramble] }); diff --git a/supercollider/seeds_and_ledgers_backend_rise.scd b/supercollider/seeds_and_ledgers_backend_rise.scd new file mode 100644 index 0000000..070e09d --- /dev/null +++ b/supercollider/seeds_and_ledgers_backend_rise.scd @@ -0,0 +1,992 @@ +( +// helper funcs +var hsArrayToCents, pDist, hdSum, hsChordalDistance, hsArrayToFreq; + +// score funcs +var isInRange, spacingScore, rangeScore, intervalScore, inclusionScore; + +// subroutines +var genTuples, initVoices, genOrders, genSubMotif, updateVoices, genDurFunc, genStepFunc; + +// primary routines +var genMotif, genSecondarySeq; + +// audition funcs +var genPatterns, genMidiPatterns; + +// resource management funcs +var seedFunc, genUID, writeResources, stringifyToDepth, setSeeds, sanityCheck, +msgInterpret, loadLedgerFile, loadLedgerJSON, loadModelFile, loadModelJSON, +setGlobalVars, globalVarsToDict, saveLedger; + +// model vars +//(model and global vars mostly set by OSC funcs +var seq, lastXChanges, +curUID, refUID, orderSeed, durSeed, motifSeed, +entrancesProbVals, passagesProbVals, exitsProbVals, +ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, +orders, susWeights, orderSize, passagesSize, +motifEdited, orderEdited; + +// model aux vars +var entrancesDurFunc, passagesDurFunc, exitsDurFunc, stepFunc; + +// other global vars +var popSize, exPath, dir, primes, dims, tuples, +group, player, resourceDir, ledgerPath, ledger, currentlyPlayingUID, +nameSpaces; + +// install JSON quark (not used) +/* +if(Quarks.isInstalled("JSONlib").not, { + Quarks.install("https://github.com/musikinformatik/JSONlib.git"); + thisProcess.recompile; + //HelpBrowser.openHelpFor("Classes/JSONlib"); +}); +*/ + + +//------helper funcs + +hsArrayToCents = { + arg hsArray; + hsArray.collect({arg dist, p; dist * 1200 * log2(primes[p][0]/primes[p][1])}).sum +}; + +pDist = { + arg array1, array2, signed = false; + var pDistance; + pDistance = hsArrayToCents.value(array2) - hsArrayToCents.value(array1); + if(signed, {pDistance}, {abs(pDistance)}) +}; + +hdSum = { + arg hsArrays; + var size, distances, mean; + size = hsArrays.size; + distances = (size - 1).collect({arg i; + ((i + 1)..(size - 1)).collect({arg j; + abs(hsArrays[i] - hsArrays[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsChordalDistance = { + arg hsArrays1, hsArrays2; + var size, distances, mean; + size = hsArrays1.size; + distances = hsArrays1.size.collect({arg i; + hsArrays2.size.collect({arg j; + abs(hsArrays1[i] - hsArrays2[j]).collect({arg dist, p; dist * log2(primes[p].product)}).sum + }); + }).flat; + mean = distances.sum / distances.size; + distances.sum + //mean + ((1 / sqrt((pow(distances - mean, 2)).sum / distances.size)) * mean) +}; + +hsArrayToFreq = { + arg array; + array.collect({arg dim, d; pow(primes[d][0]/primes[d][1], dim)}).product +}; + +//------score funcs + +/* +isInRange = { + arg hsArray, min, max; + var cents; + cents = hsArrayToCents.value(hsArray); + (cents >= min) && (cents <= max) +}; +*/ + +spacingScore = { + arg hsArrays, min; + var centsArray; + centsArray = hsArrays.collect({arg hsArray; hsArrayToCents.value(hsArray)}).sort({arg a, b; a < b}); + centsArray.differentiate.drop(1).collect({arg pDistance; if(pDistance >= min, {1}, {0.01})}).sum; +}; + +rangeScore = { + arg hsArray1, hsArray2, min, max, low, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + if((pDistance >= min) && (pDistance <= max), {1}, {low}); +}; + +/* +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = false; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + stepFunc.value(pDistance) +}; +*/ + +intervalScore = { + arg hsArray1, hsArray2, mean, sd, signed = true; + var pDistance; + pDistance = pDist.value(hsArray1, hsArray2, signed); + //pDistance.gaussCurve(1, mean, sd) + //if(pDistance >= 0, {stepFunc.value(abs(pDistance))}, {0.01}); + stepFunc.value(pDistance) +}; + +inclusionScore = { + arg array, test, min = 0.01; + if(array.collect({arg v; v.hash}).includes(test.hash), {min}, {1}); +}; + + +//------subroutines + +genTuples = { + var tuples; + tuples = dims.collect({[-1, 0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum <= 1) && (tuple[0] == 0)}); + //tuples = dims.collect({[0, 1]}).allTuples.select({arg tuple; (abs(tuple.drop(1)).sum == 1) && (tuple[0] == 0)}); + tuples = tuples ++ tuples.collect({arg tuple; [-3, -2, -1, 1, 2, 3].collect({arg octTrans; tuple.deepCopy.put(0, octTrans)})}).flatten; +}; + +initVoices = { + var init, voicesInit; + voicesInit = popSize.collect({dims.collect({0})}); + /* + voicesInit = [dims.collect({0})]; + (popSize - 1).do({ + arg rep, new; + rep = dims.rand; + new = voicesInit.last.deepCopy; + new[rep] = new[rep] + [-1, 1].choose(); + voicesInit = voicesInit.add(new); + }); + */ + voicesInit.deepCopy; +}; + +genDurFunc = {arg chordProb, minPad, maxPad, minDur, maxDur, envData, seed; + var env, pTable, durFunc; + env = Env.pairs([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).asSignal(256).asList.asArray; + pTable = env.asRandomTable; + //[chordProb, minPad, maxPad, minDur, maxDur, envData].postln; + durFunc = {arg allowChord, pad = false; + var res; + res = if(allowChord.not, { + pTable.tableRand * (maxDur - minDur) + minDur + }, { + if(1.0.rand < chordProb, {0}, {pTable.tableRand * (maxDur - minDur) + minDur}); + }).round(0.125); + if(pad, {res = res + rrand(minPad.asFloat, maxPad.asFloat).round(0.125)}); + if(res.asInteger == res, {res = res.asInteger}); + res + }; + seedFunc.value(durFunc, seed); +}; + +genStepFunc = {arg minStep, maxStep, envData, seed; + var envDataNorm, env, pTable, stepFunc; + //[minStep, maxStep, envData].postln; + envDataNorm = ([[0, 0]] ++ envData.clump(2) ++ [[1, 0]]).flop; + envDataNorm = [envDataNorm[0].normalize(minStep, maxStep), envDataNorm[1]].flop; + env = Env.pairs(envDataNorm); + stepFunc = {arg pDist; + env.at(pDist).clip(0.001, 1); + }; + seedFunc.value(stepFunc, seed); +}; + +genOrders = {arg minMotifLength = 1, maxMotifLength = 5, minProgLength = 0, maxProgLength = 5; + ((maxMotifLength.asInteger - minMotifLength.asInteger).rand + minMotifLength.asInteger).collect({ + var noProgIns, noSusIns, noSilentIns, prog, sus, silent, order; + noSusIns = [1, 2, 3].wchoose(susWeights.normalizeSum); + + //noProgIns = (popSize - noSusIns).rand + 1; + noProgIns = (popSize - noSusIns); + noSilentIns = popSize - noSusIns - noProgIns; + + /* + noSilentIns = (popSize - noSusIns).rand.clip(0, 1); + noProgIns = popSize - noSusIns - noSilentIns; + */ + + # prog, sus, silent = (0..(popSize-1)).scramble.clumps([noProgIns, noSusIns, noSilentIns]); + + prog = (prog.scramble ++ ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}).scramble); + //prog = ((maxProgLength.asInteger - minProgLength.asInteger).rand + minProgLength.asInteger).collect({prog.choose}); + + if(silent == nil, {silent = []}); + [sus.scramble, prog, silent.scramble] + }); +}; + +updateVoices = {arg ins, sus; + var voices, candidates, nWeights, nProbs, sel; + + voices = lastXChanges.deepCopy.last; + + candidates = sus.collect({arg v; tuples.collect({arg t; voices[v] + t})}).flatten; + candidates = difference(candidates.asSet, voices.asSet).asList; + //candidates.select({arg item; (item ++ voices).asSet.size >= 4}); + nProbs = candidates.collect({arg candidate; + var stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore; + + //stepScore = intervalScore.value(voices[ins], candidate, 30, 400, 0.1); + stepScore = intervalScore.value(voices[ins], candidate, 100, 100); + recentlySoundedScore = inclusionScore.value(lastXChanges.flop[ins], candidate, 0.01); + //recentlySoundedScore = inclusionScore.value(lastXChanges.flatten.collect({arg item; item.drop(1)}), candidate.drop(1), 0.01); + + /* + if(rangeScore.value(candidate.collect({0}), voices[ins], ranges[ins][1] - 500, ranges[ins][1], 0, true) == 1, { + isInRangeScore = 1 - rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0] + 500, ranges[ins][1] - 500, 0, true); + isInRangeScore = isInRangeScore * rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true) + }, { + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + }); + */ + + + isInRangeScore = rangeScore.value(candidate.collect({0}), candidate, ranges[ins][0], ranges[ins][1], 0, true); + //old way - worked but actually by accident (I think) + //isInRangeScore = rangeScore.value(candidate, candidate.collect({0}), ranges[ins][0], ranges[ins][1], 0, true); + + + regScore = spacingScore.value(voices.deepCopy.put(ins, candidate), 300); + hdScore = pow(hdSum.value(voices.deepCopy.put(ins, candidate)), hdExp); + if(hdInvert == 0, {hdScore = 1/hdScore}); + //maybe what you want here is a vector to another root and then favoring movement towards it. + //distScore = pow(hsChordalDistance.value(voices, voices.put(ins, candidate)), 2); + + [stepScore, recentlySoundedScore, isInRangeScore, regScore, hdScore] + }); + + nWeights = passagesWeights; + + //this handles nWeights of 0; mainly for testing + nProbs = nProbs.flop.select({arg scores, s; nWeights[s] != 0}).flop; + nWeights = nWeights.select({arg weight; weight != 0}); + nProbs = nProbs.flop.collect({arg scores, s; + if(scores.sum == 0, {scores}, {scores.normalizeSum * nWeights[s]}).postln; + }); + nProbs = nProbs.flop.collect({arg scores, s; scores.product}).normalizeSum; + + nProbs.round(0.001).postln; + [candidates, nProbs.round(0.001)].flop.postln; + + sel = candidates.wchoose(nProbs); + + voices[ins] = sel; + lastXChanges = lastXChanges.add(voices).keep(-5); +}; + +genSubMotif = {arg order, orderIndex, lastState, repeatLast = false, startFromLast = false, isLastOrder = false; + var sus, prog, silent, flatOrder, res, isInChord, allowChord, pad, lastXChangesHold, voices, adder; + # sus, prog, silent = order; + flatOrder = silent ++ sus ++ prog; + lastXChangesHold = lastXChanges.deepCopy; + voices = lastState.deepCopy; + isInChord = popSize.collect({false}); + allowChord = false; + pad = false; + res = []; + "------generating motif".postln; + //need to figure out here if voices move between motifs + flatOrder.do({arg ins, i; + + if(prog.includes(ins) && repeatLast.not, {updateVoices.value(ins, sus)}); + adder = if(silent.includes(ins), {["Rest"]}, {lastXChanges.last.deepCopy[ins]}); + + if(voices[ins] != adder, { + var dur; + + if((sus ++ silent).includes(ins), { + allowChord = (ins != sus.last); + pad = (ins == sus.last); + }, { + if(i < (flatOrder.size - 1), { + allowChord = (isInChord[flatOrder[i + 1]] || (ins == flatOrder[i + 1])).not; + pad = false; + }, { + allowChord = false; + pad = true + }); + }); + if((orderIndex == 0) && sus.includes(ins), { + dur = entrancesDurFunc.value(allowChord, pad); + }, { + dur = passagesDurFunc.value(allowChord, pad); + }); + if(dur == 0, {isInChord[ins] = true}, {isInChord = popSize.collect({false})}); + + voices[ins] = adder; + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + + // pad ending + /* + if(orderIndex == (orders.size - 1), { + (0..(popSize-1)).scramble.do({arg ins; + if(res.last.first[ins] != ["Rest"], { + var dur; + voices[ins] = ["Rest"]; + allowChord = (voices != popSize.collect({["Rest"]})); + pad = allowChord.not; + dur = exitsDurFunc.value(allowChord, pad); + res = res.add([voices.deepCopy.postln, dur]); + }); + }); + }); + */ + + //format and return + if(startFromLast, {lastXChanges = lastXChangesHold.deepCopy}); + res; +}; + + +//------primary routines + +genMotif = { + var repeats, fSeq, fDur, durAdd; + + repeats = 1; + fSeq = []; + + repeats.do({arg index; + var motif; + + motif = []; + + orders.do({arg order, o; + var lastState, subMotif; + //lastState = if(o == 0, {popSize.collect({["Rest"]})}, {motif.last.last.first}); + lastState = if(o == 0, {lastXChanges.last.deepCopy}, {motif.last.last.first}); + subMotif = genSubMotif.value(order, o, lastState, isLastOrder: o == (orders.size - 1)); + motif = motif.add(subMotif); + + }); + + sanityCheck.value(motif, index); + + fSeq = fSeq.add(motif); + }); + + //round last duration to measure + /* + fDur = fSeq.flatten.flatten.slice(nil, 1).sum; + durAdd = fDur.round(4) - fDur; + if(durAdd < 0, {durAdd = 4 - durAdd}); + fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] = fSeq[0][orders.size - 1][fSeq[0][orders.size - 1].size - 1][1] + durAdd; + */ + + fSeq +}; + +genSecondarySeq = {arg seq; + var curdles, fSeq; + curdles = []; + while({curdles.sum < seq.size}, {curdles = curdles ++ [3.rand + 1]}); + + fSeq = seq.clumps(curdles).collect({arg clump, m; + var repeats, paddedSeq; + + //add rest + paddedSeq = clump.add([[[popSize.collect({["Rest"]}), 0.5.rand]]]); + + //implement repeats + repeats = [0.rand + 1, 1].wchoose([1, 0].normalizeSum); + repeats.collect({paddedSeq}); + }); + fSeq +}; + + +//------audition funcs + +/* +Event.addEventType(\osc, { + if (~addr.postln.notNil) { + ~addr.sendMsg(~indexPath, ~indexMsg); + ~addr.sendMsg(~seqPath, stringifyToDepth.value(~seqMsg, 3)); + //~addr.sendMsg("/STATE/OPEN", (dir.replace("supercollider", "resources") +/+ ~idMsg +/+ ~idMsg ++ "_gui_state" ++ ".state").standardizePath.postln); + }; +}); +*/ + +Event.addEventType(\osc, { + if (~addr.notNil) { + ~msg; + ~addr.sendMsg(~path, *~msg); + }; +}); + +genPatterns = {arg inSeq, addr, oneShot = false; + var voices, durs, pbinds, res, indices, sectionDurs, msg, ids, seq; + seq = inSeq.collect({arg mSeq; mSeq[0]}); + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(if(oneShot, {2}, {3})).flop; + pbinds = voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs, attacks, rels, amps; + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(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(2).flop[1].sum}); + pbinds = pbinds ++ + [ + Pbind( + \type, \osc, + \addr, addr, + \path, "/playing", + \msg, Pseq(msg, 1), + \dur, Pseq(sectionDurs, 1) + ); + ] + }); + res = Ppar(pbinds); + res +}; + +/* +genMidiPatterns = {arg seq; + var voices, durs, patterns, res, mOut, pbRange; + pbRange = 1; //semitones - change this as needed for your situation + mOut = MIDIOut.newByName("TiMidity", "TiMidity port 0").latency_(Server.default.latency); + # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + res = Ppar( + voices.flop.collect({arg voice, v; + var clumps, hdScores, freqs, fDurs; + + mOut.program(v, 70); + + clumps = voice.separate({arg a, b; a != b }); + freqs = clumps.collect({arg clump; if(clump[0] != ["Rest"], {(60.midicps * hsArrayToFreq.value(clump[0]))}, {Rest(0)})}); + fDurs = durs.clumps(clumps.collect({arg clump; clump.size})).collect({arg clump; clump.sum}); + + Pbind( + \type, \midi, + \chan, v, + \noteval, Pseq(freqs.cpsmidi - 24, 1), + \note, Pfunc({ | event | event[\noteval].floor }), + \dur, Pseq(fDurs, 1), + \midiout, mOut, + \amp, 1, + \bend, Pfunc({ + | event | + if (event[\note].isRest.not) { + var pitchbendvalue = event[\noteval].frac.linlin(0, pbRange, 8192, 8192*2).asInteger; + m.bend(v, pitchbendvalue); + }; + 0; // return something other than nil to avoid stopping the pattern + }), + ); + }); + ); + res +}; +*/ + + +//------resource management funcs + +genUID = {Date.seed.asHexString.toLower}; + +seedFunc = {arg func, seed; + var funcArgs, next; + next = Routine({loop{func.valueArray(funcArgs).yield }}); + next.randSeed_(seed); + {arg ...args; funcArgs = args; next.value} +}; + +stringifyToDepth = {arg data, maxDepth = 1; + var prettyString = "", rCount = 0, writeArray, indent; + + if(maxDepth == 0, { + data.asCompileString + }, { + indent = {arg size; size.collect({" "}).join("")}; + writeArray = {arg array; + prettyString = prettyString ++ indent.value(rCount) ++ "[\n"; + rCount = rCount + 1; + if(rCount < maxDepth, { + array.do({arg subArray; writeArray.value(subArray)}); + }, { + prettyString = prettyString ++ array.collect({arg subArray; + indent.value(rCount + 1) ++ subArray.asCompileString + }).join(",\n"); + }); + rCount = rCount - 1; + prettyString = prettyString ++ "\n" ++ indent.value(rCount) ++ "],\n"; + }; + + writeArray.value(data); + prettyString.replace(",\n\n", "\n").drop(-2); + }) +}; + +sanityCheck = {arg motif, index; + //print functions = very helpful + ("----------" + index + "------------").postln; + + motif.flatten.do({arg val, v; + if(v > 0, { + if(motif.flatten[v-1][0].hammingDistance(val[0]) > 1, {"problem 1".postln}); + if(motif.flatten[v-1][0].hammingDistance(val[0]) == 0, {"problem 2".postln}); + }); + val.postln + }); + "***********".postln; +}; + +msgInterpret = {arg in, escapeDoubleQuotes = true, escapeSingleQuotes = true; + var res; + + res = in; + if(res.isNil.not, { + if((res.isArray && res.isString.not), { + res = res.asCompileString; + res = res.replace(" ", "").replace("\n", "").replace("\t", ""); + if(escapeSingleQuotes, {res = res.replace("\'", "")}); + if(escapeDoubleQuotes, {res = res.replace("\"", "")}); + res = res.replace("Rest", "\"Rest\""); + res = res.interpret; + }, { + var tmpRes; + if(res.every({arg char; char.isDecDigit}), {tmpRes = res.asInteger}); + if(res.contains("."), {tmpRes = res.asFloat}); + if(tmpRes != nil, {res = tmpRes}); + }); + }); + res +}; + +writeResources = {arg path, dict; + var file, modelItems, resString; + file = File(path,"w"); + + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + + resString = nameSpaces.collect({arg nameSpace; + var depth = 0, insert = " "; + if(nameSpace == "music_data", {depth = 3; insert = "\n"}); + if(nameSpace == "last_changes", {depth = 1; insert = "\n"}); + if(nameSpace == "order", {depth = 1; insert = "\n"}); + if((nameSpace == "ref_uid") && (dict[nameSpace] == nil), {dict[nameSpace] = "nil"}); + "\"" ++ nameSpace ++ "\":" ++ insert ++ stringifyToDepth.value(dict[nameSpace], depth) + }).join(",\n"); + + resString = "{\n" ++ resString ++ "\n}"; + + file.write(resString); + file.close; + resString +}; + +loadModelFile = {arg path; loadModelJSON.value(File(path, "r").readAllString.parseJSON)}; + +loadModelJSON = {arg jsonObject; + var dict; + dict = Dictionary.with(*nameSpaces.collect({arg nS; nS->msgInterpret.value(jsonObject[nS])})); + dict +}; + +setGlobalVars = {arg dict, skipLastXChanges = false; + var tmpLastXChanges; + tmpLastXChanges = lastXChanges.deepCopy; + // order really matters!!!! + # seq, lastXChanges, curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited = nameSpaces.collect({arg nS; dict[nS]}); + if(skipLastXChanges, {lastXChanges = tmpLastXChanges}); + dict +}; + +globalVarsToDict = { + var modelItems, dict; + // order really matters!!!! + modelItems = [ + seq, lastXChanges, + curUID, refUID, orderSeed, durSeed, motifSeed, + entrancesProbVals, passagesProbVals, exitsProbVals, + ranges, stepProbsVals, passagesWeights, hdExp, hdInvert, + orders, susWeights, orderSize, passagesSize, + motifEdited, orderEdited + ]; + dict = Dictionary.with(*nameSpaces.collect({arg nS, n; nS->modelItems[n]})); +}; + +loadLedgerFile = {arg path; + ledgerPath = path; + resourceDir = path.splitext(".").drop(-1).join; + loadLedgerJSON.value(File(ledgerPath, "r").readAllString.parseJSON) +}; + +loadLedgerJSON = {arg jsonObject; ledger = jsonObject["ledger"]}; + +saveLedger = {arg ledger, path; + var file, curResourceDir; + file = File(path, "w"); + curResourceDir = resourceDir; + resourceDir = path.splitext(".").drop(-1).join; + if(curResourceDir != resourceDir, { + File.mkdir(resourceDir); + ledger.do({arg id; + File.copy(curResourceDir +/+ id, resourceDir +/+ id); + }); + }); + file.write("{\n\"ledger\":\n" ++ stringifyToDepth.value(ledger, 1) ++ "\n}"); + file.close; +}; + +//------global vars + +primes = [[2, 1], [3, 2], [5, 4], [7, 4], [11, 8], [13, 8]]; +//ranges = [[-2400, 0], [-1200, 1200], [0, 2400], [0, 2400]]; +exPath = thisProcess.nowExecutingPath; +dir = exPath.dirname; +//popSize = 4; +dims = primes.size; +tuples = genTuples.value(); +//refUID = nil; +group = Group.new; +~group = group; +loadLedgerFile.value(dir +/+ ".." +/+ "resources" +/+ "piece_ledger.json"); +resourceDir = (dir +/+ ".." +/+ "resources" +/+ "piece_ledger"); +//passagesWeights = [1, 1, 1, 1, 1]; +//susWeights = [1, 1, 1]; +// order really matters!!!! +nameSpaces = [ + "music_data", "last_changes", "cur_uid", "ref_uid", "order_seed", "dur_seed", "motifs_seed", + "entrances_probs_vals","passages_probs_vals", "exits_probs_vals", + "ranges", "step_probs_vals", "passages_weights", "hd_exp", "hd_invert", + "order", "sus_weights", "order_size", "passages_size", + "motif_edited", "order_edited" +]; + + +//------OSC funcs + +OSCdef(\load_ledger, {arg msg, time, addr, port; + loadLedgerFile.value(msg[1].asString); +}, \load_ledger); + +OSCdef(\load_model, {arg msg, time, addr, port; + var dict; + dict = loadModelFile.value(msg[1].asString); + setGlobalVars.value(dict); +}, \load_model); + +OSCdef(\save_ledger, {arg msg, time, addr, port; + msg.postln; + ledger = msgInterpret.value(msg[1].asString.parseJSON["ledger"], false).postln; + //loadLedgerJSON.value(msg[0]) + saveLedger.value(ledger, msg[2].asString); + //loadLedgerFile.value(msg[1].asString); +}, \save_ledger); + +OSCdef(\generate, {arg msg, time, addr, port; + var path, dict, durSeeds, musPath, modelString; + msg.postln; + + path = msg[1].asString; + + dict = loadModelFile.value(path); + setGlobalVars.value(dict, true); + + popSize = ranges.size; + + //refUID.postln; + + loadLedgerFile.value(ledgerPath); + if(ledger == nil, {ledger = ["tmp"]}); + if(ledger.last != "tmp", {ledger = ledger.add("tmp")}); + + if(refUID == nil, {lastXChanges = [initVoices.value().deepCopy]}); + if((refUID != nil) && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + lastXChanges = msgInterpret.value(file.readAllString.parseJSON["last_changes"]); + }); + + refUID.postln; + lastXChanges.collect({arg item; item.postln}); + + durSeeds = seedFunc.value({3.collect({rrand(100000, 999999)})}, durSeed).value.postln; + entrancesDurFunc = genDurFunc.valueArray(entrancesProbVals[..4] ++ [entrancesProbVals[5..]] ++ [durSeeds[0]]); + passagesDurFunc = genDurFunc.valueArray(passagesProbVals[..4] ++ [passagesProbVals[5..]] ++ [durSeeds[1]]); + exitsDurFunc = genDurFunc.valueArray(exitsProbVals[..4] ++ [exitsProbVals[5..]] ++ [durSeeds[2]]); + + if(orders == nil, { + orders = seedFunc.value(genOrders, orderSeed).valueArray(orderSize ++ passagesSize); + //addr.sendMsg("/order", stringifyToDepth.value(orders, 1)); + }); + + stepFunc = genStepFunc.valueArray(stepProbsVals[..1] ++ [stepProbsVals[2..]] ++ [motifSeed]); + seq = seedFunc.value(genMotif, motifSeed).value; + + lastXChanges.collect({arg item; item.postln}); + + dict = globalVarsToDict.value; + modelString = writeResources.value(path, dict); + + //addr.sendMsg("/generated", musPath, stringifyToDepth.value(seq, 3)); + //~seq = seq; + + addr.sendMsg("/generated", path, modelString, ledgerPath); +}, \generate); + + +OSCdef(\commit, {arg msg, time, addr, port; + var musicData, musicChanged, dict, newLedger, modelPath, musString, musFile, test1, test2, lastCurUID, commitType, commitPos, equalityLedger; + //msg.postln; + + /* + test1 = msg[1].asString.parseJSON; + test2 = (dir +/+ ".." +/+ "resources/tmp/tmp_music" ++ ".json").standardizePath.parseJSONFile; + msgInterpret.value(test1["music"])[0][0][0][1].class.postln; + msgInterpret.value(test2["music_data"])[0][0][0][1].class.postln; + (test1["music"] == test2["music_data"]).postln; + */ + + musicData = loadModelJSON.value(msg[1].asString.parseJSON)["music_data"].postln; + musicChanged = (musicData != seq).postln; + commitType = msg[2].asString; + commitPos = msg[3].postln.asInteger; + + lastCurUID = curUID.deepCopy; + curUID = genUID.value; + + File.mkdir((resourceDir +/+ curUID).standardizePath); + File.copy(exPath, (resourceDir +/+ curUID +/+ curUID ++ "_code" ++ ".scd").standardizePath); + + modelPath = (resourceDir +/+ curUID +/+ curUID ++ "_mus_model" ++ ".json").standardizePath; + dict = globalVarsToDict.value; + if(musicChanged, { + seq = musicData; + dict["music_data"] = seq; + dict["motif_edited"] = "true" + }); + dict["cur_uid"] = curUID; + + writeResources.value(modelPath, dict); + + File.delete(ledgerPath ++ "_bak"); + File.copy(ledgerPath, ledgerPath ++ "_bak"); + File.delete(ledgerPath); + + /* + if(commitType == "add", { + if(lastCurUID == "tmp", { + ledger = ledger.drop(-1).add(curUID); + }, { + ledger = ledger.add(curUID); + }) + }); + */ + + ledger.postln; + + if(commitType == "add", {ledger = ledger.add(curUID)}); + + if(commitType == "insert", {ledger = ledger.insert(commitPos + 1, curUID)}); + + if(commitType == "replace", {ledger = ledger.put(commitPos, curUID)}); + + equalityLedger = ledger.collect({arg item; item.asSymbol}); + if(equalityLedger.includes(\tmp).postln, {ledger.removeAt(equalityLedger.indexOf(\tmp).postln)}); + + ledger.postln; + + saveLedger.value(ledger, ledgerPath); + + addr.sendMsg("/committed", curUID, ledgerPath); + //refUID = curUID; + +}, \commit); + +OSCdef(\transport, {arg msg, time, addr, port; + msg.postln; + if(msg[1] == 0, { + group.set(\release, 2); + group.set(\gate, 0); + player.stop; + }, { + // the cued sequence can now be read from file, so this can be cleaned up + var cSize, patterns, pSeq, cuedSeek, indexStart, indexEnd, tmpLedger; + if(msg[1] == 1, { + pSeq = []; + cuedSeek = (seq != nil); + indexStart = msg[2].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.postln.parseJSON["music_data"]), path, indexStart + index, uid]); + file.close; + }); + }); + if(cuedSeek, { + var path, file; + path = (resourceDir +/+ "tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + patterns = genPatterns.value(pSeq, addr); + }, { + pSeq = [loadModelJSON.value(msg[2].asString.parseJSON)["music_data"].postln]; + patterns = genPatterns.value(pSeq, addr, true); + }); + player = Pfset(pattern: patterns, cleanupFunc: { + addr.sendMsg("/transport", 0); + addr.sendMsg("/one_shot", 0); + }); + player = player.play + }); +}, \transport); + + +OSCdef(\transcribe_motif, {arg msg, time, addr, port; + var tSeq, refChord, refUID; + + msg.postln; + + tSeq = [loadModelJSON.value(msg[1].asString.parseJSON)["music_data"]]; + refUID = msg[2].asString.postln; + + if((refUID != "nil") && (refUID != "tmp"), { + var file; + file = File((resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }, { + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + }); + + ~transcribe.value(tSeq, refChord, (dir +/+ ".." +/+ "lilypond" +/+ "includes").standardizePath, addr, "/transcribe_motif"); +}, \transcribe_motif); + + +OSCdef(\transcribe_all, {arg msg, time, addr, port; + var cSize, patterns, cuedSeek, indexStart, indexEnd, tmpLedger; + if(true, { + cuedSeek = (seq != nil); + indexStart = msg[1].asInteger; + indexEnd = ledger.size - if(cuedSeek, {2}, {1}); + + //tmp for testing transcription + //indexEnd = (indexStart+5); + + //ledger.postln; + if(((indexStart == (ledger.size - 1)) && cuedSeek).not, { + var lilyPartLedgerFiles; + + lilyPartLedgerFiles = 4.collect({arg p; + File((dir +/+ ".." +/+ "lilypond" +/+ "includes" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly").standardizePath, "w"); + }); + + ledger[indexStart..indexEnd].do({arg uid, index; + var path, file, fileString, tSeq, refUID, refChord; + path = (resourceDir +/+ uid +/+ uid ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + fileString = file.readAllString; + tSeq = msgInterpret.value(fileString.parseJSON["music_data"]); + refUID = msgInterpret.value(fileString.parseJSON["ref_uid"]); + file.close; + + //uid.postln; + //(refUID == "nil").postln; + + refChord = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]; + + if(refUID != "nil", { + path = (resourceDir +/+ refUID +/+ refUID ++ "_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + refChord = msgInterpret.value(file.readAllString.parseJSON["last_changes"]).last; + file.close; + }); + + if(index != indexEnd, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath); + }, { + ~transcribe.value(tSeq, refChord, (resourceDir +/+ uid +/+ "lilypond").standardizePath, addr, "/transcribe_all"); + }); + + lilyPartLedgerFiles.do({arg f, p; + f.write("\\include \"" ++ resourceDir +/+ uid +/+ "lilypond" +/+ "part_" ++ ["IV", "III", "II", "I"][p] ++ ".ly\"\n"); + }); + + }); + + lilyPartLedgerFiles.do({arg f; + f.close + }); + }); + /* + if(cuedSeek, { + var path, file; + path = (dir +/+ ".." +/+ "resources/tmp/tmp_mus_model" ++ ".json").standardizePath; + file = File(path, "r"); + pSeq = pSeq.add([msgInterpret.value(file.readAllString.parseJSON["music_data"]), path, ledger.size - 1, "tmp"]); + file.close; + }); + */ + }, { + + }); + +}, \transcribe_all); + +) + + diff --git a/supercollider/seeds_and_ledgers_main.scd b/supercollider/seeds_and_ledgers_main.scd index b6ea2cf..a16ad4c 100644 --- a/supercollider/seeds_and_ledgers_main.scd +++ b/supercollider/seeds_and_ledgers_main.scd @@ -12,7 +12,8 @@ s.waitForBoot({ c = Condition.new; // load all files - "seeds_and_ledgers_backend.scd".loadRelative; + //"seeds_and_ledgers_backend.scd".loadRelative; + "seeds_and_ledgers_backend_rise.scd".loadRelative; "seeds_and_ledgers_transcriber.scd".loadRelative; "seeds_and_ledgers_synthdefs.scd".loadRelative; s.sync(c); diff --git a/supercollider/seeds_and_ledgers_transcriber.scd b/supercollider/seeds_and_ledgers_transcriber.scd index 45176b0..a28840d 100644 --- a/supercollider/seeds_and_ledgers_transcriber.scd +++ b/supercollider/seeds_and_ledgers_transcriber.scd @@ -44,7 +44,9 @@ hsArrayDimDiff = { formatMusicData = {arg seq, refChord; var maxSize, voices, durs, baseData, musicData; maxSize = 0; - # voices, durs = seq.flatten2(seq.maxDepth - 5).flop; + # voices, durs = seq.flatten2(2).flop; + //# voices, durs = seq.flatten2(3).flop; + //# voices, durs = seq.flatten2(seq.maxDepth - 5).flop; baseData = voices.flop.collect({arg voice, v; var isFirstNote, clumps, hdScores, freqs, fDurs, refs;