mirror of
https://codeberg.org/uzu/strudel
synced 2026-09-13 09:25:11 -04:00
Compare commits
1812 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 185abf1fc1 | |||
| 37efdd9ce1 | |||
| f4039d3666 | |||
| 7000a4cead | |||
| 5aa983b45b | |||
| 800989419b | |||
| 362679954a | |||
| 2732d64beb | |||
| 88684d6d9e | |||
| 9afddf0850 | |||
| a2d7d6e81f | |||
| 49bcb5e19f | |||
| 3744ea6218 | |||
| b2c6d87633 | |||
| e4f538b674 | |||
| ea0e0b4396 | |||
| 1ac784dc7a | |||
| d980c9ca4c | |||
| c0d6e36113 | |||
| 6a2a60df0d | |||
| ee9b89b6d5 | |||
| 8261802078 | |||
| 68cc109933 | |||
| 1015bb0fdc | |||
| 7da94dd109 | |||
| 171808cf2c | |||
| 78d14cdfea | |||
| 19982a2ffb | |||
| 79904560fc | |||
| 28aee1c58d | |||
| d4512f360b | |||
| 48d0ffe868 | |||
| 62af12f3b9 | |||
| efa4bfef2f | |||
| fc3ead4c01 | |||
| ca9c813b40 | |||
| e3b0f47c40 | |||
| bf13e42092 | |||
| 4345fda9bc | |||
| 4059e9faa9 | |||
| 95fcce1bcf | |||
| 86cde9716a | |||
| 6e315a50ed | |||
| 6dda6bac66 | |||
| 500c0b6534 | |||
| db75dca118 | |||
| d5ab1a3471 | |||
| d5f37e2cbe | |||
| a120a9895b | |||
| e9c1db020f | |||
| ded90733f6 | |||
| c64485db99 | |||
| 08cd319a0b | |||
| 896ec69329 | |||
| afa5d6e704 | |||
| bde7e79a38 | |||
| 32e64bbb6e | |||
| 0974540e5f | |||
| e06963e616 | |||
| a675815529 | |||
| 3d48135b32 | |||
| 8f09e7f097 | |||
| e8b09f1124 | |||
| 57ba353594 | |||
| b695e090cd | |||
| 69ecb7b54f | |||
| f57fe182ed | |||
| 10ee11c886 | |||
| 45842b1f05 | |||
| 9ca5f9ad48 | |||
| bb00036a30 | |||
| 4a2a540500 | |||
| 09217216dc | |||
| 22a64c4931 | |||
| 93f520b941 | |||
| c84714dc3a | |||
| a7aa09a13e | |||
| efa1cdfcb2 | |||
| 70695daae8 | |||
| 62fdba0600 | |||
| 5733e00908 | |||
| e56abaefd7 | |||
| c71e893e8e | |||
| 3992356517 | |||
| dbb84ef129 | |||
| 8781fa2e33 | |||
| 605a1365d3 | |||
| ebb1bbc966 | |||
| d4518f7a6e | |||
| 68c9008019 | |||
| cafde1e910 | |||
| 41f5f3fe6e | |||
| cf12ecfa34 | |||
| 4fa668927f | |||
| 24d2b5b4bd | |||
| 4da2e003e1 | |||
| 205debde9c | |||
| ba6c7b0901 | |||
| 8e5d02fd25 | |||
| 11d3251c67 | |||
| 9ec5787b4d | |||
| 027c669ad7 | |||
| 405c12ba3f | |||
| 64fd53a8e5 | |||
| 069d63e3cf | |||
| 66049e0e25 | |||
| db7ef01dbb | |||
| 1a6b649fd6 | |||
| 548619775e | |||
| 4b79a75f39 | |||
| 28912fff97 | |||
| 98a83ddcc0 | |||
| e9d965ce7a | |||
| b7624813cc | |||
| 734e451bd9 | |||
| 7a96436085 | |||
| 670af8fb1b | |||
| 0abd062e5c | |||
| 1286468d5a | |||
| 0f3da5be4a | |||
| 9170269e06 | |||
| 8bb460701f | |||
| c0a7173ca4 | |||
| 949e15d8c6 | |||
| 0caedeb481 | |||
| 83ca8d95be | |||
| 8483868783 | |||
| 6961388545 | |||
| 8ef1ae5202 | |||
| 26b8f93cea | |||
| a576523976 | |||
| cf21c4085b | |||
| 204f655d6d | |||
| fb14865785 | |||
| 5cd052cc59 | |||
| af49c4117c | |||
| dd736130a0 | |||
| cee8bda676 | |||
| 6420a72ceb | |||
| 380e7c7899 | |||
| 16b4058a8b | |||
| e3fba0257f | |||
| cb8b65f6f3 | |||
| 3944a8ed54 | |||
| 2ceafe6379 | |||
| 9f058bd88a | |||
| 1dd0ff13d8 | |||
| 6061f10230 | |||
| ea59fafb52 | |||
| 2ff71126de | |||
| 6d522e1743 | |||
| b37f3c819d | |||
| 1b41994e5f | |||
| 4fd4b0d713 | |||
| 7b43ed702e | |||
| 56cd77942b | |||
| 37dc723b4b | |||
| d7ea37c917 | |||
| c1d2bf9b9f | |||
| 36f837730a | |||
| b8f0a1dd82 | |||
| 2d1b62a978 | |||
| 0792d0d59d | |||
| 8c6da1a658 | |||
| 26be10f937 | |||
| ef4d9a13ed | |||
| 320d987ede | |||
| 51a4c4d13b | |||
| aaf3ae5f9c | |||
| 858f4f74b3 | |||
| 3feed90039 | |||
| b46330fc7a | |||
| 5ca7ce1f75 | |||
| d1d9b37ec7 | |||
| 823115c4ec | |||
| 8fe3fda27f | |||
| 63fffd7493 | |||
| 243a350e2b | |||
| 8c09e7ba36 | |||
| d89502f91f | |||
| 847602f933 | |||
| 8fcf8fe39c | |||
| d59eec9296 | |||
| b9122ebc34 | |||
| 38e066ee49 | |||
| 232a04a1a2 | |||
| dcb145635c | |||
| c9b73c68a6 | |||
| 08f7925ea9 | |||
| ed79ec7940 | |||
| 869a44dd04 | |||
| 6a9e9ebc4c | |||
| f18a2a11fa | |||
| 9aaaa755c7 | |||
| 818cd9044b | |||
| 006ca12b6d | |||
| b5f9ab7c20 | |||
| 0981f95f16 | |||
| f64ef32eaa | |||
| 7a19659c9e | |||
| b1202fcd55 | |||
| b8f991eddb | |||
| d0013f55a2 | |||
| 7efaff4f6f | |||
| bc7a84e462 | |||
| 61657bd1b1 | |||
| fc63f4f89c | |||
| 2c44c252d7 | |||
| 613fc6e3ef | |||
| 0a9604d10e | |||
| 1cabedc65e | |||
| 4e5a0d9e02 | |||
| bf373d894d | |||
| e6bbad84c1 | |||
| 4de477d46c | |||
| 264cab3c9b | |||
| 7f9ef59bea | |||
| 8ebbef4cbb | |||
| 0c98a84242 | |||
| 2bbdd21b2f | |||
| 794cfe949b | |||
| 6a5010a2d6 | |||
| 61559fee9d | |||
| 2118a20408 | |||
| b0bc20cb83 | |||
| 2cd77b075b | |||
| 03450c6085 | |||
| 5d3c522da2 | |||
| bf1ba6efc0 | |||
| 67ad82d52e | |||
| f437d976f8 | |||
| c94443faaa | |||
| c6d57f6a56 | |||
| 09b06da403 | |||
| df73ce8a60 | |||
| 8b22c2e04f | |||
| b4f4d6cea2 | |||
| cdf7b2acf1 | |||
| 94651731a5 | |||
| 56424d29a3 | |||
| c5083b2aef | |||
| 1b06795ffe | |||
| 6679cd60ce | |||
| 843a3c16ba | |||
| df40204a57 | |||
| 210910eb6e | |||
| ef427758da | |||
| def6bbf683 | |||
| 5f37a4a21c | |||
| dc869987f9 | |||
| b98d2201e5 | |||
| 8dd6440bb8 | |||
| a2d83c48eb | |||
| a0ae8f4a07 | |||
| 719dcbcd4d | |||
| caa207e868 | |||
| 306004ecd2 | |||
| bcac3c5b84 | |||
| ccc90108df | |||
| b9781915a4 | |||
| 1890e65b75 | |||
| 2f4da3ed52 | |||
| 27a6ac0c0b | |||
| b81a66f07e | |||
| bc43fc9585 | |||
| 4f7c013c78 | |||
| 09b15a07c6 | |||
| 4340f024d3 | |||
| 98e9780819 | |||
| a45d63595f | |||
| 5fbd1453b4 | |||
| f827201ee0 | |||
| 485a96962a | |||
| 98a104f731 | |||
| ff987f14d7 | |||
| 4dd08268dc | |||
| c18a13d4fc | |||
| 7a79995727 | |||
| 01eca562c5 | |||
| f491d0c0c4 | |||
| edf2287e13 | |||
| fcaab633c1 | |||
| 33b6579b5d | |||
| 72a8e4bc22 | |||
| c67b02b7b0 | |||
| 486cd00384 | |||
| caea7bb39d | |||
| e8fb7ee683 | |||
| e4ee4211c8 | |||
| 3fa3ebee14 | |||
| 8e531f400e | |||
| 5727c0dc8a | |||
| f599088cdf | |||
| d78da55f54 | |||
| 9052ff71e6 | |||
| 713da887ca | |||
| ad5950697e | |||
| 85d0d06cab | |||
| be6ec558e3 | |||
| dd2f60810a | |||
| 6c0b86d8f4 | |||
| 517ada6dbf | |||
| 0a098e3008 | |||
| c40078ffc9 | |||
| 729e0afdbe | |||
| eab86840d3 | |||
| 43aa2247f1 | |||
| afe17a507e | |||
| e621020474 | |||
| ac2aa7f288 | |||
| 805bd7e930 | |||
| f9bf2122b7 | |||
| 4e3e533e13 | |||
| 611c429912 | |||
| e2898ee5bf | |||
| e1a532500e | |||
| 4cf412b93d | |||
| 54c9c434e0 | |||
| de19f3e5fe | |||
| 8304993481 | |||
| 4c838aeaca | |||
| 4ec1145dce | |||
| 547d925065 | |||
| 874633051b | |||
| 2293f1ba15 | |||
| 483c3f61b5 | |||
| 83263968f2 | |||
| f279c61792 | |||
| 9717c696d8 | |||
| 94a594c777 | |||
| f1ae8a17cf | |||
| add9c43827 | |||
| d3f6a5c296 | |||
| 4aea78b7c0 | |||
| 4313fe670d | |||
| 2e8a1b67ea | |||
| e73044c533 | |||
| 418f09e364 | |||
| 08f207a396 | |||
| 4f57060708 | |||
| 1507724ecd | |||
| 4bc2f64b24 | |||
| 839ba31dfb | |||
| 2341452d91 | |||
| 7ab263b514 | |||
| 45b5b5960f | |||
| 1b8ec1f01c | |||
| c92cb1c096 | |||
| 8f31fbe275 | |||
| aa76419eda | |||
| 21503c0c38 | |||
| d957b19f55 | |||
| 122c4242ce | |||
| e769ed2fd8 | |||
| c722904831 | |||
| c93faa9564 | |||
| ce6495a46d | |||
| 04f2b905af | |||
| b668a2c0d2 | |||
| 5fc8f10602 | |||
| a27f399a9e | |||
| ff5d14fb63 | |||
| 27fb2d2d5b | |||
| 0b9d59bf0e | |||
| 23e059a065 | |||
| 45c7b29a96 | |||
| 3e6c3fda60 | |||
| 1a1363ef77 | |||
| b5c51e231b | |||
| 22711dd7be | |||
| 1ba4d3d5d1 | |||
| 823ab73585 | |||
| 66c444efac | |||
| b688b8921a | |||
| 00550ddab5 | |||
| 42fedec088 | |||
| ff3bc0e8c2 | |||
| dde7dff870 | |||
| 25ffc9cf96 | |||
| eca92cf5b5 | |||
| 372bdf8be7 | |||
| c7c90b0647 | |||
| 14c2da4fa2 | |||
| 64275cfd22 | |||
| f0b099d9f7 | |||
| c99d957bc8 | |||
| 51b14d55ec | |||
| f92d9994df | |||
| 431e1b87bd | |||
| 3ec2fc6030 | |||
| 99f416bf6b | |||
| 89acc4870b | |||
| 314c739c3a | |||
| 30b18fc997 | |||
| 168ac60853 | |||
| dff0c0c3b0 | |||
| ea3e9de7ab | |||
| c93fd900bc | |||
| a761aa9a37 | |||
| f37eb92d93 | |||
| 2c7cc5d325 | |||
| 16659c82d2 | |||
| 4ed3ca4128 | |||
| 9b2899c8fb | |||
| fada7fc9cc | |||
| 2be5bca42b | |||
| b1ebf6367c | |||
| 57e373f578 | |||
| 5c01874a32 | |||
| dd89e11633 | |||
| 4e51f19780 | |||
| 33d37d8dee | |||
| 60ce34af43 | |||
| 2602ff6e34 | |||
| ef5cd36a37 | |||
| 7d47f5aba3 | |||
| 33cb506c71 | |||
| 5fb36acb04 | |||
| 5f985f359b | |||
| 09045d5387 | |||
| 371af755df | |||
| 0485632e22 | |||
| c3d6de2412 | |||
| 36d49b11d9 | |||
| 866277510e | |||
| da8e00eb4e | |||
| cd30b2af3f | |||
| 38b3337d35 | |||
| 84d34aa12b | |||
| 96b7b68900 | |||
| 2a6ffbf5a4 | |||
| 1ac99e260b | |||
| 519062c8d0 | |||
| 78d7cdf662 | |||
| a191b1e811 | |||
| b372161874 | |||
| 5bbec7b17c | |||
| 54b847a8ef | |||
| a40a0f730f | |||
| a677b94297 | |||
| d102528d0a | |||
| 854692f975 | |||
| bdc321df4d | |||
| 230e8e9d4d | |||
| 96c49689ec | |||
| 698f560d56 | |||
| 67ee493e56 | |||
| 4fc7ef698a | |||
| 89434fc3bd | |||
| 6129f9afe8 | |||
| e5f38dbbc6 | |||
| 5534db95bc | |||
| 37c45424fd | |||
| ed44f4ed0c | |||
| f829f35cfa | |||
| 3033acadbb | |||
| 5c2a8a68c0 | |||
| e9544f8f92 | |||
| 1a6743dba9 | |||
| 06d6621c14 | |||
| d7b82edae3 | |||
| 5eae156134 | |||
| d8843e6057 | |||
| cf7c734778 | |||
| 240a8930ef | |||
| 4f3046593a | |||
| a0bd5bdd35 | |||
| e3bc63e4fa | |||
| fbf7c6293f | |||
| e128b02da1 | |||
| 2168dbc6fc | |||
| 97db895354 | |||
| 48fc68bfdb | |||
| 3741ffbb59 | |||
| caf58492f2 | |||
| 0c5bdd0205 | |||
| ae6da0d6ef | |||
| bdbb4cb495 | |||
| 566c358e8d | |||
| 324cffe6df | |||
| dadb8119e8 | |||
| 335d54b5f4 | |||
| 0fd851999f | |||
| 50f88798d8 | |||
| 8033cfc49d | |||
| 882f450ee8 | |||
| 4d60a582fe | |||
| 7ef3d33d0e | |||
| 24328ee8ae | |||
| d45c380389 | |||
| fbb3d144a1 | |||
| 7e7e299bce | |||
| d18116fcbe | |||
| e38513f077 | |||
| 08c4c641ec | |||
| 5d7e46b246 | |||
| dac906a79d | |||
| ecfeac4d63 | |||
| db1fc399a1 | |||
| 60616a5228 | |||
| 699c8d5b60 | |||
| 4264f013af | |||
| ecc4e1803c | |||
| 5c230c2d65 | |||
| 6af681354b | |||
| 5f381cf153 | |||
| 2ed290a214 | |||
| cf11472348 | |||
| 2c14514c56 | |||
| 053e6d8325 | |||
| 603c55fda7 | |||
| 113adcc5cc | |||
| a5bf8f6732 | |||
| c4b482634f | |||
| 4b083d6f81 | |||
| 04973d153b | |||
| 69ce7c78a4 | |||
| b64cf197f2 | |||
| a8b0279853 | |||
| e715e2ba2f | |||
| 985de36647 | |||
| b7b576e38b | |||
| 5018669ded | |||
| c5342d3eb0 | |||
| 0e7a0b5754 | |||
| 933ef02df0 | |||
| 513e0d748d | |||
| 852f01fddd | |||
| 9ebada63e9 | |||
| dbda70eee9 | |||
| 18bd10dfed | |||
| f76d03a0dc | |||
| 86ba5c9c1a | |||
| 5b56fbed97 | |||
| dfdd918ea0 | |||
| df2c7ec1a1 | |||
| 0d179f2042 | |||
| 63c6a9735c | |||
| f7e3f7d187 | |||
| cc54da5980 | |||
| ca82796081 | |||
| d092068bd2 | |||
| 0c1529237c | |||
| 6dbb2cb98a | |||
| ead763b909 | |||
| 415139851e | |||
| 5c99b72e2f | |||
| 82cb6c93cf | |||
| d21734ccf2 | |||
| 91934006d5 | |||
| 014c258c57 | |||
| 5e76bc3f2f | |||
| 7f6940f3a5 | |||
| 8d5682deb0 | |||
| 819abbfa7a | |||
| 227ad332b8 | |||
| 17c83e1a85 | |||
| 5d699496ca | |||
| e4f4fe81ae | |||
| abe28367fb | |||
| cc394d9f74 | |||
| 476b51ee31 | |||
| ec823ade14 | |||
| 0c3777f7d7 | |||
| a679e47c0d | |||
| 1e82735241 | |||
| a09ec6b0b5 | |||
| 0134a50ffa | |||
| 1da005e811 | |||
| 8ab912429b | |||
| 88d12223a5 | |||
| 6c363d9c4b | |||
| 6d3d9659e2 | |||
| 8977c5cef4 | |||
| d3645f6c63 | |||
| dbb057621d | |||
| 3a65bc0650 | |||
| 1eb6ff8c17 | |||
| 505cc01a18 | |||
| fdc4bb455f | |||
| c1a27aa6e4 | |||
| a14c7233d4 | |||
| c786d77b07 | |||
| e4913dfb73 | |||
| dfd33bab8e | |||
| 19b8f073b1 | |||
| b38b16f70e | |||
| dd69e4caff | |||
| 762a17d949 | |||
| d90cb23f22 | |||
| 1ccc391c8e | |||
| cafcead62e | |||
| 71ca46b21b | |||
| acf339ad13 | |||
| de4726ec8d | |||
| e58aff41f9 | |||
| 3c8acf9e0e | |||
| 782e8522c5 | |||
| dd4464cb08 | |||
| 3fdfd12448 | |||
| a11d23ba62 | |||
| 6bab362171 | |||
| 5d28c5c825 | |||
| b9173ebe50 | |||
| 0a1577ff43 | |||
| a616d6fd3e | |||
| 149b2ab9f9 | |||
| 4406dd88d1 | |||
| 48a950faf8 | |||
| 0ffbb5cb2c | |||
| f4c31bcf2c | |||
| f0e412a443 | |||
| ce91d36b7a | |||
| a022e5bb93 | |||
| 8e61673bd6 | |||
| 88e39d22f1 | |||
| 1441374f6e | |||
| d1cedaa3e8 | |||
| 5b721f6453 | |||
| a5f05b4715 | |||
| 96f49f117a | |||
| 88e1211762 | |||
| 59e1664223 | |||
| 42922a0d75 | |||
| 4f8a2a0900 | |||
| 9a2a26e904 | |||
| 067c3433b6 | |||
| d0b07c4890 | |||
| 24f8f48a2f | |||
| acafcc9888 | |||
| ec4cfe6f86 | |||
| 7b0c80c8be | |||
| 29029714ee | |||
| cd1203d1e2 | |||
| 4dfc34515c | |||
| 11a9f3ced3 | |||
| 5be1a7000c | |||
| 9059c1b850 | |||
| ec60fe42be | |||
| 275931969e | |||
| e37e0f189a | |||
| 8103943396 | |||
| 9497c99e9e | |||
| 2172d1a767 | |||
| e72c47869a | |||
| 5ea2124e91 | |||
| 0c08919bfa | |||
| a7562c3803 | |||
| 5f4d8f6862 | |||
| 9c7d0951b7 | |||
| 75e57c75cf | |||
| 04502b05d4 | |||
| e3e3a8404a | |||
| 48bf17614f | |||
| 1941b1ff65 | |||
| 46aa3d275a | |||
| d667e790b9 | |||
| bc345b569b | |||
| ee84b4e013 | |||
| e77129fa51 | |||
| 591855919e | |||
| 7af6188a8a | |||
| 1cd31cfce9 | |||
| 227dda921c | |||
| bdf2047ea1 | |||
| 0522996b7f | |||
| e9e25ef8d8 | |||
| a08325c975 | |||
| 4d8adde9d5 | |||
| f2b2f4f226 | |||
| 40bbc57269 | |||
| 48526824d6 | |||
| 745c37f2d1 | |||
| 0e185b7335 | |||
| 596ae696f9 | |||
| b5e2bdf0f9 | |||
| 4e09cc14b6 | |||
| 58dc46b445 | |||
| f34d0a29e8 | |||
| 365a90e72e | |||
| 72f4d3efa8 | |||
| 98f4d4de8b | |||
| 57f84748ec | |||
| 4fb57d2818 | |||
| aff819069f | |||
| af585588af | |||
| eaf6617a43 | |||
| 4b294c4aff | |||
| c2354bffa2 | |||
| ea1ffca4ae | |||
| 86a68d78c1 | |||
| 2e4a3516c8 | |||
| e71fec0e95 | |||
| 9478915cf4 | |||
| f955d77d71 | |||
| c1255bfa88 | |||
| 9aaeee2026 | |||
| 27b3a4083b | |||
| 2f1d963126 | |||
| 235e82a348 | |||
| 17d0b7d291 | |||
| 22c60fb894 | |||
| dc3a873abc | |||
| 543d5f0619 | |||
| 8b088ab6c2 | |||
| 29009e14f4 | |||
| bf3e90baf5 | |||
| 3986c407ff | |||
| 8bd56d29d6 | |||
| 467b3db824 | |||
| 6025dd6f79 | |||
| 984f3d2781 | |||
| 0a558b963d | |||
| 1227d98904 | |||
| f533e4a5d0 | |||
| 0b00ad98e6 | |||
| 8988408d18 | |||
| f858d89f7b | |||
| 964ee68974 | |||
| 8244ba7754 | |||
| 21b2184750 | |||
| 8d1c98f03e | |||
| 684bab99a9 | |||
| 7a4a8a4581 | |||
| b8843a0eb6 | |||
| 563dfbea43 | |||
| 488ebf8f1a | |||
| 03158bd689 | |||
| 765f2df18e | |||
| a15d8553e0 | |||
| 3a790b03ac | |||
| 90e5e03531 | |||
| a4158c16f0 | |||
| ffc0b364ca | |||
| 1efb8f6681 | |||
| 55b2d7f68f | |||
| 7f0e98ea93 | |||
| 67c21c18cf | |||
| 0cd82c7e57 | |||
| 1942c36443 | |||
| 93251582ab | |||
| decd2d6625 | |||
| 6824dcb89b | |||
| eeac075a29 | |||
| b864b1dec3 | |||
| 130fb9c217 | |||
| 688a3d29fd | |||
| 800f2f466b | |||
| 655a9ff1a4 | |||
| 12f8902c5f | |||
| 9cdb1a53f9 | |||
| f5efa18e96 | |||
| fcebde5abd | |||
| 7fbd4527e5 | |||
| d79716c3f8 | |||
| 884811fd99 | |||
| 712a2ae018 | |||
| 4f460eeb32 | |||
| 913416a9d5 | |||
| fd61001b4d | |||
| ce4d2c17a7 | |||
| a42ebd7aaf | |||
| f38718004f | |||
| 1ddf968750 | |||
| 71dc108b2d | |||
| 82728be44b | |||
| bebfa23cc9 | |||
| 1dad987073 | |||
| 6728abf595 | |||
| 669fce36e9 | |||
| b11003c472 | |||
| 187f80bfc9 | |||
| 611d216bb0 | |||
| 8a1cd32d50 | |||
| 2a18b27114 | |||
| 55f91143d9 | |||
| cf564a7d06 | |||
| b55067ad60 | |||
| a437c698eb | |||
| f8ada70fe7 | |||
| 196d9db3fa | |||
| e29eaf3785 | |||
| 7048ea473f | |||
| 720b1eeb27 | |||
| 4e282dac5b | |||
| 42343254b1 | |||
| e98829c96b | |||
| 5791f12a28 | |||
| fe9a6e8fdd | |||
| dd3f4e4751 | |||
| a89fa766ea | |||
| 39c2cf6c72 | |||
| c1ce72469c | |||
| eca76dd7c4 | |||
| 621b2d8fc9 | |||
| 4039b962b4 | |||
| 70cc0b93f0 | |||
| 81e90cd4e4 | |||
| 80bd3be0a1 | |||
| 829a794df7 | |||
| 00997408b8 | |||
| 4d3bdfc52f | |||
| b14d0926ed | |||
| 59b46db8db | |||
| a240284727 | |||
| 3d314324e0 | |||
| b9829bb696 | |||
| 85f03fb1d0 | |||
| 69810303e5 | |||
| 5b0e31acf3 | |||
| f0a934e014 | |||
| 328e4db1a2 | |||
| a708265e14 | |||
| a0618f7f47 | |||
| f2bd9fae01 | |||
| 64f717f81b | |||
| fd17377c18 | |||
| 6698fdb6e6 | |||
| e2eab1ee5d | |||
| aee9bf7cc2 | |||
| 63d9d554b9 | |||
| f6c04f9e74 | |||
| e167300194 | |||
| 319400ab13 | |||
| 38b1717fac | |||
| d5938a016c | |||
| 3ac592b18a | |||
| 82d6103907 | |||
| b353db67d4 | |||
| 5334abebaa | |||
| ad053361dc | |||
| ad62ccd8c7 | |||
| ecd93c9a92 | |||
| 774c02afab | |||
| 18f1f7d83d | |||
| 9eb3266891 | |||
| c3a3a68054 | |||
| 925fb30e05 | |||
| 363895761e | |||
| c191accc64 | |||
| 71b6b83ab3 | |||
| d532f8007e | |||
| 44574af3eb | |||
| faa4019b27 | |||
| c1e934d171 | |||
| 5fd88f9213 | |||
| 2bb095d47d | |||
| a4a30e8b60 | |||
| 4c05876066 | |||
| 23be3e5ba3 | |||
| f69d776cf6 | |||
| 25795eaecc | |||
| 3e1e92a6f1 | |||
| f2f3511a2f | |||
| 0ce70f4d0d | |||
| cac5d96c16 | |||
| 25d6ff1c90 | |||
| 87de8c0800 | |||
| 34ba762b42 | |||
| badc4bcc2f | |||
| 729b4e18ab | |||
| c3f03f50b3 | |||
| 8fd4cfe0a3 | |||
| 8dee07082d | |||
| 11e33f5c70 | |||
| 3d68b0c566 | |||
| fb8dd537e0 | |||
| affce4a4a1 | |||
| a479a5740b | |||
| 8e68b4e001 | |||
| d10b6f04cf | |||
| 052f2db0ea | |||
| 4b9a6ffee5 | |||
| 6d6784d429 | |||
| b3d2d3028c | |||
| 68814dd97c | |||
| ac39a609d7 | |||
| ba5cf5ba83 | |||
| 394e77c152 | |||
| c7fdf5245a | |||
| 6d91b2d984 | |||
| 36a401c6a7 | |||
| cbb59eafc8 | |||
| 5cd80b5817 | |||
| 6306cc9c34 | |||
| e335b07efe | |||
| e86144255c | |||
| e2c4a3b591 | |||
| 81c20e6792 | |||
| 204941c1e4 | |||
| 307afa5c52 | |||
| 92dc50e348 | |||
| 5c6c5c9608 | |||
| e27b8aeb66 | |||
| 7274a455a8 | |||
| 91dfef52ea | |||
| 9147b4e39f | |||
| 052159ce6f | |||
| ea6d728b89 | |||
| 093049a1f8 | |||
| 17fa8d904e | |||
| a996a7043d | |||
| 822659f7fd | |||
| e1fb0acff7 | |||
| d132a9bec5 | |||
| f9f1b55185 | |||
| 15b32abadf | |||
| 93e565f8df | |||
| 69703072df | |||
| ad3a324719 | |||
| 8c9e178206 | |||
| 8a812dd853 | |||
| 7250824e6b | |||
| cc1a61d607 | |||
| e7fe1ab567 | |||
| cad24714d4 | |||
| 4acdabe439 | |||
| b5b0fbbd38 | |||
| 2680eed681 | |||
| 1b8a12dd2a | |||
| b0c6236c24 | |||
| 8646410e6f | |||
| b2743108f1 | |||
| 4be798d15a | |||
| 4c4d0b426e | |||
| 58104b5a3a | |||
| 68a3842c8c | |||
| 987cc63764 | |||
| a8672ec432 | |||
| a9f5bd679d | |||
| f2b5dcf466 | |||
| 6b26f999e4 | |||
| f2a70396d1 | |||
| b50daed0d0 | |||
| e664834fd9 | |||
| 4c49e30763 | |||
| 2d2c671e6f | |||
| d2f47262de | |||
| cb13eb7f3b | |||
| 96825f9fcc | |||
| 7eeae4292c | |||
| a29c5f1bd8 | |||
| b7a2386874 | |||
| 43a0c7e604 | |||
| 73d8afd61b | |||
| 87162a9f31 | |||
| c643f10530 | |||
| ae81f6d573 | |||
| 44ba1f85c3 | |||
| f34301b427 | |||
| 666cbf6568 | |||
| f31a5a4bbb | |||
| bbf0565007 | |||
| c41ac9a413 | |||
| ab1dd27875 | |||
| 788a4c9143 | |||
| 43c2f46196 | |||
| fdb60a3fc1 | |||
| 6c6ea3ab9f | |||
| 806c8925c9 | |||
| ed35f967b0 | |||
| 0bcc01ed0e | |||
| b7fac65a87 | |||
| 605cb98b8e | |||
| b8a0559012 | |||
| fb2f63ee26 | |||
| b59f36f40b | |||
| 2a618b42b7 | |||
| aa3db09f22 | |||
| 9fa01aa6ff | |||
| 999251e137 | |||
| 55d5cc9308 | |||
| 58301c380b | |||
| ee45337d89 | |||
| f2e1ee1aac | |||
| ee5bc0f3a6 | |||
| 273cb81da0 | |||
| 1cbf4c8c7e | |||
| 446ddc2c11 | |||
| 42d3510c74 | |||
| 92c17d6376 | |||
| 645ea30b53 | |||
| 0d63ec9146 | |||
| 9588272639 | |||
| aca3bea56b | |||
| a8d676641b | |||
| 4fd843f8cf | |||
| 608283ba65 | |||
| f5e5ef8662 | |||
| d5a1832f31 | |||
| 73b0a3a0ef | |||
| 6dedeae6e1 | |||
| 14487ed5cf | |||
| e7def57c5b | |||
| 5419b0896c | |||
| f8f744dd87 | |||
| b0dfb44e96 | |||
| cdc068bb47 | |||
| 09c5c07bfb | |||
| 56962196ea | |||
| 8b4af5ce9d | |||
| 8034eb14b6 | |||
| 2739c89475 | |||
| cf79083edb | |||
| cbe9c9248d | |||
| e0c77a5fa8 | |||
| 339e91b026 | |||
| 25bfdd80e3 | |||
| 68c6d340ce | |||
| 91261b8b3f | |||
| 6e8bd1ff33 | |||
| b01865f0f5 | |||
| 4319b6d2d1 | |||
| 53d82e5126 | |||
| 90b74a72ad | |||
| c23f9b8629 | |||
| d752d0eb5e | |||
| 65fbdba047 | |||
| 6273b34254 | |||
| 4779dd13c0 | |||
| 469db7d5bd | |||
| 42269cf72b | |||
| 38bb0ca041 | |||
| 1949071259 | |||
| a119b83b65 | |||
| c939a8a0c1 | |||
| 5f26773c61 | |||
| 6cac8ba108 | |||
| 3eebccd566 | |||
| f81190bf35 | |||
| 065f4191f0 | |||
| b2d58ce0bd | |||
| 41b7939dac | |||
| 68c63cca7e | |||
| 2348d056cb | |||
| f469e48970 | |||
| 7e52fdd5ca | |||
| 29fceff9b3 | |||
| bdb5dabba5 | |||
| 3fe705427f | |||
| 21a1fe5056 | |||
| e40bdad6c3 | |||
| dcbe24d45c | |||
| d4607c68f7 | |||
| 026ae3c8e1 | |||
| f6d128842a | |||
| a6dac6ded3 | |||
| 4fcec82917 | |||
| 9e46b3cd00 | |||
| d56f151edb | |||
| 4b49c95e73 | |||
| cf922b036e | |||
| f7dedd6244 | |||
| 7af3c33814 | |||
| 8cd3be6f67 | |||
| 86e8ed2ac0 | |||
| 0d1e7f5c17 | |||
| fc9a9e33d2 | |||
| f00e28ced3 | |||
| 4c29677d5b | |||
| 07b8dc3f76 | |||
| ee514ee8ba | |||
| 97a0fb3e18 | |||
| ef42ca692a | |||
| 9ca49c587c | |||
| 3b38d936a8 | |||
| 0d325a3ec4 | |||
| 7f8cdfa361 | |||
| 8da4217d69 | |||
| d3a04071eb | |||
| 0c5903d822 | |||
| f97f8be5a4 | |||
| d2ec9768f1 | |||
| 37218e8764 | |||
| 069fff673a | |||
| 5aed046b34 | |||
| 5dea15ade9 | |||
| 10487683d3 | |||
| 12967f2819 | |||
| 07b22a2c28 | |||
| fe13a7fe34 | |||
| 602cac5a35 | |||
| 11f697fa9c | |||
| d5957035fa | |||
| b80d7d226b | |||
| 8564dafbfb | |||
| dc66dab237 | |||
| a32d77b24d | |||
| 23836e2345 | |||
| 1a5c95d084 | |||
| 6a6464b0f7 | |||
| 60027e664f | |||
| 27ff329981 | |||
| df5ffb4736 | |||
| a194b908e7 | |||
| 79f5faa544 | |||
| 5c3f095ef2 | |||
| 283e5fde69 | |||
| f96ca562a2 | |||
| 58a936cf33 | |||
| a0d6fc47e0 | |||
| dd03ad6c14 | |||
| 2bbd306b94 | |||
| 766cccf335 | |||
| 9e2e5ce581 | |||
| e747c0b060 | |||
| 67bef933be | |||
| f1c0d8c8f8 | |||
| dd2552eda8 | |||
| ab9871640a | |||
| 615f71b099 | |||
| ff418c9f0c | |||
| 4c43d3d850 | |||
| a4cacc079e | |||
| 9df20fcbc5 | |||
| f4dd549a41 | |||
| cc445e89a3 | |||
| 08f8a7a17c | |||
| ab14d70c6c | |||
| ecfc6fd31e | |||
| 0fae8b2b10 | |||
| c38e4f7206 | |||
| 47717e872b | |||
| 903c065e26 | |||
| 3742bf384c | |||
| 1029f8674c | |||
| c801660b2a | |||
| 48d4541ed7 | |||
| 8007ea6a7e | |||
| 8e441ecb19 | |||
| c305e82f5c | |||
| 87cfa30508 | |||
| 2c224d5092 | |||
| 72eeaf446e | |||
| 51a806b0ba | |||
| 52840f9141 | |||
| 974503160d | |||
| d9e3ff3256 | |||
| b510ab0371 | |||
| 5eba78d795 | |||
| 884c13a22d | |||
| 90c5605409 | |||
| 3ae6443c59 | |||
| b370c405da | |||
| 36d1a68b9a | |||
| 58bbbecb2d | |||
| 1cdff32b5c | |||
| 7dbdb005b0 | |||
| e6c15dc3c5 | |||
| 1778d19bde | |||
| 56c3c0462c | |||
| 80102353d6 | |||
| d7dc158861 | |||
| b52c42c31d | |||
| f8739e341b | |||
| 1ead12a2a3 | |||
| c2feb6e214 | |||
| 3d74b8a2fa | |||
| 83a46c68dc | |||
| bcefce9df7 | |||
| dcb5af3339 | |||
| 1d56d79757 | |||
| 0b370bd40d | |||
| 07a5ae83b5 | |||
| cc60addbaa | |||
| 30b7429169 | |||
| 28ebf6559e | |||
| 1c6e3dd9a6 | |||
| 6bba661e24 | |||
| eccb20c3b7 | |||
| f210c4425b | |||
| e0e3ab941e | |||
| 9cc568fe3e | |||
| a638f9505f | |||
| 1b35dd0c55 | |||
| d394b4939f | |||
| c751d4eb79 | |||
| bb147abb7f | |||
| 1c3793e2ef | |||
| 1274641f89 | |||
| 803b50535f | |||
| 028af735c9 | |||
| d42cd6efaf | |||
| 2cf129408b | |||
| 0ed1c75c38 | |||
| 5588b62b40 | |||
| 03cb80b75b | |||
| 86a3fd6e22 | |||
| 27ebe7ba73 | |||
| 0ce2dcacef | |||
| 6c8fc6bb21 | |||
| 0e26d4e741 | |||
| 3d6f01c872 | |||
| 4894588284 | |||
| 7adea0ac65 | |||
| 138c84cd5f | |||
| d283791f77 | |||
| 911d9d17d6 | |||
| 8abbaf2e56 | |||
| dca8b46abf | |||
| 2b968c4387 | |||
| c88508ace3 | |||
| b4adbcd6cb | |||
| 031b93f062 | |||
| f1ac9c4443 | |||
| 544e32e0f8 | |||
| f8f806e8eb | |||
| 2aed40d99b | |||
| d0443a7f41 | |||
| 1d83ad5282 | |||
| 8fda84c15a | |||
| 939bc643be | |||
| c163039c6e | |||
| 9c6652b19d | |||
| 2510399017 | |||
| 9d2cbbaa2c | |||
| f47d4d0c9d | |||
| f41d89515c | |||
| ea16d6b050 | |||
| 19f7dc1657 | |||
| b674014377 | |||
| 802ed9d481 | |||
| 1f1b20041c | |||
| 3ae0762f62 | |||
| 89934886f7 | |||
| 233cc4bdc4 | |||
| f278c85f2a | |||
| 8dde34565a | |||
| 14774ae687 | |||
| ed4156530b | |||
| 45e8cd104b | |||
| fc195eae1e | |||
| 18d8049f37 | |||
| 21e328569b | |||
| ff32485f67 | |||
| d718eb0be2 | |||
| 325eec2c6f | |||
| 5629c0ee72 | |||
| 369604aa42 | |||
| db20ff93c7 | |||
| 192eb28ac7 | |||
| 023da097a1 | |||
| 02738a0694 | |||
| 61892bdf3d | |||
| 02ece1183e | |||
| 38fe310a7c | |||
| 70fbbd65e9 | |||
| 2973223fb1 | |||
| 1dd19c0831 | |||
| 16e2c97e0c | |||
| 050296d0c2 | |||
| f75da0e2e3 | |||
| 0396c3f475 | |||
| 97044fee16 | |||
| ca4bffe5a1 | |||
| 0df67fa920 | |||
| ec3371c4ff | |||
| dcc5f66691 | |||
| ec10f349bd | |||
| 3b9e854042 | |||
| 9eba883978 | |||
| fb826bed70 | |||
| 2797856f50 | |||
| ad69fa19b6 | |||
| 0ad80c1af9 | |||
| 2060e86875 | |||
| 87605ca25e | |||
| 3cd10bf4b3 | |||
| 6bdb0fee55 | |||
| 7182894eb6 | |||
| 42e2531e8d | |||
| 171f845069 | |||
| f9b043dc7e | |||
| e069e66952 | |||
| c9d7a2f313 | |||
| c69af72060 | |||
| 4111a12cbf | |||
| 57c1c4e016 | |||
| 324dcd7ef8 | |||
| c604f920a0 | |||
| f8cbd67557 | |||
| 3c31891774 | |||
| 0e5dc4f368 | |||
| e75d8048fe | |||
| 7c10d171f6 | |||
| 5aa01ce91c | |||
| 86bb92d3be | |||
| e199564415 | |||
| 51db0eaf40 | |||
| 83c9200e7d | |||
| 529d448383 | |||
| 8ea59067ce | |||
| ac5c844f0b | |||
| b10e0c3a15 | |||
| c2cc6b6dc8 | |||
| 357b0ee467 | |||
| ac409965d5 | |||
| c596fe33a9 | |||
| 914bc117a1 | |||
| 4dbe366978 | |||
| 7ca8c8d298 | |||
| cf7b2b2acf | |||
| 81598aefce | |||
| 9effc166bb | |||
| de3c453763 | |||
| 9da114d965 | |||
| 3f603eb84f | |||
| 3f1df8faa6 | |||
| 2a3af80deb | |||
| abfff8bcf1 | |||
| c6d7dabada | |||
| 0c61bb0063 | |||
| 0f003abf52 | |||
| 80393bf765 | |||
| f43ecf1a85 | |||
| a242c0654b | |||
| fb1976a6bc | |||
| 0e7b345010 | |||
| fd28cfc4f8 | |||
| b20467d6e3 | |||
| 068b95eacb | |||
| 95e4239fd5 | |||
| 05c900d291 | |||
| f698078f94 | |||
| a54b6f0359 | |||
| c8e9fe519f | |||
| 0c726d835d | |||
| 86b1e992a1 | |||
| c3b15fffa4 | |||
| 7bbac0ed23 | |||
| 1fb1d15d4c | |||
| 64ea9b7c8b | |||
| 5a03386ad5 | |||
| acca363a4e | |||
| e28a411408 | |||
| 89a7bd4e7d | |||
| 46b2e82b43 | |||
| 723b239fda | |||
| bfb757b3b1 | |||
| 7fb9c3bf1e | |||
| bf7c8cebc4 | |||
| ac01a621be | |||
| 47a046ca1d | |||
| 4ef70d2a15 | |||
| ee0d835cd8 | |||
| 330263028e | |||
| d0d19778a4 | |||
| 0bfbd87bbf | |||
| ba095d8e7f | |||
| fb52227a92 | |||
| 0b8919de61 | |||
| 7fca7e6bd9 | |||
| 69884c61b4 | |||
| ed6cf2ecf1 | |||
| 0be0347f8b | |||
| e376f25d92 | |||
| d2a715af0f | |||
| 3c230986e7 | |||
| f529d09227 | |||
| b7988f2158 | |||
| 2a0d8c3f77 | |||
| 429fcaf05a | |||
| 4603d16162 | |||
| 6d4a7f96eb | |||
| 4c277afea9 | |||
| 11be5db628 | |||
| 779d303572 | |||
| 788f344012 | |||
| 33c9326427 | |||
| 9ef57152e4 | |||
| f8f2fb608d | |||
| 1cf6b37ee4 | |||
| 76822717f4 | |||
| 4142b113e9 | |||
| 3c123538e9 | |||
| 73996997e5 | |||
| c4d14594ce | |||
| 657960024d | |||
| e05842cbef | |||
| bac1a289e3 | |||
| 75097594b2 | |||
| ff4beaeb2c | |||
| 9120affd7f | |||
| 81b6dede03 | |||
| 74e49aba79 | |||
| 8911326a95 | |||
| 1812300fd6 | |||
| 55ff571da6 | |||
| 5481919f5c | |||
| df99669340 | |||
| 93db3cd935 | |||
| a9c95bbbfb | |||
| 2c636dfd3b | |||
| 6f838c555c | |||
| 134d29d82c | |||
| 5dbe550c5f | |||
| 2f13e425c6 | |||
| 54d41410fb | |||
| 0a8397b3dc | |||
| e1bb58fb06 | |||
| 9458faf5b0 | |||
| ef75590853 | |||
| 43d8f46eab | |||
| 65deb7df1d | |||
| edba815b0b | |||
| e2eea91261 | |||
| 2338a1ee7d | |||
| 6430f6207d | |||
| 6f60a3a1d5 | |||
| a19d789145 | |||
| b1cde584bf | |||
| ca318375a4 | |||
| d417d5b314 | |||
| b3c853ee26 | |||
| 6d02f4c264 | |||
| 10bb347ddf | |||
| cfe9cfd03c | |||
| 4b124e3493 | |||
| 92a7d81063 | |||
| 45a0f340b6 | |||
| c0bd0966a8 | |||
| c71be675aa | |||
| 8ebe44405e | |||
| 2441e70153 | |||
| 68b5fc40f6 | |||
| f0eba54ace | |||
| f83aba0d2e | |||
| e8f6825b64 | |||
| f522812336 | |||
| 876e8be54e | |||
| c22eab7406 | |||
| 4487834591 | |||
| 98f91607cd | |||
| a02a48ac82 | |||
| 72b963fb21 | |||
| 2efbb68627 | |||
| 9af6a779f2 | |||
| 07aca218cb | |||
| 3c56f24eed | |||
| aec5b3bb2b | |||
| 67c54c07c2 | |||
| a3166dfbae | |||
| 3e33427bcf | |||
| 73738acf91 | |||
| 77fd516b15 | |||
| 61e81a4e60 | |||
| 77fafd40fb | |||
| d13bfa9220 | |||
| 682462ca07 | |||
| 5c0d9f6f15 | |||
| bbc8ef009d | |||
| 1e57526d7e | |||
| d973d56ef6 | |||
| b7a61cbd99 | |||
| aef7f7bbc2 | |||
| 9e8f911f02 | |||
| c263eac7fd | |||
| 88bcfc5e48 | |||
| 8af4a92fc0 | |||
| 6691afb9b9 | |||
| 679f30abe3 | |||
| 733994f9b5 | |||
| 1182d0e45d | |||
| e694475556 | |||
| 6c6a7cf136 | |||
| c1a0f11c39 | |||
| a3fa476c7f | |||
| 15e49952f9 | |||
| 91c0a46ba9 | |||
| eaa250a962 | |||
| d87049c7f8 | |||
| 947aecdd33 | |||
| 6fa78f83ba | |||
| fe604ca2b1 | |||
| 305b4e1258 | |||
| 4128213acb | |||
| d8d460f175 | |||
| 963cfe052a | |||
| 252b7037bd | |||
| e5f462814e | |||
| 82b75e2690 | |||
| 9644a54f67 | |||
| 225f6c7785 | |||
| 5d8ccdff25 | |||
| b5bd9260ef | |||
| d5a928e6f7 | |||
| 04d14dea90 | |||
| 92937fa72e | |||
| ffd6ab2cc9 | |||
| 78a2a47fbf | |||
| 1558fd9519 | |||
| 984bd95cc0 | |||
| 2295ba7554 | |||
| abb698281c | |||
| cbd1bde7e6 | |||
| 1d1d29a163 | |||
| 0b206a4d50 | |||
| ffeaf7e050 | |||
| ab37b80f8b | |||
| 29b3965e74 | |||
| a2ab2b9da5 | |||
| ad42e5eb61 | |||
| 830f0a1026 | |||
| 76760fc85f | |||
| 1dda16c010 | |||
| af81626f43 | |||
| 1d4129e84d | |||
| 68b481af6e | |||
| 0a7b8317d3 | |||
| 58e8e311a3 | |||
| fbc629c6da | |||
| 629d36c63c | |||
| d5c79f697a | |||
| 7e0b6d7f2a | |||
| 05804262f0 | |||
| 14ab6c5960 | |||
| 0792ef7288 | |||
| a1fa306cae | |||
| 43f861e219 | |||
| 29802b0f4d | |||
| 0ef62708ab | |||
| 52da8552ef | |||
| e78b3a88d7 | |||
| 85f0886886 | |||
| ffe258a9fb | |||
| 1242b1738a | |||
| 9b3f77f4f5 | |||
| c4f31cb929 | |||
| 71952ca8fd | |||
| df21c81bfa | |||
| 020c1d5589 | |||
| 63eb912434 | |||
| 125d190ff1 | |||
| 349aa45f54 | |||
| 921b23e9f0 | |||
| 827c983175 | |||
| df0650e48d | |||
| ee2d32339f | |||
| 420a955314 | |||
| 8a7e49780a | |||
| 190729df73 | |||
| f209841e06 | |||
| 5f424e7113 | |||
| f361b50f7f | |||
| 31319b4045 | |||
| 4f2f00ee62 | |||
| bab2df3442 | |||
| 4b778e444d | |||
| 634d4f1099 | |||
| 4da1fa4832 | |||
| 70472f11cc | |||
| 9442b5c66b | |||
| ef3ab6161f | |||
| e719cea0de | |||
| 1375932c05 | |||
| 0969f47404 | |||
| 34872b3a02 | |||
| cad81acc44 | |||
| 54f052d661 | |||
| c3ad7a032f | |||
| 62fd2f599b | |||
| 1dd4c7ea11 | |||
| cefe9f76fd | |||
| 0d78f3695a | |||
| 6091f7499a | |||
| ce8d22c29b | |||
| 1217dea5fc | |||
| c6cfa34997 | |||
| 83d0644fd9 | |||
| aeb51a157f | |||
| 867dbf33dc | |||
| 508767278f | |||
| 4bea071071 | |||
| dcebdd6dbc | |||
| 8b0e036ed5 | |||
| 067c0bfe70 | |||
| 08ab06c9cc | |||
| a3971f793c | |||
| f1362b1c94 | |||
| 9ccc78a929 | |||
| 5fb31c2a9f | |||
| 755e2dcbd2 | |||
| 29bdfb2d90 | |||
| fc739d1283 | |||
| 1b6d3734cf | |||
| 912c36d590 | |||
| b323855b69 | |||
| 1b748648e0 | |||
| 8bf7a6ba6e | |||
| 56069a2d38 | |||
| c246bec753 | |||
| 914d12115c | |||
| 62bb91ab14 | |||
| 9f2bc146c0 | |||
| 1b5046da69 | |||
| 164d007f95 | |||
| 37a240ee2d | |||
| fb149808ee | |||
| ef877a40ca | |||
| 14a4a4492b | |||
| db22653124 | |||
| becc73e58a | |||
| 0418517c3e | |||
| a9a4cd2f5c | |||
| b4b38f1dc9 | |||
| 9170eadbb0 | |||
| 413d401567 | |||
| f6be175af0 | |||
| 522005a7ab | |||
| 9a480344de | |||
| 8ce9986479 | |||
| fd5ac5898c | |||
| 6bbe759f74 | |||
| 966de74d5b | |||
| fd8f70d914 | |||
| 3b248ae94c | |||
| 26fe6c8f61 | |||
| b4b303a2d9 | |||
| acd07c31ff | |||
| 358ba0101e | |||
| bc790d8cd5 | |||
| 74040e52b4 | |||
| f8df4b485d | |||
| 1de371e944 | |||
| 8d872ccee9 | |||
| cf5ae93638 | |||
| ed50da52f7 | |||
| 292f171f28 | |||
| 72835c80ed | |||
| d3dce4c662 | |||
| ecec96c943 | |||
| b2646dbd65 | |||
| d941cb79ce | |||
| 8339d18e0a | |||
| f0a96ba75d | |||
| 1380babcf2 | |||
| c5089d8c58 | |||
| ce78e0d88d | |||
| b497967647 | |||
| 4614b6b6bd | |||
| c548b61fa4 | |||
| ca9fef50d9 | |||
| 0039b60020 | |||
| 7b34f3f524 | |||
| 7456810517 | |||
| f3a3376e24 | |||
| 9e05da552f | |||
| c427b1594d | |||
| d58a706b9b | |||
| d26467d0de | |||
| 57bddf9189 | |||
| cd815d064d | |||
| da2c5c738b | |||
| 5d649a516f | |||
| a05757f8d6 | |||
| 3f051cdd8d | |||
| 7b28a620a9 | |||
| aa850918ad | |||
| bad6cf0ed7 | |||
| 873ca66d55 | |||
| 57d85ee7de | |||
| f95f181301 | |||
| f97837d15a | |||
| b8d3fe6f1a | |||
| 47d29bc654 | |||
| 70f858361f | |||
| 71023fc09c | |||
| 6babfa3435 | |||
| ef9527d691 | |||
| 9ac7212f9c | |||
| 3c614e6e16 | |||
| b64ccb4522 | |||
| c11c217baf | |||
| c225c058bc | |||
| 2f164649b9 | |||
| 22baae10dd | |||
| 240ebede6f | |||
| f2acc12174 | |||
| 3fb4582d61 | |||
| 69d802a060 | |||
| 0c8d4a9671 | |||
| b29857e81f | |||
| 3d0d526051 | |||
| 016d5c5ef4 | |||
| 67c8ac7d9d | |||
| b95daf7874 | |||
| 8d81458b7f | |||
| a0c09f7575 | |||
| ffffca8cdc | |||
| 2069b02ba7 | |||
| c20d5e5be9 | |||
| 64e24e5fc1 | |||
| 70dea049ae | |||
| f15686ba0f | |||
| f8a178c282 | |||
| 1e7cbf235f | |||
| 4c15cab048 | |||
| a074c146ec | |||
| fff10f43bd | |||
| 063289b435 | |||
| abe6509ba8 | |||
| 8986399736 | |||
| cff3da3fd0 | |||
| ea7c079e30 | |||
| a977a9b444 | |||
| 8fa9fda9b4 | |||
| fabfee5976 | |||
| ad777ed183 | |||
| 40d4e138b7 | |||
| 5b8c34e504 | |||
| f5fb5857df | |||
| 295e910cc7 | |||
| ab2cfdeeec | |||
| c891dda8eb | |||
| f6c4bdb8a6 | |||
| b0e50db4f6 | |||
| e783de3453 | |||
| 9ed198db28 | |||
| ce579e48a6 | |||
| 03d3f1d426 | |||
| 20cbaf913a | |||
| fa59334a46 | |||
| d6c22d516b | |||
| 89ee94e953 | |||
| 44336efb8d | |||
| c4a8180592 | |||
| 9d809be775 | |||
| 5ecbcfffcb | |||
| 4dadcbcb49 | |||
| 92103dbb06 | |||
| e9b2bb6169 | |||
| 6f9c51db9e | |||
| d3f4dcacff | |||
| e12bcea613 | |||
| 3e989f4bb0 | |||
| 7415737cf1 | |||
| cf7a158944 | |||
| d1befc29cb | |||
| 45ff4444b3 | |||
| 8663f7f102 | |||
| 76153c5791 | |||
| 651f6d1462 | |||
| 4cdcfd24fa | |||
| 8b17437e4a | |||
| bc79986dd2 | |||
| a00f00a7fe | |||
| 557f3b77bd | |||
| ffe0556c90 | |||
| 36db844155 | |||
| b91eeb72f0 | |||
| 27be90358f | |||
| c8bf4331c8 | |||
| 5b7054b0be | |||
| bc4893b4ba | |||
| 3919a21650 | |||
| f56d40ac8d | |||
| 174c0135ac | |||
| 06ebcfff89 | |||
| 4437f7c89e | |||
| 2062e4233e | |||
| d00cdcf126 | |||
| 988e81670d | |||
| de7b67f085 | |||
| e98e8ea13f | |||
| 47f051b73a | |||
| cf9d6fb699 | |||
| 7812eb49f2 | |||
| ac574f7b36 | |||
| 68c3450a44 | |||
| 7c9219d294 | |||
| 0d266413c7 | |||
| 78f080c31b | |||
| 38994c233f | |||
| 868a7b9795 | |||
| 74355e86da | |||
| 01af71cd1c | |||
| 1a56c63658 | |||
| 7fc10552f6 | |||
| 9d20c62106 | |||
| 96bac589cb | |||
| e9e76f7259 | |||
| e024c091b7 | |||
| 07d534b43c | |||
| 6eae714d1d | |||
| df865e323d | |||
| 71a3bdfeac | |||
| 83d06ec2af | |||
| 3a8c1d103c | |||
| fa7796ea59 | |||
| caa28ce01e | |||
| e31fa95c23 | |||
| d911e1f51a | |||
| c496002ca5 | |||
| b0979ba153 | |||
| eb6f685f66 | |||
| 2756155b7c | |||
| cba0084e3d | |||
| b62571f9aa | |||
| e53ec44d98 | |||
| c924057c56 | |||
| fc542861e8 | |||
| 38cae8c905 | |||
| f696cd554f | |||
| d15ac42969 | |||
| 780a6482e5 | |||
| e0023a722b | |||
| 30b7eb7c73 | |||
| b5bafc8f60 | |||
| 615d441381 | |||
| 40f2355fbf | |||
| fb72bb2290 | |||
| 38b5a0d5cd |
@@ -0,0 +1,17 @@
|
||||
krill-parser.js
|
||||
krill.pegjs
|
||||
.eslintrc.json
|
||||
server.js
|
||||
tidal-sniffer.js
|
||||
*.jsx
|
||||
tunejs.js
|
||||
out/**
|
||||
postcss.config.js
|
||||
postcss.config.cjs
|
||||
tailwind.config.js
|
||||
tailwind.config.cjs
|
||||
vite.config.js
|
||||
/**/dist/**/*
|
||||
!**/*.mjs
|
||||
**/*.tsx
|
||||
**/*.ts
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": ["eslint:recommended"],
|
||||
"overrides": [],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [],
|
||||
"rules": {
|
||||
"no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
open_collective: tidalcycles
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Build and Deploy
|
||||
|
||||
on: [workflow_dispatch]
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
deployments: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "npm"
|
||||
- name: Install Dependencies
|
||||
run: npm ci && cd website && npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v2
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: "./website/dist"
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
@@ -0,0 +1,21 @@
|
||||
name: Strudel tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "npm"
|
||||
- run: npm install
|
||||
- run: npm run format-check
|
||||
- run: npm run lint
|
||||
- run: npm test
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
dist
|
||||
out
|
||||
!docs/dist
|
||||
dist-*
|
||||
cabal-dev
|
||||
*.o
|
||||
*.hi
|
||||
*.hie
|
||||
*.chi
|
||||
*.chs.h
|
||||
*.dyn_o
|
||||
*.dyn_hi
|
||||
.hpc
|
||||
.hsenv
|
||||
.cabal-sandbox/
|
||||
cabal.sandbox.config
|
||||
*.prof
|
||||
*.aux
|
||||
*.hp
|
||||
*.eventlog
|
||||
.stack-work/
|
||||
cabal.project.local
|
||||
cabal.project.local~
|
||||
.HTF/
|
||||
.ghc.environment.*
|
||||
node_modules/
|
||||
.DS_Store
|
||||
repl-parcel
|
||||
mytunes.ts
|
||||
doc
|
||||
out
|
||||
.parcel-cache
|
||||
repl_old
|
||||
tutorial.rendered.mdx
|
||||
doc.json
|
||||
talk/public/EmuSP12
|
||||
talk/public/samples
|
||||
server/samples/old
|
||||
repl/stats.html
|
||||
coverage
|
||||
@@ -0,0 +1,9 @@
|
||||
*.md
|
||||
*.json
|
||||
*.yml
|
||||
*.
|
||||
**/out
|
||||
**/dist
|
||||
packages/mini/krill-parser.js
|
||||
packages/xen/tunejs.js
|
||||
paper
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"proseWrap": "preserve",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"subspan",
|
||||
"vals"
|
||||
],
|
||||
"yaml.schemas": {
|
||||
"https://json.schemastore.org/github-workflow.json": "file:///home/felix/projects/strudel/.github/workflows/deploy.yml"
|
||||
},
|
||||
"testing.automaticallyOpenPeekView": "never"
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
# 🌀 Contributing to Strudel 🌀
|
||||
|
||||
Thanks for wanting to contribute!!! There are many ways you can add value to this project
|
||||
|
||||
## Communication Channels
|
||||
|
||||
To get in touch with the contributors, either
|
||||
|
||||
- open a [github discussion](https://github.com/tidalcycles/strudel/discussions) or
|
||||
- [join the Tidal Discord Channel](https://discord.gg/remJ6gQA) and go to the #strudel channel
|
||||
- Find related discussions on the [tidal club forum](https://club.tidalcycles.org/)
|
||||
|
||||
## Ask a Question
|
||||
|
||||
If you have any questions about strudel, make sure you've glanced through the
|
||||
[docs](https://strudel.tidalcycles.org/learn/) to find out if it answers your question.
|
||||
If not, use one of the Communication Channels above!
|
||||
|
||||
Don't be afraid to ask! Your question might be of great value for other people too.
|
||||
|
||||
## Give Feedback
|
||||
|
||||
No matter if you've used the Strudel REPL or if you are using the strudel packages, we are happy to hear some feedback.
|
||||
Use one of the Communication Channels listed above and drop us a line or two!
|
||||
|
||||
## Share Music
|
||||
|
||||
If you made some music with strudel, you can give back some love and share what you've done!
|
||||
Your creation could also be part of the random selection in the REPL if you want.
|
||||
Use one of the Communication Channels listed above.
|
||||
|
||||
## Improve the Docs
|
||||
|
||||
If you find some weak spots in the [docs](https://strudel.tidalcycles.org/learn/getting-started),
|
||||
you can edit each file directly on github via the "Edit this page" link located in the right sidebar.
|
||||
|
||||
## Propose a Feature
|
||||
|
||||
If you want a specific feature that is not part of strudel yet, feel free to use one of the communication channels above.
|
||||
Maybe you even want to help with the implementation of that feature!
|
||||
|
||||
## Report a Bug
|
||||
|
||||
If you've found a bug, or some behaviour that does not seem right, you are welcome to file an [issue](https://github.com/tidalcycles/strudel/issues).
|
||||
Please check that it has not been reported before.
|
||||
|
||||
## Fix a Bug
|
||||
|
||||
To fix a bug that has been reported,
|
||||
|
||||
1. check that nobody else is already fixing it and respond to the issue to let people know you're on it
|
||||
2. fork the repository
|
||||
3. make sure you've setup the project (see below)
|
||||
4. hopefully fix the bug
|
||||
5. make sure the tests pass
|
||||
6. send a pull request
|
||||
|
||||
## Write Tests
|
||||
|
||||
There are still many tests that have not been written yet! Reading and writing tests is a great opportunity to get familiar with the codebase.
|
||||
You can find the tests in each package in the `test` folder. To run all tests, run `npm test` from the root folder.
|
||||
|
||||
## Project Setup
|
||||
|
||||
To get the project up and running for development, make sure you have installed:
|
||||
|
||||
- git
|
||||
- node, preferably v16
|
||||
|
||||
then, do the following:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/tidalcycles/strudel.git && cd strudel
|
||||
npm i # install at root to symlink packages
|
||||
npx lerna bootstrap # install all dependencies in packages
|
||||
cd repl && npm i # install repl dependencies
|
||||
npm run start # start repl
|
||||
```
|
||||
|
||||
Those commands might look slightly different for your OS.
|
||||
Please report any problems you've had with the setup instructions!
|
||||
|
||||
## Code Style
|
||||
|
||||
To make sure the code changes only where it should, we are using prettier to unify the code style.
|
||||
If you use VSCode, you can
|
||||
|
||||
1. install [the prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
||||
2. open command palette and run "Format Document With..."
|
||||
3. Choose "Configure Default Formatter..."
|
||||
4. Select prettier
|
||||
|
||||
## Package Workflow
|
||||
|
||||
The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning.
|
||||
When you run `npm i` on the root folder, [npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces) will symlink all packages
|
||||
in the `node_modules` folder. This will allow any js file to import `@strudel.cycles/<package-name>` to get the local version,
|
||||
which allows developing multiple packages at the same time
|
||||
|
||||
## Package Publishing
|
||||
|
||||
To publish all packages that have been changed since the last release, run:
|
||||
|
||||
```sh
|
||||
npm login
|
||||
npx lerna publish
|
||||
```
|
||||
|
||||
### New Packages
|
||||
|
||||
To add a new package, you have to publish it manually the first time, using:
|
||||
|
||||
```sh
|
||||
cd packages/<package-name> && npm publish --access public
|
||||
```
|
||||
|
||||
## Have Fun
|
||||
|
||||
Remember to have fun, and that this project is driven by the passion of volunteers!
|
||||
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
@@ -0,0 +1,53 @@
|
||||
# strudel
|
||||
|
||||
[](https://github.com/tidalcycles/strudel/actions)
|
||||
|
||||
An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using web technologies. This software is slowly stabilising, but please continue to tread carefully.
|
||||
|
||||
- Try it here: <https://strudel.tidalcycles.org/>
|
||||
- Docs: <https://strudel.tidalcycles.org/learn/>
|
||||
- Technical Blog Post: <https://loophole-letters.vercel.app/strudel>
|
||||
|
||||
## Running Locally
|
||||
|
||||
After cloning the project, you can run the REPL locally:
|
||||
|
||||
```bash
|
||||
npm run setup
|
||||
npm run repl
|
||||
```
|
||||
|
||||
## Using Strudel In Your Project
|
||||
|
||||
There are multiple npm packages you can use to use strudel, or only parts of it, in your project:
|
||||
|
||||
- [`core`](./packages/core/): tidal pattern engine
|
||||
- [`mini`](./packages/mini): mini notation parser + core binding
|
||||
- [`eval`](./packages/eval): user code evaluator. syntax sugar + highlighting
|
||||
- [`tone`](./packages/tone): bindings for Tone.js instruments and effects
|
||||
- [`osc`](./packages/osc): bindings to communicate via OSC
|
||||
- [`midi`](./packages/midi): webmidi bindings
|
||||
- [`serial`](./packages/serial): webserial bindings
|
||||
- [`tonal`](./packages/tonal): tonal functions
|
||||
- [`xen`](./packages/xen): microtonal / xenharmonic functions
|
||||
- ... [and there are more](./packages/)
|
||||
|
||||
Click on the package names to find out more about each one.
|
||||
|
||||
## Contributing
|
||||
|
||||
There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md).
|
||||
|
||||
<a href="https://github.com/tidalcycles/strudel/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=tidalcycles/strudel" />
|
||||
</a>
|
||||
|
||||
Made with [contrib.rocks](https://contrib.rocks).
|
||||
|
||||
## Community
|
||||
|
||||
There is a #strudel channel on the TidalCycles discord: <https://discord.com/invite/HGEdXmRkzT>
|
||||
|
||||
You can also ask questions and find related discussions on the tidal club forum: <https://club.tidalcycles.org/>
|
||||
|
||||
The discord and forum is shared with the haskell (tidal) and python (vortex) siblings of this project.
|
||||
@@ -1 +0,0 @@
|
||||
import{W as a,J as b}from"./index.deca0c1f.js";export{a as WebMidi,b as enableWebMidi};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
var d=Object.defineProperty;var g=(s,t,e)=>t in s?d(s,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):s[t]=e;var i=(s,t,e)=>(g(s,typeof t!="symbol"?t+"":t,e),e);import{K as S,L as y,N as T,S as B,M as I,O as R,R as W,Q as j}from"./index.deca0c1f.js";const k=s=>"("+s+")();",w=s=>URL.createObjectURL(new Blob([k(s)],{type:"text/javascript"})),m=s=>new Worker(w(s));class x{constructor(t,e=this.interval){i(this,"worker");i(this,"interval",.1);i(this,"tick",0);this.interval=e,this.worker=m(()=>{let r,o=null;const h=()=>{o&&(clearInterval(o),o=null)},c=()=>{if(h(),!r)throw new Error("no interval set! call worker.postMessage({interval}) before starting.");postMessage("tick"),o=setInterval(()=>postMessage("tick"),r*1e3)};self.onmessage=function(n){n.data=="start"?c():n.data.interval?(r=n.data.interval,o&&c()):n.data=="stop"&&h()}}),this.worker.postMessage({interval:e}),this.worker.onmessage=r=>{r.data==="tick"&&t(this.tick++,this.interval)}}start(){this.worker.postMessage("start")}stop(){this.worker.postMessage("stop"),this.tick=0}setInterval(t){this.worker.postMessage({interval:t})}}class v{constructor({audioContext:t,interval:e=.1,onEvent:r,latency:o=.1}){i(this,"worker");i(this,"pattern");i(this,"phase");i(this,"audioContext");i(this,"cps",1);this.audioContext=t,this.worker=new x((h,c)=>{const n=this.phase,l=this.phase+c*this.cps;this.phase=l,this.pattern.queryArc(n,l).forEach(a=>{var p,u;if(typeof((p=a.value)==null?void 0:p.cps)=="number"&&this.setCps((u=a.value)==null?void 0:u.cps),!!a.part.begin.equals(a.whole.begin)){if(a.context.onTrigger){const f=(a.whole.begin-n)/this.cps+this.audioContext.currentTime+o;a.context.onTrigger(f,a,this.audioContext.currentTime,this.cps)}r&&(r==null||r(a))}})},e)}start(){if(!this.pattern)throw new Error("Scheduler: no pattern set! call .setPattern first.");this.audioContext.resume(),this.phase=0,this.worker.start()}stop(){this.worker.stop()}setPattern(t){this.pattern=t}setCps(t=1){this.cps=t}}export{x as ClockWorker,v as Scheduler,S as getAudioContext,y as getCachedBuffer,T as getLoadedBuffer,B as getLoadedSamples,I as loadBuffer,R as loadGithubSamples,W as resetLoadedSamples,j as samples};
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{U as a}from"./index.deca0c1f.js";export{a as loadWebDirt};
|
||||
@@ -1 +0,0 @@
|
||||
import{T as e,o as n,a as o,x as t,u as h,y as l,d as p,f as r,g as m,t as y,w as u,k as c,l as d,m as g,b as i,c as f,n as w,q as b,r as k,j as x,h as D,p as v,e as T,s as j,i as q,v as C}from"./index.deca0c1f.js";export{e as Tone,n as adsr,o as amsynth,t as backgroundImage,h as cleanupDraw,l as cleanupUi,p as duosynth,r as fmsynth,m as getDefaultSynth,y as getDrawContext,u as hideHeader,c as highpass,d as lowpass,g as membrane,i as metal,f as monosynth,w as noise,b as osc,k as out,x as piano,D as players,v as pluck,T as polysynth,j as sampler,q as synth,C as vol};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
var d=Object.defineProperty,m=Object.defineProperties;var x=Object.getOwnPropertyDescriptors;var f=Object.getOwnPropertySymbols;var p=Object.prototype.hasOwnProperty,T=Object.prototype.propertyIsEnumerable;var u=(r,e,t)=>e in r?d(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t,v=(r,e)=>{for(var t in e||(e={}))p.call(e,t)&&u(r,t,e[t]);if(f)for(var t of f(e))T.call(e,t)&&u(r,t,e[t]);return r},w=(r,e)=>m(r,x(e));import{P as W}from"./index.deca0c1f.js";var n,b=!1;async function $(r=38400){if(!b){if(b=!0,n)return n;if("serial"in navigator){const e=await navigator.serial.requestPort();await e.open({baudRate:r});const t=new TextEncoderStream;t.readable.pipeTo(e.writable);const s=t.writable.getWriter();n=function(i){s.write(i)}}else throw"Webserial is not available in this browser."}}const j=.1;W.prototype.serial=function(...r){return this._withHap(e=>{n||$(...r);const t=(s,i,g)=>{var o="";if(typeof i.value=="object")if("action"in i.value){o+=i.value.action+"(";var c=!0;for(const[a,l]of Object.entries(i.value))a!=="action"&&(c?c=!1:o+=",",o+=`${a}:${l}`);o+=")"}else for(const[a,l]of Object.entries(i.value))o+=`${a}:${l};`;else o=i.value;const y=(s-g+j)*1e3;window.setTimeout(n,y,o)};return e.setContext(w(v({},e.context),{onTrigger:t}))})};export{$ as getWriter};
|
||||
+1806
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 150 KiB |
-20
@@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/x-icon" href="/assets/favicon.97356716.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Strudel REPL" />
|
||||
<!-- TODO: add manifest images -->
|
||||
<!-- <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> -->
|
||||
<title>Strudel REPL</title>
|
||||
<script type="module" crossorigin src="/assets/index.deca0c1f.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index.ea54525d.css">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
// this barrel export is currently only used to find undocumented exports
|
||||
export * from './packages/core/index.mjs';
|
||||
export * from './packages/csound/index.mjs';
|
||||
export * from './packages/embed/index.mjs';
|
||||
export * from './packages/eval/index.mjs';
|
||||
export * from './packages/midi/index.mjs';
|
||||
export * from './packages/mini/index.mjs';
|
||||
export * from './packages/osc/index.mjs';
|
||||
export * from './packages/react/index.mjs';
|
||||
export * from './packages/serial/index.mjs';
|
||||
export * from './packages/soundfonts/index.mjs';
|
||||
export * from './packages/tonal/index.mjs';
|
||||
export * from './packages/tone/index.mjs';
|
||||
export * from './packages/transpiler/index.mjs';
|
||||
export * from './packages/webaudio/index.mjs';
|
||||
export * from './packages/webdirt/index.mjs';
|
||||
export * from './packages/xen/index.mjs';
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"source": {
|
||||
"includePattern": ".+\\.(js(doc|x)?|mjs)$",
|
||||
"excludePattern": "node_modules|shift-parser|shift-reducer|shift-traverser|dist"
|
||||
},
|
||||
"plugins": ["plugins/markdown"],
|
||||
"opts": {
|
||||
"destination": "./out/",
|
||||
"recurse": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "independent"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
# my-patterns
|
||||
|
||||
This directory can be used to save your own patterns, which then get
|
||||
made into a pattern swatch.
|
||||
|
||||
0. fork and clone the strudel repository
|
||||
1. run `npm run setup` in the strudel folder
|
||||
1. Save one or more .txt files in this folder
|
||||
2. run `npm run repl` in the top-level strudel folder
|
||||
3. open `http://localhost:3000/swatch/` !
|
||||
|
||||
## deploy
|
||||
|
||||
1. in your fork, go to settings -> pages and select "Github Actions" as source
|
||||
2. edit `website/public/CNAME` to contain `<your-username>.github.io/strudel`
|
||||
3. edit `website/astro.config.mjs` to use site: `https://<your-username>.github.io` and base `/strudel`
|
||||
4. go to Actions -> `Build and Deploy` and click `Run workflow`
|
||||
5. view your patterns at `<your-username>.github.io/strudel/swatch/`
|
||||
|
||||
Alternatively, github pages allows you to use a custom domain, like https://mycooldomain.org/swatch/. [See their documentation for details](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site).
|
||||
Generated
+22021
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "@strudel.cycles/monorepo",
|
||||
"version": "0.5.0",
|
||||
"private": true,
|
||||
"description": "Port of tidalcycles to javascript",
|
||||
"scripts": {
|
||||
"pretest": "npm run jsdoc-json",
|
||||
"prebuild": "npm run jsdoc-json",
|
||||
"test": "vitest run --version",
|
||||
"test-ui": "vitest --ui",
|
||||
"test-coverage": "vitest --coverage",
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"setup": "npm i && npm run jsdoc-json && npm run bootstrap && cd website && npm i",
|
||||
"snapshot": "vitest run -u --silent",
|
||||
"repl": "cd website && npm run dev",
|
||||
"osc": "cd packages/osc && npm run server",
|
||||
"build": "cd website && npm run build",
|
||||
"preview": "cd website && npm run preview",
|
||||
"deploy": "NODE_DEBUG=gh-pages gh-pages -d out",
|
||||
"jsdoc": "jsdoc packages/ -c jsdoc.config.json",
|
||||
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json",
|
||||
"lint": "eslint . --ext mjs,js --quiet",
|
||||
"codeformat": "prettier --write .",
|
||||
"format-check": "prettier --check .",
|
||||
"report-undocumented": "npm run jsdoc-json && node undocumented.mjs > undocumented.json",
|
||||
"check": "npm run format-check && npm run lint && npm run test",
|
||||
"iclc": "cd paper && pandoc --template=pandoc/iclc.html --citeproc --number-sections iclc2023.md -o iclc2023.html && pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md -o iclc2023.pdf"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Alex McLean <alex@slab.org> (https://slab.org)",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://strudel.tidalcycles.org",
|
||||
"devDependencies": {
|
||||
"@vitest/ui": "^0.25.7",
|
||||
"c8": "^7.12.0",
|
||||
"canvas": "^2.11.0",
|
||||
"dependency-tree": "^9.0.0",
|
||||
"eslint": "^8.28.0",
|
||||
"events": "^3.3.0",
|
||||
"gh-pages": "^4.0.0",
|
||||
"jsdoc": "^3.6.10",
|
||||
"jsdoc-json": "^2.0.2",
|
||||
"jsdoc-to-markdown": "^7.1.1",
|
||||
"lerna": "^4.0.0",
|
||||
"prettier": "^2.8.1",
|
||||
"rollup-plugin-visualizer": "^5.8.1",
|
||||
"vitest": "^0.25.7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# Packages
|
||||
|
||||
Each folder represents one of the @strudel.cycles/* packages [published to npm](https://www.npmjs.com/org/strudel.cycles).
|
||||
|
||||
To understand how those pieces connect, refer to the [Technical Manual](https://github.com/tidalcycles/strudel/wiki/Technical-Manual) or the individual READMEs.
|
||||
|
||||
This is a graphical view of all the packages: [full screen](https://raw.githubusercontent.com/tidalcycles/strudel/main/dependencies.svg)
|
||||
|
||||

|
||||
|
||||
Generated with
|
||||
|
||||
```sh
|
||||
npx depcruise --include-only "^packages" -X "node_modules" --output-type dot packages | dot -T svg > dependencygraph.svg
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
examples
|
||||
@@ -0,0 +1,38 @@
|
||||
# @strudel.cycles/core
|
||||
|
||||
This package contains the bare essence of strudel.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm i @strudel.cycles/core --save
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import { sequence } from '@strudel.cycles/core';
|
||||
|
||||
const pattern = sequence('a', ['b', 'c']);
|
||||
|
||||
const events = pattern.queryArc(0, 1);
|
||||
|
||||
const spans = events.map(
|
||||
(event) => `${event.value}: ${event.whole.begin.toFraction()} - ${event.whole.end.toFraction()} `,
|
||||
);
|
||||
```
|
||||
|
||||
yields:
|
||||
|
||||
```log
|
||||
a: 0 - 1/2
|
||||
b: 1/2 - 3/4
|
||||
c: 3/4 - 1
|
||||
a: 1 - 3/2
|
||||
b: 3/2 - 7/4
|
||||
c: 7/4 - 2
|
||||
```
|
||||
|
||||
- [play with @strudel.cycles/core on codesandbox](https://codesandbox.io/s/strudel-core-test-qmz6qr?file=/src/index.js).
|
||||
- [open color pattern example](https://raw.githack.com/tidalcycles/strudel/package-examples/packages/core/examples/canvas.html)
|
||||
- [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/package-examples/packages/core/examples/metro.html)
|
||||
@@ -0,0 +1,67 @@
|
||||
import { controls, Pattern, getDrawContext, silence, register, pure } from './index.mjs';
|
||||
const { createParams } = controls;
|
||||
|
||||
let clearColor = '#22222210';
|
||||
|
||||
Pattern.prototype.animate = function ({ callback, sync = false, smear = 0.5 } = {}) {
|
||||
window.frame && cancelAnimationFrame(window.frame);
|
||||
const ctx = getDrawContext();
|
||||
const { clientWidth: ww, clientHeight: wh } = ctx.canvas;
|
||||
let smearPart = smear === 0 ? '99' : Number((1 - smear) * 100).toFixed(0);
|
||||
smearPart = smearPart.length === 1 ? `0${smearPart}` : smearPart;
|
||||
clearColor = `#200010${smearPart}`;
|
||||
const render = (t) => {
|
||||
let frame;
|
||||
/* if (sync) {
|
||||
t = scheduler.now();
|
||||
frame = this.queryArc(t, t);
|
||||
} else { */
|
||||
t = Math.round(t);
|
||||
frame = this.slow(1000).queryArc(t, t);
|
||||
// }
|
||||
ctx.fillStyle = clearColor;
|
||||
ctx.fillRect(0, 0, ww, wh);
|
||||
frame.forEach((f) => {
|
||||
let { x, y, w, h, s, r, a = 0, fill = 'darkseagreen' } = f.value;
|
||||
w *= ww;
|
||||
h *= wh;
|
||||
if (r !== undefined && a !== undefined) {
|
||||
const radians = a * 2 * Math.PI;
|
||||
const [cx, cy] = [(ww - w) / 2, (wh - h) / 2];
|
||||
x = cx + Math.cos(radians) * r * cx;
|
||||
y = cy + Math.sin(radians) * r * cy;
|
||||
} else {
|
||||
x *= ww - w;
|
||||
y *= wh - h;
|
||||
}
|
||||
const val = { ...f.value, x, y, w, h };
|
||||
ctx.fillStyle = fill;
|
||||
if (s === 'rect') {
|
||||
ctx.fillRect(x, y, w, h);
|
||||
} else if (s === 'ellipse') {
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(x + w / 2, y + h / 2, w / 2, h / 2, 0, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
callback && callback(ctx, val, f);
|
||||
});
|
||||
window.frame = requestAnimationFrame(render);
|
||||
};
|
||||
window.frame = requestAnimationFrame(render);
|
||||
return silence;
|
||||
};
|
||||
|
||||
export const { x, y, w, h, a, r, fill, smear } = createParams('x', 'y', 'w', 'h', 'a', 'r', 'fill', 'smear');
|
||||
|
||||
export const rescale = register('rescale', function (f, pat) {
|
||||
return pat.mul(x(f).w(f).y(f).h(f));
|
||||
});
|
||||
|
||||
export const moveXY = register('moveXY', function (dx, dy, pat) {
|
||||
return pat.add(x(dx).y(dy));
|
||||
});
|
||||
|
||||
export const zoomIn = register('zoomIn', function (f, pat) {
|
||||
const d = pure(1).sub(f).div(2);
|
||||
return pat.rescale(f).move(d, d);
|
||||
});
|
||||
@@ -0,0 +1,175 @@
|
||||
export const colorMap = {
|
||||
aliceblue: '#f0f8ff',
|
||||
antiquewhite: '#faebd7',
|
||||
aqua: '#00ffff',
|
||||
aquamarine: '#7fffd4',
|
||||
azure: '#f0ffff',
|
||||
beige: '#f5f5dc',
|
||||
bisque: '#ffe4c4',
|
||||
black: '#000000',
|
||||
blanchedalmond: '#ffebcd',
|
||||
blue: '#0000ff',
|
||||
blueviolet: '#8a2be2',
|
||||
brown: '#a52a2a',
|
||||
burlywood: '#deb887',
|
||||
cadetblue: '#5f9ea0',
|
||||
chartreuse: '#7fff00',
|
||||
chocolate: '#d2691e',
|
||||
coral: '#ff7f50',
|
||||
cornflowerblue: '#6495ed',
|
||||
cornsilk: '#fff8dc',
|
||||
crimson: '#dc143c',
|
||||
cyan: '#00ffff',
|
||||
darkblue: '#00008b',
|
||||
darkcyan: '#008b8b',
|
||||
darkgoldenrod: '#b8860b',
|
||||
darkgray: '#a9a9a9',
|
||||
darkgreen: '#006400',
|
||||
darkgrey: '#a9a9a9',
|
||||
darkkhaki: '#bdb76b',
|
||||
darkmagenta: '#8b008b',
|
||||
darkolivegreen: '#556b2f',
|
||||
darkorange: '#ff8c00',
|
||||
darkorchid: '#9932cc',
|
||||
darkred: '#8b0000',
|
||||
darksalmon: '#e9967a',
|
||||
darkseagreen: '#8fbc8f',
|
||||
darkslateblue: '#483d8b',
|
||||
darkslategray: '#2f4f4f',
|
||||
darkslategrey: '#2f4f4f',
|
||||
darkturquoise: '#00ced1',
|
||||
darkviolet: '#9400d3',
|
||||
deeppink: '#ff1493',
|
||||
deepskyblue: '#00bfff',
|
||||
dimgray: '#696969',
|
||||
dimgrey: '#696969',
|
||||
dodgerblue: '#1e90ff',
|
||||
firebrick: '#b22222',
|
||||
floralwhite: '#fffaf0',
|
||||
forestgreen: '#228b22',
|
||||
fuchsia: '#ff00ff',
|
||||
gainsboro: '#dcdcdc',
|
||||
ghostwhite: '#f8f8ff',
|
||||
gold: '#ffd700',
|
||||
goldenrod: '#daa520',
|
||||
gray: '#808080',
|
||||
green: '#008000',
|
||||
greenyellow: '#adff2f',
|
||||
grey: '#808080',
|
||||
honeydew: '#f0fff0',
|
||||
hotpink: '#ff69b4',
|
||||
indianred: '#cd5c5c',
|
||||
indigo: '#4b0082',
|
||||
ivory: '#fffff0',
|
||||
khaki: '#f0e68c',
|
||||
lavender: '#e6e6fa',
|
||||
lavenderblush: '#fff0f5',
|
||||
lawngreen: '#7cfc00',
|
||||
lemonchiffon: '#fffacd',
|
||||
lightblue: '#add8e6',
|
||||
lightcoral: '#f08080',
|
||||
lightcyan: '#e0ffff',
|
||||
lightgoldenrodyellow: '#fafad2',
|
||||
lightgray: '#d3d3d3',
|
||||
lightgreen: '#90ee90',
|
||||
lightgrey: '#d3d3d3',
|
||||
lightpink: '#ffb6c1',
|
||||
lightsalmon: '#ffa07a',
|
||||
lightseagreen: '#20b2aa',
|
||||
lightskyblue: '#87cefa',
|
||||
lightslategray: '#778899',
|
||||
lightslategrey: '#778899',
|
||||
lightsteelblue: '#b0c4de',
|
||||
lightyellow: '#ffffe0',
|
||||
lime: '#00ff00',
|
||||
limegreen: '#32cd32',
|
||||
linen: '#faf0e6',
|
||||
magenta: '#ff00ff',
|
||||
maroon: '#800000',
|
||||
mediumaquamarine: '#66cdaa',
|
||||
mediumblue: '#0000cd',
|
||||
mediumorchid: '#ba55d3',
|
||||
mediumpurple: '#9370db',
|
||||
mediumseagreen: '#3cb371',
|
||||
mediumslateblue: '#7b68ee',
|
||||
mediumspringgreen: '#00fa9a',
|
||||
mediumturquoise: '#48d1cc',
|
||||
mediumvioletred: '#c71585',
|
||||
midnightblue: '#191970',
|
||||
mintcream: '#f5fffa',
|
||||
mistyrose: '#ffe4e1',
|
||||
moccasin: '#ffe4b5',
|
||||
navajowhite: '#ffdead',
|
||||
navy: '#000080',
|
||||
oldlace: '#fdf5e6',
|
||||
olive: '#808000',
|
||||
olivedrab: '#6b8e23',
|
||||
orange: '#ffa500',
|
||||
orangered: '#ff4500',
|
||||
orchid: '#da70d6',
|
||||
palegoldenrod: '#eee8aa',
|
||||
palegreen: '#98fb98',
|
||||
paleturquoise: '#afeeee',
|
||||
palevioletred: '#db7093',
|
||||
papayawhip: '#ffefd5',
|
||||
peachpuff: '#ffdab9',
|
||||
peru: '#cd853f',
|
||||
pink: '#ffc0cb',
|
||||
plum: '#dda0dd',
|
||||
powderblue: '#b0e0e6',
|
||||
purple: '#800080',
|
||||
red: '#ff0000',
|
||||
rosybrown: '#bc8f8f',
|
||||
royalblue: '#4169e1',
|
||||
saddlebrown: '#8b4513',
|
||||
salmon: '#fa8072',
|
||||
sandybrown: '#f4a460',
|
||||
seagreen: '#2e8b57',
|
||||
seashell: '#fff5ee',
|
||||
sienna: '#a0522d',
|
||||
silver: '#c0c0c0',
|
||||
skyblue: '#87ceeb',
|
||||
slateblue: '#6a5acd',
|
||||
slategray: '#708090',
|
||||
slategrey: '#708090',
|
||||
snow: '#fffafa',
|
||||
springgreen: '#00ff7f',
|
||||
steelblue: '#4682b4',
|
||||
tan: '#d2b48c',
|
||||
teal: '#008080',
|
||||
thistle: '#d8bfd8',
|
||||
tomato: '#ff6347',
|
||||
turquoise: '#40e0d0',
|
||||
violet: '#ee82ee',
|
||||
wheat: '#f5deb3',
|
||||
white: '#ffffff',
|
||||
whitesmoke: '#f5f5f5',
|
||||
yellow: '#ffff00',
|
||||
yellowgreen: '#9acd32',
|
||||
};
|
||||
|
||||
export function convertColorToNumber(color) {
|
||||
// Convert color to lowercase for easier matching
|
||||
color = color.toLowerCase();
|
||||
|
||||
// If the color is a hex code, convert it to a number
|
||||
if (color[0] === '#') {
|
||||
return convertHexToNumber(color);
|
||||
}
|
||||
|
||||
// If the color is a named color, return the corresponding number
|
||||
if (colorMap[color] !== undefined) {
|
||||
return convertHexToNumber(colorMap[color]);
|
||||
}
|
||||
|
||||
// If the color is not recognized, return null
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function convertHexToNumber(hex) {
|
||||
// Remove the leading '#' from the hex code
|
||||
hex = hex.slice(1);
|
||||
|
||||
// Convert the hex code to a number
|
||||
return parseInt(hex, 16);
|
||||
}
|
||||
@@ -0,0 +1,847 @@
|
||||
/*
|
||||
controls.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/controls.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern, sequence } from './pattern.mjs';
|
||||
|
||||
const controls = {};
|
||||
const generic_params = [
|
||||
/**
|
||||
* Select a sound / sample by name.
|
||||
*
|
||||
* <details style={{display:'none'}}>
|
||||
* <summary>show all sounds</summary>
|
||||
*
|
||||
* 808 (6) 808bd (25) 808cy (25) 808hc (5) 808ht (5) 808lc (5) 808lt (5) 808mc (5) 808mt (5) 808oh (5) 808sd (25) 909 (1) ab (12) ade (10) ades2 (9) ades3 (7) ades4 (6) alex (2) alphabet (26) amencutup (32) armora (7) arp (2) arpy (11) auto (11) baa (7) baa2 (7) bass (4) bass0 (3) bass1 (30) bass2 (5) bass3 (11) bassdm (24) bassfoo (3) battles (2) bd (24) bend (4) bev (2) bin (2) birds (10) birds3 (19) bleep (13) blip (2) blue (2) bottle (13) breaks125 (2) breaks152 (1) breaks157 (1) breaks165 (1) breath (1) bubble (8) can (14) casio (3) cb (1) cc (6) chin (4) circus (3) clak (2) click (4) clubkick (5) co (4) coins (1) control (2) cosmicg (15) cp (2) cr (6) crow (4) d (4) db (13) diphone (38) diphone2 (12) dist (16) dork2 (4) dorkbot (2) dr (42) dr2 (6) dr55 (4) dr_few (8) drum (6) drumtraks (13) e (8) east (9) electro1 (13) em2 (6) erk (1) f (1) feel (7) feelfx (8) fest (1) fire (1) flick (17) fm (17) foo (27) future (17) gab (10) gabba (4) gabbaloud (4) gabbalouder (4) glasstap (3) glitch (8) glitch2 (8) gretsch (24) gtr (3) h (7) hand (17) hardcore (12) hardkick (6) haw (6) hc (6) hh (13) hh27 (13) hit (6) hmm (1) ho (6) hoover (6) house (8) ht (16) if (5) ifdrums (3) incoming (8) industrial (32) insect (3) invaders (18) jazz (8) jungbass (20) jungle (13) juno (12) jvbass (13) kicklinn (1) koy (2) kurt (7) latibro (8) led (1) less (4) lighter (33) linnhats (6) lt (16) made (7) made2 (1) mash (2) mash2 (4) metal (10) miniyeah (4) monsterb (6) moog (7) mouth (15) mp3 (4) msg (9) mt (16) mute (28) newnotes (15) noise (1) noise2 (8) notes (15) numbers (9) oc (4) odx (15) off (1) outdoor (6) pad (3) padlong (1) pebbles (1) perc (6) peri (15) pluck (17) popkick (10) print (11) proc (2) procshort (8) psr (30) rave (8) rave2 (4) ravemono (2) realclaps (4) reverbkick (1) rm (2) rs (1) sax (22) sd (2) seawolf (3) sequential (8) sf (18) sheffield (1) short (5) sid (12) sine (6) sitar (8) sn (52) space (18) speakspell (12) speech (7) speechless (10) speedupdown (9) stab (23) stomp (10) subroc3d (11) sugar (2) sundance (6) tabla (26) tabla2 (46) tablex (3) tacscan (22) tech (13) techno (7) tink (5) tok (4) toys (13) trump (11) ul (10) ulgab (5) uxay (3) v (6) voodoo (5) wind (10) wobble (1) world (3) xmas (1) yeah (31)
|
||||
*
|
||||
* <a href="https://tidalcycles.org/docs/configuration/Audio%20Samples/default_library" target="_blank">more info</a>
|
||||
*
|
||||
* </details>
|
||||
*
|
||||
* @name s
|
||||
* @param {string | Pattern} sound The sound / pattern of sounds to pick
|
||||
* @example
|
||||
* s("bd hh")
|
||||
*
|
||||
*/
|
||||
['s', 's', 'sound'],
|
||||
/**
|
||||
* The note or sample number to choose for a synth or sampleset
|
||||
* Note names currently not working yet, but will hopefully soon. Just stick to numbers for now
|
||||
*
|
||||
* @name n
|
||||
* @param {string | number | Pattern} value note name, note number or sample number
|
||||
* @example
|
||||
* s('superpiano').n("<0 1 2 3>").osc()
|
||||
* @example
|
||||
* s('superpiano').n("<c4 d4 e4 g4>").osc()
|
||||
* @example
|
||||
* n("0 1 2 3").s('east').osc()
|
||||
*/
|
||||
// also see https://github.com/tidalcycles/strudel/pull/63
|
||||
['f', 'n', 'The note or sample number to choose for a synth or sampleset'],
|
||||
['f', 'note', 'The note or pitch to play a sound or synth with'],
|
||||
//['s', 'toArg', 'for internal sound routing'],
|
||||
// ["f", "from", "for internal sound routing"),
|
||||
//['f', 'to', 'for internal sound routing'],
|
||||
/**
|
||||
* A pattern of numbers that speed up (or slow down) samples while they play. Currently only supported by osc / superdirt.
|
||||
*
|
||||
* @name accelerate
|
||||
* @param {number | Pattern} amount acceleration.
|
||||
* @superdirtOnly
|
||||
* @example
|
||||
* s("sax").accelerate("<0 1 2 4 8 16>").slow(2).osc()
|
||||
*
|
||||
*/
|
||||
['f', 'accelerate', 'a pattern of numbers that speed up (or slow down) samples while they play.'],
|
||||
/**
|
||||
* Controls the gain by an exponential amount.
|
||||
*
|
||||
* @name gain
|
||||
* @param {number | Pattern} amount gain.
|
||||
* @example
|
||||
* s("hh*8").gain(".4!2 1 .4!2 1 .4 1")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'gain',
|
||||
'a pattern of numbers that specify volume. Values less than 1 make the sound quieter. Values greater than 1 make the sound louder. For the linear equivalent, see @amp@.',
|
||||
],
|
||||
/**
|
||||
* Like {@link gain}, but linear.
|
||||
*
|
||||
* @name amp
|
||||
* @param {number | Pattern} amount gain.
|
||||
* @superdirtOnly
|
||||
* @example
|
||||
* s("bd*8").amp(".1*2 .5 .1*2 .5 .1 .5").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'amp', 'like @gain@, but linear.'],
|
||||
/**
|
||||
* A pattern of numbers to specify the attack time of an envelope applied to each sample.
|
||||
* [More info about envelopes](/learn/synths-samples-effects/#envelope)
|
||||
*
|
||||
* @name attack
|
||||
* @param {number | Pattern} attack time in seconds.
|
||||
* @example
|
||||
* note("c3 e3").attack("<0 .1 .5>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'attack',
|
||||
'a pattern of numbers to specify the attack time (in seconds) of an envelope applied to each sample.',
|
||||
],
|
||||
|
||||
/**
|
||||
* Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`.
|
||||
*
|
||||
* @name bank
|
||||
* @param {string | Pattern} bank the name of the bank
|
||||
* @example
|
||||
* s("bd sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd")
|
||||
*
|
||||
*/
|
||||
['f', 'bank', 'selects sound bank to use'],
|
||||
|
||||
/**
|
||||
* Gain envelope decay time = the time it takes after the attack time to reach the sustain level.
|
||||
* [More info about envelopes](/learn/synths-samples-effects/#envelope)
|
||||
*
|
||||
* @name decay
|
||||
* @param {number | Pattern} time decay time in seconds
|
||||
* @example
|
||||
* note("c3 e3").decay("<.1 .2 .3 .4>").sustain(0)
|
||||
*
|
||||
*/
|
||||
['f', 'decay', ''],
|
||||
/**
|
||||
* Gain envelope sustain level. [More info about envelopes](/learn/synths-samples-effects/#envelope)
|
||||
*
|
||||
* @name sustain
|
||||
* @param {number | Pattern} gain sustain level between 0 and 1
|
||||
* @example
|
||||
* note("c3 e3").decay(.2).sustain("<0 .1 .4 .6 1>")
|
||||
*
|
||||
*/
|
||||
['f', 'sustain', ''],
|
||||
/**
|
||||
* Gain envelope release time. [More info about envelopes](/learn/synths-samples-effects/#envelope)
|
||||
*
|
||||
* @name release
|
||||
* @param {number | Pattern} time release time in seconds
|
||||
* @example
|
||||
* note("c3 e3 g3 c4").release("<0 .1 .4 .6 1>/2")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'release',
|
||||
'a pattern of numbers to specify the release time (in seconds) of an envelope applied to each sample.',
|
||||
],
|
||||
[
|
||||
'f',
|
||||
'hold',
|
||||
'a pattern of numbers to specify the hold time (in seconds) of an envelope applied to each sample. Only takes effect if `attack` and `release` are also specified.',
|
||||
],
|
||||
// TODO: in tidal, it seems to be normalized
|
||||
/**
|
||||
* Sets the center frequency of the band-pass filter.
|
||||
*
|
||||
* @name bandf
|
||||
* @param {number | Pattern} frequency center frequency
|
||||
* @synonyms bpf
|
||||
* @example
|
||||
* s("bd sd,hh*3").bandf("<1000 2000 4000 8000>")
|
||||
*
|
||||
*/
|
||||
['f', 'bandf', 'A pattern of numbers from 0 to 1. Sets the center frequency of the band-pass filter.'],
|
||||
['f', 'bpf', ''],
|
||||
// TODO: in tidal, it seems to be normalized
|
||||
/**
|
||||
* Sets the q-factor of the band-pass filter
|
||||
*
|
||||
* @name bandq
|
||||
* @param {number | Pattern} q q factor
|
||||
* @synonyms bpq
|
||||
* @example
|
||||
* s("bd sd").bandf(500).bandq("<0 1 2 3>")
|
||||
*
|
||||
*/
|
||||
['f', 'bandq', 'a pattern of anumbers from 0 to 1. Sets the q-factor of the band-pass filter.'],
|
||||
['f', 'bpq', ''],
|
||||
/**
|
||||
* a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.
|
||||
*
|
||||
* @memberof Pattern
|
||||
* @name begin
|
||||
* @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample
|
||||
* @example
|
||||
* samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/Dirt-Samples/master/')
|
||||
* s("rave").begin("<0 .25 .5 .75>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'begin',
|
||||
'a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.',
|
||||
],
|
||||
/**
|
||||
* The same as .begin, but cuts off the end off each sample.
|
||||
*
|
||||
* @memberof Pattern
|
||||
* @name end
|
||||
* @param {number | Pattern} length 1 = whole sample, .5 = half sample, .25 = quarter sample etc..
|
||||
* @example
|
||||
* s("bd*2,oh*4").end("<.1 .2 .5 1>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'end',
|
||||
'the same as `begin`, but cuts the end off samples, shortening them; e.g. `0.75` to cut off the last quarter of each sample.',
|
||||
],
|
||||
/**
|
||||
* Loops the sample (from `begin` to `end`) the specified number of times.
|
||||
* Note that the tempo of the loop is not synced with the cycle tempo.
|
||||
*
|
||||
* @name loop
|
||||
* @param {number | Pattern} times How often the sample is looped
|
||||
* @example
|
||||
* s("bd").loop("<1 2 3 4>").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'loop', 'loops the sample (from `begin` to `end`) the specified number of times.'],
|
||||
// TODO: currently duplicated with "native" legato
|
||||
// TODO: superdirt legato will do more: https://youtu.be/dQPmE1WaD1k?t=419
|
||||
/**
|
||||
* a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.
|
||||
*
|
||||
* @name legato
|
||||
* @param {number | Pattern} duration between 0 and 1, where 1 is the length of the whole hap time
|
||||
* @example
|
||||
* "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>")
|
||||
*
|
||||
*/
|
||||
// ['f', 'legato', 'controls the amount of overlap between two adjacent sounds'],
|
||||
// ['f', 'clhatdecay', ''],
|
||||
/**
|
||||
* bit crusher effect.
|
||||
*
|
||||
* @name crush
|
||||
* @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction).
|
||||
* @example
|
||||
* s("<bd sd>,hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'crush',
|
||||
'bit crushing, a pattern of numbers from 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction).',
|
||||
],
|
||||
/**
|
||||
* fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers
|
||||
*
|
||||
* @name coarse
|
||||
* @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on.
|
||||
* @example
|
||||
* s("bd sd,hh*4").coarse("<1 4 8 16 32>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'coarse',
|
||||
'fake-resampling, a pattern of numbers for lowering the sample rate, i.e. 1 for original 2 for half, 3 for a third and so on.',
|
||||
],
|
||||
|
||||
/**
|
||||
* choose the channel the pattern is sent to in superdirt
|
||||
*
|
||||
* @name channel
|
||||
* @param {number | Pattern} channel channel number
|
||||
*
|
||||
*/
|
||||
['i', 'channel', 'choose the channel the pattern is sent to in superdirt'],
|
||||
/**
|
||||
* In the style of classic drum-machines, `cut` will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open.
|
||||
*
|
||||
* @name cut
|
||||
* @param {number | Pattern} group cut group number
|
||||
* @example
|
||||
* s("rd*4").cut(1)
|
||||
*
|
||||
*/
|
||||
[
|
||||
'i',
|
||||
'cut',
|
||||
'In the style of classic drum-machines, `cut` will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open.',
|
||||
],
|
||||
/**
|
||||
* Applies the cutoff frequency of the low-pass filter.
|
||||
*
|
||||
* @name cutoff
|
||||
* @param {number | Pattern} frequency audible between 0 and 20000
|
||||
* @synonyms lpf
|
||||
* @example
|
||||
* s("bd sd,hh*3").cutoff("<4000 2000 1000 500 200 100>")
|
||||
*
|
||||
*/
|
||||
['f', 'cutoff', 'a pattern of numbers from 0 to 1. Applies the cutoff frequency of the low-pass filter.'],
|
||||
['f', 'lpf'],
|
||||
/**
|
||||
* Applies the cutoff frequency of the high-pass filter.
|
||||
*
|
||||
* @name hcutoff
|
||||
* @param {number | Pattern} frequency audible between 0 and 20000
|
||||
* @synonyms hpf
|
||||
* @example
|
||||
* s("bd sd,hh*4").hcutoff("<4000 2000 1000 500 200 100>")
|
||||
*
|
||||
*/
|
||||
['f', 'hcutoff', ''],
|
||||
['f', 'hpf', ''],
|
||||
/**
|
||||
* Applies the resonance of the high-pass filter.
|
||||
*
|
||||
* @name hresonance
|
||||
* @param {number | Pattern} q resonance factor between 0 and 50
|
||||
* @synonyms hpq
|
||||
* @example
|
||||
* s("bd sd,hh*4").hcutoff(2000).hresonance("<0 10 20 30>")
|
||||
*
|
||||
*/
|
||||
['f', 'hpq', ''],
|
||||
['f', 'hresonance', ''],
|
||||
// TODO: add hpq synonym
|
||||
/**
|
||||
* Applies the cutoff frequency of the low-pass filter.
|
||||
*
|
||||
* @name resonance
|
||||
* @param {number | Pattern} q resonance factor between 0 and 50
|
||||
* @synonyms lpq
|
||||
* @example
|
||||
* s("bd sd,hh*4").cutoff(2000).resonance("<0 10 20 30>")
|
||||
*
|
||||
*/
|
||||
['f', 'lpq'],
|
||||
['f', 'resonance', ''],
|
||||
/**
|
||||
* DJ filter, below 0.5 is low pass filter, above is high pass filter.
|
||||
*
|
||||
* @name djf
|
||||
* @param {number | Pattern} cutoff below 0.5 is low pass filter, above is high pass filter
|
||||
* @example
|
||||
* n("0 3 7 [10,24]").s('superzow').octave(3).djf("<.5 .25 .5 .75>").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'djf', 'DJ filter, below 0.5 is low pass filter, above is high pass filter.'],
|
||||
// ['f', 'cutoffegint', ''],
|
||||
// TODO: does not seem to work
|
||||
/**
|
||||
* Sets the level of the delay signal.
|
||||
*
|
||||
* @name delay
|
||||
* @param {number | Pattern} level between 0 and 1
|
||||
* @example
|
||||
* s("bd").delay("<0 .25 .5 1>")
|
||||
*
|
||||
*/
|
||||
['f', 'delay', 'a pattern of numbers from 0 to 1. Sets the level of the delay signal.'],
|
||||
/**
|
||||
* Sets the level of the signal that is fed back into the delay.
|
||||
* Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it
|
||||
*
|
||||
* @name delayfeedback
|
||||
* @param {number | Pattern} feedback between 0 and 1
|
||||
* @example
|
||||
* s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>").slow(2)
|
||||
*
|
||||
*/
|
||||
['f', 'delayfeedback', 'a pattern of numbers from 0 to 1. Sets the amount of delay feedback.'],
|
||||
/**
|
||||
* Sets the time of the delay effect.
|
||||
*
|
||||
* @name delaytime
|
||||
* @param {number | Pattern} seconds between 0 and Infinity
|
||||
* @example
|
||||
* s("bd").delay(.25).delaytime("<.125 .25 .5 1>").slow(2)
|
||||
*
|
||||
*/
|
||||
['f', 'delaytime', 'a pattern of numbers from 0 to 1. Sets the length of the delay.'],
|
||||
/* // TODO: test
|
||||
* Specifies whether delaytime is calculated relative to cps.
|
||||
*
|
||||
* @name lock
|
||||
* @param {number | Pattern} enable When set to 1, delaytime is a direct multiple of a cycle.
|
||||
* @example
|
||||
* s("sd").delay().lock(1).osc()
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'lock',
|
||||
'A pattern of numbers. Specifies whether delaytime is calculated relative to cps. When set to 1, delaytime is a direct multiple of a cycle.',
|
||||
],
|
||||
/**
|
||||
* Set detune of oscillators. Works only with some synths, see <a target="_blank" href="https://tidalcycles.org/docs/patternlib/tutorials/synthesizers">tidal doc</a>
|
||||
*
|
||||
* @name detune
|
||||
* @param {number | Pattern} amount between 0 and 1
|
||||
* @example
|
||||
* n("0 3 7").s('superzow').octave(3).detune("<0 .25 .5 1 2>").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'detune', ''],
|
||||
/**
|
||||
* Set dryness of reverb. See {@link room} and {@link size} for more information about reverb.
|
||||
*
|
||||
* @name dry
|
||||
* @param {number | Pattern} dry 0 = wet, 1 = dry
|
||||
* @example
|
||||
* n("[0,3,7](3,8)").s("superpiano").room(.7).dry("<0 .5 .75 1>").osc()
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'dry',
|
||||
'when set to `1` will disable all reverb for this pattern. See `room` and `size` for more information about reverb.',
|
||||
],
|
||||
// TODO: does not seem to do anything
|
||||
/*
|
||||
* Used when using {@link begin}/{@link end} or {@link chop}/{@link striate} and friends, to change the fade out time of the 'grain' envelope.
|
||||
*
|
||||
* @name fadeTime
|
||||
* @param {number | Pattern} time between 0 and 1
|
||||
* @example
|
||||
* s("oh*4").end(.1).fadeTime("<0 .2 .4 .8>").osc()
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'fadeTime',
|
||||
"Used when using begin/end or chop/striate and friends, to change the fade out time of the 'grain' envelope.",
|
||||
],
|
||||
// TODO: see above
|
||||
[
|
||||
'f',
|
||||
'fadeInTime',
|
||||
'As with fadeTime, but controls the fade in time of the grain envelope. Not used if the grain begins at position 0 in the sample.',
|
||||
],
|
||||
/**
|
||||
* Set frequency of sound.
|
||||
*
|
||||
* @name freq
|
||||
* @param {number | Pattern} frequency in Hz. the audible range is between 20 and 20000 Hz
|
||||
* @example
|
||||
* freq("220 110 440 110").s("superzow").osc()
|
||||
* @example
|
||||
* freq("110".mul.out(".5 1.5 .6 [2 3]")).s("superzow").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'freq', ''],
|
||||
// TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate
|
||||
['f', 'gate', ''],
|
||||
// ['f', 'hatgrain', ''],
|
||||
// ['f', 'lagogo', ''],
|
||||
// ['f', 'lclap', ''],
|
||||
// ['f', 'lclaves', ''],
|
||||
// ['f', 'lclhat', ''],
|
||||
// ['f', 'lcrash', ''],
|
||||
// TODO:
|
||||
// https://tidalcycles.org/docs/reference/audio_effects/#leslie-1
|
||||
// https://tidalcycles.org/docs/reference/audio_effects/#leslie
|
||||
/**
|
||||
* Emulation of a Leslie speaker: speakers rotating in a wooden amplified cabinet.
|
||||
*
|
||||
* @name leslie
|
||||
* @param {number | Pattern} wet between 0 and 1
|
||||
* @example
|
||||
* n("0,4,7").s("supersquare").leslie("<0 .4 .6 1>").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'leslie', ''],
|
||||
/**
|
||||
* Rate of modulation / rotation for leslie effect
|
||||
*
|
||||
* @name lrate
|
||||
* @param {number | Pattern} rate 6.7 for fast, 0.7 for slow
|
||||
* @example
|
||||
* n("0,4,7").s("supersquare").leslie(1).lrate("<1 2 4 8>").osc()
|
||||
*
|
||||
*/
|
||||
// TODO: the rate seems to "lag" (in the example, 1 will be fast)
|
||||
['f', 'lrate', ''],
|
||||
/**
|
||||
* Physical size of the cabinet in meters. Be careful, it might be slightly larger than your computer. Affects the Doppler amount (pitch warble)
|
||||
*
|
||||
* @name lsize
|
||||
* @param {number | Pattern} meters somewhere between 0 and 1
|
||||
* @example
|
||||
* n("0,4,7").s("supersquare").leslie(1).lrate(2).lsize("<.1 .5 1>").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'lsize', ''],
|
||||
// ['f', 'lfo', ''],
|
||||
// ['f', 'lfocutoffint', ''],
|
||||
// ['f', 'lfodelay', ''],
|
||||
// ['f', 'lfoint', ''],
|
||||
// ['f', 'lfopitchint', ''],
|
||||
// ['f', 'lfoshape', ''],
|
||||
// ['f', 'lfosync', ''],
|
||||
// ['f', 'lhitom', ''],
|
||||
// ['f', 'lkick', ''],
|
||||
// ['f', 'llotom', ''],
|
||||
// ['f', 'lophat', ''],
|
||||
// ['f', 'lsnare', ''],
|
||||
['f', 'degree', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'mtranspose', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'ctranspose', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'harmonic', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'stepsPerOctave', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'octaveR', ''], // TODO: what is this? not found in tidal doc
|
||||
// TODO: why is this needed? what's the difference to late / early?
|
||||
[
|
||||
'f',
|
||||
'nudge',
|
||||
'Nudges events into the future by the specified number of seconds. Negative numbers work up to a point as well (due to internal latency)',
|
||||
],
|
||||
// TODO: the following doc is just a guess, it's not documented in tidal doc.
|
||||
/**
|
||||
* Sets the default octave of a synth.
|
||||
*
|
||||
* @name octave
|
||||
* @param {number | Pattern} octave octave number
|
||||
* @example
|
||||
* n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc()
|
||||
*/
|
||||
['i', 'octave', ''],
|
||||
['f', 'offset', ''], // TODO: what is this? not found in tidal doc
|
||||
// ['f', 'ophatdecay', ''],
|
||||
// TODO: example
|
||||
/**
|
||||
* An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects.
|
||||
*
|
||||
* @name orbit
|
||||
* @param {number | Pattern} number
|
||||
* @example
|
||||
* stack(
|
||||
* s("hh*3").delay(.5).delaytime(.25).orbit(1),
|
||||
* s("~ sd").delay(.5).delaytime(.125).orbit(2)
|
||||
* )
|
||||
*/
|
||||
[
|
||||
'i',
|
||||
'orbit',
|
||||
'a pattern of numbers. An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share hardware output bus offset and global effects, e.g. reverb and delay. The maximum number of orbits is specified in the superdirt startup, numbers higher than maximum will wrap around.',
|
||||
],
|
||||
['f', 'overgain', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'overshape', ''], // TODO: what is this? not found in tidal doc
|
||||
/**
|
||||
* Sets position in stereo.
|
||||
*
|
||||
* @name pan
|
||||
* @param {number | Pattern} pan between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel)
|
||||
* @example
|
||||
* s("[bd hh]*2").pan("<.5 1 .5 0>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'pan',
|
||||
'a pattern of numbers between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel)',
|
||||
],
|
||||
// TODO: this has no effect (see example)
|
||||
/*
|
||||
* Controls how much multichannel output is fanned out
|
||||
*
|
||||
* @name panspan
|
||||
* @param {number | Pattern} span between -inf and inf, negative is backwards ordering
|
||||
* @example
|
||||
* s("[bd hh]*2").pan("<.5 1 .5 0>").panspan("<0 .5 1>").osc()
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'panspan',
|
||||
'a pattern of numbers between -inf and inf, which controls how much multichannel output is fanned out (negative is backwards ordering)',
|
||||
],
|
||||
// TODO: this has no effect (see example)
|
||||
/*
|
||||
* Controls how much multichannel output is spread
|
||||
*
|
||||
* @name pansplay
|
||||
* @param {number | Pattern} spread between 0 and 1
|
||||
* @example
|
||||
* s("[bd hh]*2").pan("<.5 1 .5 0>").pansplay("<0 .5 1>").osc()
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'pansplay',
|
||||
'a pattern of numbers between 0.0 and 1.0, which controls the multichannel spread range (multichannel only)',
|
||||
],
|
||||
[
|
||||
'f',
|
||||
'panwidth',
|
||||
'a pattern of numbers between 0.0 and inf, which controls how much each channel is distributed over neighbours (multichannel only)',
|
||||
],
|
||||
[
|
||||
'f',
|
||||
'panorient',
|
||||
'a pattern of numbers between -1.0 and 1.0, which controls the relative position of the centre pan in a pair of adjacent speakers (multichannel only)',
|
||||
],
|
||||
// ['f', 'pitch1', ''],
|
||||
// ['f', 'pitch2', ''],
|
||||
// ['f', 'pitch3', ''],
|
||||
// ['f', 'portamento', ''],
|
||||
// TODO: LFO rate see https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare
|
||||
['f', 'rate', "used in SuperDirt softsynths as a control rate or 'speed'"],
|
||||
// TODO: slide param for certain synths
|
||||
['f', 'slide', ''],
|
||||
// TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare
|
||||
['f', 'semitone', ''],
|
||||
// TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano
|
||||
// ['f', 'velocity', ''],
|
||||
['f', 'voice', ''], // TODO: synth param
|
||||
/**
|
||||
* Sets the level of reverb.
|
||||
*
|
||||
* @name room
|
||||
* @param {number | Pattern} level between 0 and 1
|
||||
* @example
|
||||
* s("bd sd").room("<0 .2 .4 .6 .8 1>")
|
||||
*
|
||||
*/
|
||||
['f', 'room', 'a pattern of numbers from 0 to 1. Sets the level of reverb.'],
|
||||
/**
|
||||
* Sets the room size of the reverb, see {@link room}.
|
||||
*
|
||||
* @name roomsize
|
||||
* @synonyms size
|
||||
* @param {number | Pattern} size between 0 and 10
|
||||
* @example
|
||||
* s("bd sd").room(.8).roomsize("<0 1 2 4 8>")
|
||||
*
|
||||
*/
|
||||
// TODO: find out why :
|
||||
// s("bd sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc()
|
||||
// .. does not work. Is it because room is only one effect?
|
||||
[
|
||||
'f',
|
||||
'size',
|
||||
'a pattern of numbers from 0 to 1. Sets the perceptual size (reverb time) of the `room` to be used in reverb.',
|
||||
],
|
||||
[
|
||||
'f',
|
||||
'roomsize',
|
||||
'a pattern of numbers from 0 to 1. Sets the perceptual size (reverb time) of the `room` to be used in reverb.',
|
||||
],
|
||||
// ['f', 'sagogo', ''],
|
||||
// ['f', 'sclap', ''],
|
||||
// ['f', 'sclaves', ''],
|
||||
// ['f', 'scrash', ''],
|
||||
/**
|
||||
* Wave shaping distortion. CAUTION: it might get loud
|
||||
*
|
||||
* @name shape
|
||||
* @param {number | Pattern} distortion between 0 and 1
|
||||
* @example
|
||||
* s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'shape',
|
||||
'wave shaping distortion, a pattern of numbers from 0 for no distortion up to 1 for loads of distortion.',
|
||||
],
|
||||
/**
|
||||
* Changes the speed of sample playback, i.e. a cheap way of changing pitch.
|
||||
*
|
||||
* @name speed
|
||||
* @param {number | Pattern} speed -inf to inf, negative numbers play the sample backwards.
|
||||
* @example
|
||||
* s("bd").speed("<1 2 4 1 -2 -4>")
|
||||
* @example
|
||||
* speed("1 1.5*2 [2 1.1]").s("piano").clip(1)
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'speed',
|
||||
'a pattern of numbers which changes the speed of sample playback, i.e. a cheap way of changing pitch. Negative values will play the sample backwards!',
|
||||
],
|
||||
/**
|
||||
* Used in conjunction with {@link speed}, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`.
|
||||
*
|
||||
* @name unit
|
||||
* @param {number | string | Pattern} unit see description above
|
||||
* @example
|
||||
* speed("1 2 .5 3").s("bd").unit("c").osc()
|
||||
*
|
||||
*/
|
||||
[
|
||||
's',
|
||||
'unit',
|
||||
'used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`.',
|
||||
],
|
||||
/**
|
||||
* Made by Calum Gunn. Reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter. The SuperCollider manual defines Squiz as:
|
||||
*
|
||||
* "A simplistic pitch-raising algorithm. It's not meant to sound natural; its sound is reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter, depending on the input. The algorithm works by cutting the signal into fragments (delimited by upwards-going zero-crossings) and squeezing those fragments in the time domain (i.e. simply playing them back faster than they came in), leaving silences inbetween. All the parameters apart from memlen can be modulated."
|
||||
*
|
||||
* @name squiz
|
||||
* @param {number | Pattern} squiz Try passing multiples of 2 to it - 2, 4, 8 etc.
|
||||
* @example
|
||||
* squiz("2 4/2 6 [8 16]").s("bd").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'squiz', ''],
|
||||
['f', 'stutterdepth', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'stuttertime', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'timescale', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'timescalewin', ''], // TODO: what is this? not found in tidal doc
|
||||
// ['f', 'tomdecay', ''],
|
||||
// ['f', 'vcfegint', ''],
|
||||
// ['f', 'vcoegint', ''],
|
||||
// TODO: Use a rest (~) to override the effect <- vowel
|
||||
/**
|
||||
*
|
||||
* Formant filter to make things sound like vowels.
|
||||
*
|
||||
* @name vowel
|
||||
* @param {string | Pattern} vowel You can use a e i o u.
|
||||
* @example
|
||||
* note("c2 <eb2 <g2 g1>>").s('sawtooth')
|
||||
* .vowel("<a e i <o u>>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
's',
|
||||
'vowel',
|
||||
'formant filter to make things sound like vowels, a pattern of either `a`, `e`, `i`, `o` or `u`. Use a rest (`~`) for no effect.',
|
||||
],
|
||||
/* // TODO: find out how it works
|
||||
* Made by Calum Gunn. Divides an audio stream into tiny segments, using the signal's zero-crossings as segment boundaries, and discards a fraction of them. Takes a number between 1 and 100, denoted the percentage of segments to drop. The SuperCollider manual describes the Waveloss effect this way:
|
||||
*
|
||||
* Divide an audio stream into tiny segments, using the signal's zero-crossings as segment boundaries, and discard a fraction of them (i.e. replace them with silence of the same length). The technique was described by Trevor Wishart in a lecture. Parameters: the filter drops drop out of out of chunks. mode can be 1 to drop chunks in a simple deterministic fashion (e.g. always dropping the first 30 out of a set of 40 segments), or 2 to drop chunks randomly but in an appropriate proportion.)
|
||||
*
|
||||
* mode: ?
|
||||
* waveloss: ?
|
||||
*
|
||||
* @name waveloss
|
||||
*/
|
||||
['f', 'waveloss', ''],
|
||||
// TODO: midi effects?
|
||||
['f', 'dur', ''],
|
||||
// ['f', 'modwheel', ''],
|
||||
['f', 'expression', ''],
|
||||
['f', 'sustainpedal', ''],
|
||||
/* // TODO: doesn't seem to do anything
|
||||
*
|
||||
* Tremolo Audio DSP effect
|
||||
*
|
||||
* @name tremolodepth
|
||||
* @param {number | Pattern} depth between 0 and 1
|
||||
* @example
|
||||
* n("0,4,7").tremolodepth("<0 .3 .6 .9>").osc()
|
||||
*
|
||||
*/
|
||||
// TODO: tremdp alias
|
||||
['f', 'tremolodepth', "Tremolo Audio DSP effect | params are 'tremolorate' and 'tremolodepth'"],
|
||||
['f', 'tremolorate', "Tremolo Audio DSP effect | params are 'tremolorate' and 'tremolodepth'"],
|
||||
// TODO: doesn't seem to do anything
|
||||
['f', 'phaserdepth', "Phaser Audio DSP effect | params are 'phaserrate' and 'phaserdepth'"],
|
||||
['f', 'phaserrate', "Phaser Audio DSP effect | params are 'phaserrate' and 'phaserdepth'"],
|
||||
|
||||
['f', 'fshift', 'frequency shifter'],
|
||||
['f', 'fshiftnote', 'frequency shifter'],
|
||||
['f', 'fshiftphase', 'frequency shifter'],
|
||||
|
||||
['f', 'triode', 'tube distortion'],
|
||||
['f', 'krush', 'shape/bass enhancer'],
|
||||
['f', 'kcutoff', ''],
|
||||
['f', 'octer', 'octaver effect'],
|
||||
['f', 'octersub', 'octaver effect'],
|
||||
['f', 'octersubsub', 'octaver effect'],
|
||||
['f', 'ring', 'ring modulation'],
|
||||
['f', 'ringf', 'ring modulation'],
|
||||
['f', 'ringdf', 'ring modulation'],
|
||||
['f', 'distort', 'noisy fuzzy distortion'],
|
||||
['f', 'freeze', 'Spectral freeze'],
|
||||
['f', 'xsdelay', ''],
|
||||
['f', 'tsdelay', ''],
|
||||
['f', 'real', 'Spectral conform'],
|
||||
['f', 'imag', ''],
|
||||
['f', 'enhance', 'Spectral enhance'],
|
||||
['f', 'partials', ''],
|
||||
['f', 'comb', 'Spectral comb'],
|
||||
['f', 'smear', 'Spectral smear'],
|
||||
['f', 'scram', 'Spectral scramble'],
|
||||
['f', 'binshift', 'Spectral binshift'],
|
||||
['f', 'hbrick', 'High pass sort of spectral filter'],
|
||||
['f', 'lbrick', 'Low pass sort of spectral filter'],
|
||||
['f', 'midichan', ''],
|
||||
['f', 'control', ''],
|
||||
['f', 'ccn', ''],
|
||||
['f', 'ccv', ''],
|
||||
['f', 'polyTouch', ''],
|
||||
['f', 'midibend', ''],
|
||||
['f', 'miditouch', ''],
|
||||
['f', 'ctlNum', ''],
|
||||
['f', 'frameRate', ''],
|
||||
['f', 'frames', ''],
|
||||
['f', 'hours', ''],
|
||||
['s', 'midicmd', ''],
|
||||
['f', 'minutes', ''],
|
||||
['f', 'progNum', ''],
|
||||
['f', 'seconds', ''],
|
||||
['f', 'songPtr', ''],
|
||||
['f', 'uid', ''],
|
||||
['f', 'val', ''],
|
||||
['f', 'cps', ''],
|
||||
['f', 'clip', ''],
|
||||
];
|
||||
|
||||
// TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13
|
||||
|
||||
const _name = (name, ...pats) => sequence(...pats).withValue((x) => ({ [name]: x }));
|
||||
|
||||
const _setter = (func, name) =>
|
||||
function (...pats) {
|
||||
if (!pats.length) {
|
||||
return this.fmap((value) => ({ [name]: value }));
|
||||
}
|
||||
return this.set(func(...pats));
|
||||
};
|
||||
|
||||
generic_params.forEach(([type, name, description]) => {
|
||||
controls[name] = function (...pats) {
|
||||
const result = _name(name, ...pats);
|
||||
|
||||
// Add a function for composing this control with another pattern
|
||||
result.__as_function = function (pat) {
|
||||
return pat[name](...pats);
|
||||
};
|
||||
return result;
|
||||
};
|
||||
Pattern.__registered.push(name);
|
||||
Pattern.prototype[name] = _setter(controls[name], name);
|
||||
});
|
||||
|
||||
// create custom param
|
||||
controls.createParam = (name) => {
|
||||
const func = (...pats) => _name(name, ...pats);
|
||||
Pattern.__registered.push(name);
|
||||
Pattern.prototype[name] = _setter(func, name);
|
||||
return (...pats) => _name(name, ...pats);
|
||||
};
|
||||
|
||||
controls.createParams = (...names) =>
|
||||
names.reduce((acc, name) => Object.assign(acc, { [name]: controls.createParam(name) }), {});
|
||||
|
||||
export default controls;
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
cyclist.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/cyclist.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import createClock from './zyklus.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
|
||||
export class Cyclist {
|
||||
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) {
|
||||
this.started = false;
|
||||
this.cps = 1; // TODO
|
||||
this.phase = 0;
|
||||
this.getTime = getTime;
|
||||
this.onToggle = onToggle;
|
||||
this.latency = latency;
|
||||
const round = (x) => Math.round(x * 1000) / 1000;
|
||||
this.clock = createClock(
|
||||
getTime,
|
||||
(phase, duration, tick) => {
|
||||
if (tick === 0) {
|
||||
this.origin = phase;
|
||||
}
|
||||
const begin = round(phase - this.origin);
|
||||
this.phase = begin - latency;
|
||||
const end = round(begin + duration);
|
||||
const time = getTime();
|
||||
try {
|
||||
const haps = this.pattern.queryArc(begin, end); // get Haps
|
||||
haps.forEach((hap) => {
|
||||
if (hap.part.begin.equals(hap.whole.begin)) {
|
||||
const deadline = hap.whole.begin + this.origin - time + latency;
|
||||
const duration = hap.duration * 1;
|
||||
onTrigger?.(hap, deadline, duration);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
logger(`[cyclist] error: ${e.message}`);
|
||||
onError?.(e);
|
||||
}
|
||||
}, // called slightly before each cycle
|
||||
interval, // duration of each cycle
|
||||
);
|
||||
}
|
||||
getPhase() {
|
||||
return this.getTime() - this.origin - this.latency;
|
||||
}
|
||||
now() {
|
||||
return this.getTime() - this.origin + this.clock.minLatency;
|
||||
}
|
||||
setStarted(v) {
|
||||
this.started = v;
|
||||
this.onToggle?.(v);
|
||||
}
|
||||
start() {
|
||||
if (!this.pattern) {
|
||||
throw new Error('Scheduler: no pattern set! call .setPattern first.');
|
||||
}
|
||||
logger('[cyclist] start');
|
||||
this.clock.start();
|
||||
this.setStarted(true);
|
||||
}
|
||||
pause() {
|
||||
logger('[cyclist] pause');
|
||||
this.clock.pause();
|
||||
this.setStarted(false);
|
||||
}
|
||||
stop() {
|
||||
logger('[cyclist] stop');
|
||||
this.clock.stop();
|
||||
this.setStarted(false);
|
||||
}
|
||||
setPattern(pat, autostart = false) {
|
||||
this.pattern = pat;
|
||||
if (autostart && !this.started) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
setCps(cps = 1) {
|
||||
this.cps = cps;
|
||||
}
|
||||
log(begin, end, haps) {
|
||||
const onsets = haps.filter((h) => h.hasOnset());
|
||||
console.log(`${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
draw.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/tone/draw.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern, getTime, State, TimeSpan } from './index.mjs';
|
||||
|
||||
export const getDrawContext = (id = 'test-canvas') => {
|
||||
let canvas = document.querySelector('#' + id);
|
||||
if (!canvas) {
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.id = id;
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
canvas.style = 'pointer-events:none;width:100%;height:100%;position:fixed;top:0;left:0;z-index:5';
|
||||
document.body.prepend(canvas);
|
||||
}
|
||||
return canvas.getContext('2d');
|
||||
};
|
||||
|
||||
Pattern.prototype.draw = function (callback, { from, to, onQuery }) {
|
||||
if (window.strudelAnimation) {
|
||||
cancelAnimationFrame(window.strudelAnimation);
|
||||
}
|
||||
const ctx = getDrawContext();
|
||||
let cycle,
|
||||
events = [];
|
||||
const animate = (time) => {
|
||||
const t = getTime();
|
||||
if (from !== undefined && to !== undefined) {
|
||||
const currentCycle = Math.floor(t);
|
||||
if (cycle !== currentCycle) {
|
||||
cycle = currentCycle;
|
||||
const begin = currentCycle + from;
|
||||
const end = currentCycle + to;
|
||||
setTimeout(() => {
|
||||
events = this.query(new State(new TimeSpan(begin, end)))
|
||||
.filter(Boolean)
|
||||
.filter((event) => event.part.begin.equals(event.whole.begin));
|
||||
onQuery?.(events);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
callback(ctx, events, t, time);
|
||||
window.strudelAnimation = requestAnimationFrame(animate);
|
||||
};
|
||||
requestAnimationFrame(animate);
|
||||
return this;
|
||||
};
|
||||
|
||||
export const cleanupDraw = (clearScreen = true) => {
|
||||
const ctx = getDrawContext();
|
||||
clearScreen && ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
|
||||
if (window.strudelAnimation) {
|
||||
cancelAnimationFrame(window.strudelAnimation);
|
||||
}
|
||||
if (window.strudelScheduler) {
|
||||
clearInterval(window.strudelScheduler);
|
||||
}
|
||||
};
|
||||
|
||||
Pattern.prototype.onPaint = function (onPaint) {
|
||||
// this is evil! TODO: add pattern.context
|
||||
this.context = { onPaint };
|
||||
return this;
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
drawLine.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/drawLine.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Fraction, { gcd } from './fraction.mjs';
|
||||
|
||||
/**
|
||||
* Intended for a debugging, drawLine renders the pattern as a string, where each character represents the same time span.
|
||||
* Should only be used with single characters as values, otherwise the character slots will be messed up.
|
||||
* Character legend:
|
||||
*
|
||||
* - "|" cycle separator
|
||||
* - "-" hold previous value
|
||||
* - "." silence
|
||||
*
|
||||
* @param {Pattern} pattern the pattern to use
|
||||
* @param {number} chars max number of characters (approximately)
|
||||
* @returns string
|
||||
* @example
|
||||
* const line = drawLine("0 [1 2 3]", 10); // |0--123|0--123
|
||||
* console.log(line);
|
||||
* silence;
|
||||
*/
|
||||
function drawLine(pat, chars = 60) {
|
||||
let cycle = 0;
|
||||
let pos = Fraction(0);
|
||||
let lines = [''];
|
||||
let emptyLine = ''; // this will be the "reference" empty line, which will be copied into extra lines
|
||||
while (lines[0].length < chars) {
|
||||
const haps = pat.queryArc(cycle, cycle + 1);
|
||||
const durations = haps.filter((hap) => hap.hasOnset()).map((hap) => hap.duration);
|
||||
const charFraction = gcd(...durations);
|
||||
const totalSlots = charFraction.inverse(); // number of character slots for the current cycle
|
||||
lines = lines.map((line) => line + '|'); // add pipe character before each cycle
|
||||
emptyLine += '|';
|
||||
for (let i = 0; i < totalSlots; i++) {
|
||||
const [begin, end] = [pos, pos.add(charFraction)];
|
||||
const matches = haps.filter((hap) => hap.whole.begin.lte(begin) && hap.whole.end.gte(end));
|
||||
const missingLines = matches.length - lines.length;
|
||||
if (missingLines > 0) {
|
||||
lines = lines.concat(Array(missingLines).fill(emptyLine));
|
||||
}
|
||||
lines = lines.map((line, i) => {
|
||||
const hap = matches[i];
|
||||
if (hap) {
|
||||
const isOnset = hap.whole.begin.eq(begin);
|
||||
const char = isOnset ? '' + hap.value : '-';
|
||||
return line + char;
|
||||
}
|
||||
return line + '.';
|
||||
});
|
||||
emptyLine += '.';
|
||||
pos = pos.add(charFraction);
|
||||
}
|
||||
cycle++;
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export default drawLine;
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
euclid.mjs - Bjorklund/Euclidean/Diaspora rhythms
|
||||
Copyright (C) 2023 Rohan Drape and strudel contributors
|
||||
|
||||
See <https://github.com/tidalcycles/strudel/blob/main/packages/core/euclid.mjs> for authors of this file.
|
||||
|
||||
The Bjorklund algorithm implementation is ported from the Haskell Music Theory Haskell module by Rohan Drape -
|
||||
https://rohandrape.net/?t=hmt
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern, timeCat, register, silence } from './pattern.mjs';
|
||||
import { rotate, flatten } from './util.mjs';
|
||||
import Fraction from './fraction.mjs';
|
||||
|
||||
const splitAt = function (index, value) {
|
||||
return [value.slice(0, index), value.slice(index)];
|
||||
};
|
||||
|
||||
const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i]));
|
||||
|
||||
const left = function (n, x) {
|
||||
const [ons, offs] = n;
|
||||
const [xs, ys] = x;
|
||||
const [_xs, __xs] = splitAt(offs, xs);
|
||||
return [
|
||||
[offs, ons - offs],
|
||||
[zipWith((a, b) => a.concat(b), _xs, ys), __xs],
|
||||
];
|
||||
};
|
||||
|
||||
const right = function (n, x) {
|
||||
const [ons, offs] = n;
|
||||
const [xs, ys] = x;
|
||||
const [_ys, __ys] = splitAt(ons, ys);
|
||||
const result = [
|
||||
[ons, offs - ons],
|
||||
[zipWith((a, b) => a.concat(b), xs, _ys), __ys],
|
||||
];
|
||||
return result;
|
||||
};
|
||||
|
||||
const _bjork = function (n, x) {
|
||||
const [ons, offs] = n;
|
||||
return Math.min(ons, offs) <= 1 ? [n, x] : _bjork(...(ons > offs ? left(n, x) : right(n, x)));
|
||||
};
|
||||
|
||||
export const bjork = function (ons, steps) {
|
||||
const offs = steps - ons;
|
||||
const x = Array(ons).fill([1]);
|
||||
const y = Array(offs).fill([0]);
|
||||
const result = _bjork([ons, offs], [x, y]);
|
||||
return flatten(result[1][0]).concat(flatten(result[1][1]));
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the structure of the pattern to form an euclidean rhythm.
|
||||
* Euclidian rhythms are rhythms obtained using the greatest common
|
||||
* divisor of two numbers. They were described in 2004 by Godfried
|
||||
* Toussaint, a canadian computer scientist. Euclidian rhythms are
|
||||
* really useful for computer/algorithmic music because they can
|
||||
* describe a large number of rhythms with a couple of numbers.
|
||||
*
|
||||
* @memberof Pattern
|
||||
* @name euclid
|
||||
* @param {number} pulses the number of onsets / beats
|
||||
* @param {number} steps the number of steps to fill
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* // The Cuban tresillo pattern.
|
||||
* note("c3").euclid(3,8)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence.
|
||||
* @memberof Pattern
|
||||
* @name euclidRot
|
||||
* @param {number} pulses the number of onsets / beats
|
||||
* @param {number} steps the number of steps to fill
|
||||
* @param {number} rotation offset in steps
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* // A Samba rhythm necklace from Brazil
|
||||
* note("c3").euclidRot(3,16,14)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @example // A thirteenth century Persian rhythm called Khafif-e-ramal.
|
||||
* note("c3").euclid(2,5)
|
||||
* @example // The archetypal pattern of the Cumbia from Colombia, as well as a Calypso rhythm from Trinidad.
|
||||
* note("c3").euclid(3,4)
|
||||
* @example // Another thirteenth century Persian rhythm by the name of Khafif-e-ramal, as well as a Rumanian folk-dance rhythm.
|
||||
* note("c3").euclidRot(3,5,2)
|
||||
* @example // A Ruchenitza rhythm used in a Bulgarian folk-dance.
|
||||
* note("c3").euclid(3,7)
|
||||
* @example // The Cuban tresillo pattern.
|
||||
* note("c3").euclid(3,8)
|
||||
* @example // Another Ruchenitza Bulgarian folk-dance rhythm.
|
||||
* note("c3").euclid(4,7)
|
||||
* @example // The Aksak rhythm of Turkey.
|
||||
* note("c3").euclid(4,9)
|
||||
* @example // The metric pattern used by Frank Zappa in his piece titled Outside Now.
|
||||
* note("c3").euclid(4,11)
|
||||
* @example // Yields the York-Samai pattern, a popular Arab rhythm.
|
||||
* note("c3").euclid(5,6)
|
||||
* @example // The Nawakhat pattern, another popular Arab rhythm.
|
||||
* note("c3").euclid(5,7)
|
||||
* @example // The Cuban cinquillo pattern.
|
||||
* note("c3").euclid(5,8)
|
||||
* @example // A popular Arab rhythm called Agsag-Samai.
|
||||
* note("c3").euclid(5,9)
|
||||
* @example // The metric pattern used by Moussorgsky in Pictures at an Exhibition.
|
||||
* note("c3").euclid(5,11)
|
||||
* @example // The Venda clapping pattern of a South African children’s song.
|
||||
* note("c3").euclid(5,12)
|
||||
* @example // The Bossa-Nova rhythm necklace of Brazil.
|
||||
* note("c3").euclid(5,16)
|
||||
* @example // A typical rhythm played on the Bendir (frame drum).
|
||||
* note("c3").euclid(7,8)
|
||||
* @example // A common West African bell pattern.
|
||||
* note("c3").euclid(7,12)
|
||||
* @example // A Samba rhythm necklace from Brazil.
|
||||
* note("c3").euclidRot(7,16,14)
|
||||
* @example // A rhythm necklace used in the Central African Republic.
|
||||
* note("c3").euclid(9,16)
|
||||
* @example // A rhythm necklace of the Aka Pygmies of Central Africa.
|
||||
* note("c3").euclidRot(11,24,14)
|
||||
* @example // Another rhythm necklace of the Aka Pygmies of the upper Sangha.
|
||||
* note("c3").euclidRot(13,24,5)
|
||||
*/
|
||||
|
||||
const _euclidRot = function (pulses, steps, rotation) {
|
||||
const b = bjork(pulses, steps);
|
||||
if (rotation) {
|
||||
return rotate(b, -rotation);
|
||||
}
|
||||
return b;
|
||||
};
|
||||
|
||||
export const euclid = register('euclid', function (pulses, steps, pat) {
|
||||
return pat.struct(_euclidRot(pulses, steps, 0));
|
||||
});
|
||||
|
||||
export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], function (pulses, steps, rotation, pat) {
|
||||
return pat.struct(_euclidRot(pulses, steps, rotation));
|
||||
});
|
||||
|
||||
/**
|
||||
* Similar to `euclid`, but each pulse is held until the next pulse,
|
||||
* so there will be no gaps.
|
||||
* @name euclidLegato
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* n("g2").decay(.1).sustain(.3).euclidLegato(3,8)
|
||||
*/
|
||||
|
||||
const _euclidLegato = function (pulses, steps, rotation, pat) {
|
||||
if (pulses < 1) {
|
||||
return silence;
|
||||
}
|
||||
const bin_pat = _euclidRot(pulses, steps, rotation);
|
||||
const gapless = bin_pat
|
||||
.join('')
|
||||
.split('1')
|
||||
.slice(1)
|
||||
.map((s) => [s.length + 1, true]);
|
||||
return pat.struct(timeCat(...gapless));
|
||||
};
|
||||
|
||||
export const euclidLegato = register(['euclidLegato'], function (pulses, steps, pat) {
|
||||
return _euclidLegato(pulses, steps, 0, pat);
|
||||
});
|
||||
|
||||
export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) {
|
||||
return _euclidLegato(pulses, steps, rotation, pat);
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
evaluate.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/eval/evaluate.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { isPattern } from './index.mjs';
|
||||
|
||||
let scoped = false;
|
||||
export const evalScope = async (...args) => {
|
||||
if (scoped) {
|
||||
console.warn('evalScope was called more than once.');
|
||||
}
|
||||
scoped = true;
|
||||
const results = await Promise.allSettled(args);
|
||||
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
|
||||
results.forEach((result, i) => {
|
||||
if (result.status === 'rejected') {
|
||||
console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason);
|
||||
}
|
||||
});
|
||||
// Object.assign(globalThis, ...modules);
|
||||
// below is a fix for above commented out line
|
||||
// same error as https://github.com/vitest-dev/vitest/issues/1807 when running this on astro server
|
||||
modules.forEach((module) => {
|
||||
Object.entries(module).forEach(([name, value]) => {
|
||||
globalThis[name] = value;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function safeEval(str, options = {}) {
|
||||
const { wrapExpression = true, wrapAsync = true } = options;
|
||||
if (wrapExpression) {
|
||||
str = `{${str}}`;
|
||||
}
|
||||
if (wrapAsync) {
|
||||
str = `(async ()=>${str})()`;
|
||||
}
|
||||
const body = `"use strict";return (${str})`;
|
||||
return Function(body)();
|
||||
}
|
||||
|
||||
export const evaluate = async (code, transpiler) => {
|
||||
if (!scoped) {
|
||||
await evalScope(); // at least scope Pattern.prototype.boostrap
|
||||
}
|
||||
if (transpiler) {
|
||||
code = transpiler(code); // transform syntactically correct js code to semantically usable code
|
||||
}
|
||||
// if no transpiler is given, we expect a single instruction (!wrapExpression)
|
||||
const options = { wrapExpression: !!transpiler };
|
||||
let evaluated = await safeEval(code, options);
|
||||
if (!isPattern(evaluated)) {
|
||||
console.log('evaluated', evaluated);
|
||||
const message = `got "${typeof evaluated}" instead of pattern`;
|
||||
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
|
||||
}
|
||||
return { mode: 'javascript', pattern: evaluated };
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<input
|
||||
type="text"
|
||||
id="text"
|
||||
value="cat('a', 'b')"
|
||||
style="width: 100%; font-size: 2em; outline: none; margin-bottom: 10px"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<div id="output"></div>
|
||||
<script type="module">
|
||||
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.0.2');
|
||||
Object.assign(window, strudel); // assign all strudel functions to global scope to use with eval
|
||||
const input = document.getElementById('text');
|
||||
const getEvents = () => {
|
||||
const code = document.getElementById('text').value;
|
||||
const pattern = eval(code);
|
||||
const events = pattern.firstCycle();
|
||||
console.log(code, '->', events);
|
||||
document.getElementById('output').innerHTML = events.map((e) => e.show()).join('<br/>');
|
||||
};
|
||||
getEvents();
|
||||
input.addEventListener('input', () => getEvents());
|
||||
</script>
|
||||
<p>
|
||||
This page shows how skypack can be used to import strudel core directly into a simple html page. <br />
|
||||
No server, no bundler and no build setup is needed to run this!
|
||||
</p>
|
||||
@@ -0,0 +1,34 @@
|
||||
<body style="margin: 0">
|
||||
<input
|
||||
type="text"
|
||||
id="text"
|
||||
value="cat('orange', 'indigo')"
|
||||
style="width: 100%; font-size: 2em; background: black; color: white; outline: none; position: absolute; top: 0"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script type="module">
|
||||
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.0.2');
|
||||
// this adds all strudel functions to the global scope, to be used by eval
|
||||
Object.assign(window, strudel);
|
||||
// setup elements
|
||||
const input = document.getElementById('text');
|
||||
const canvas = document.getElementById('canvas');
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
const ctx = canvas.getContext('2d');
|
||||
input.focus(); // autofocus
|
||||
input.setSelectionRange(input.value.length, input.value.length); // move cursor to end
|
||||
paint(input.value); // initial paint
|
||||
input.addEventListener('input', (e) => paint(e.target.value)); // repaint on input
|
||||
|
||||
function paint(code) {
|
||||
const pattern = eval(code); // run code
|
||||
const events = pattern.firstCycle(); // query first cycle
|
||||
events.forEach((event) => {
|
||||
ctx.fillStyle = event.value;
|
||||
ctx.fillRect(event.whole.begin * canvas.width, 0, event.duration.valueOf() * canvas.width, canvas.height);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
@@ -0,0 +1,90 @@
|
||||
<div style="position: absolute; bottom: 0; right: 0; padding: 4px; width: 100vw; display: flex">
|
||||
<button id="start" style="font-size: 2em">start</button>
|
||||
<button id="stop" style="font-size: 2em">stop</button>
|
||||
<button id="slower" style="font-size: 2em">slower</button>
|
||||
<button id="faster" style="font-size: 2em">faster</button>
|
||||
</div>
|
||||
<textarea
|
||||
style="font-size: 2em; background: #052b49; color: #fff; height: 100%; width: 100%; outline: none; border: 0"
|
||||
id="text"
|
||||
spellcheck="false"
|
||||
>
|
||||
Loading...</textarea
|
||||
>
|
||||
<script type="module">
|
||||
document.body.style = 'margin: 0';
|
||||
import * as strudel from '@strudel.cycles/core';
|
||||
|
||||
const { cat, State, TimeSpan, Scheduler, getPlayableNoteValue, getFreq } = strudel;
|
||||
Object.assign(window, strudel); // add strudel to eval scope
|
||||
|
||||
const ctx = new AudioContext();
|
||||
const scheduler = new Scheduler({
|
||||
// audioContext: getAudioContext(),
|
||||
interval: 0.1,
|
||||
onTrigger: (hap, time, duration) => {
|
||||
const freq = getFrequency(hap);
|
||||
const osc = ctx.createOscillator();
|
||||
const gain = 0.2;
|
||||
osc.frequency.value = freq;
|
||||
osc.type = 'triangle';
|
||||
const onset = ctx.currentTime + time;
|
||||
const offset = onset + duration;
|
||||
const attack = 0.05;
|
||||
const release = 0.05;
|
||||
osc.start(onset);
|
||||
osc.stop(offset + release);
|
||||
|
||||
const g = ctx.createGain();
|
||||
g.gain.setValueAtTime(gain, onset);
|
||||
g.gain.linearRampToValueAtTime(gain, onset + attack);
|
||||
g.gain.setValueAtTime(gain, offset - release);
|
||||
g.gain.linearRampToValueAtTime(0, offset);
|
||||
osc.connect(g);
|
||||
g.connect(ctx.destination);
|
||||
},
|
||||
});
|
||||
|
||||
let initialCode = `stack('c4','e4',cat('g4','a4','b4','a4'))
|
||||
.add(cat(0,1,2,3,4,3,2,1).slow(8))
|
||||
.fast(2)
|
||||
.cps(tri.range(1,8).slow(32))`;
|
||||
|
||||
try {
|
||||
const base64 = decodeURIComponent(window.location.href.split('#')[1]);
|
||||
initialCode = atob(base64);
|
||||
} catch (err) {
|
||||
console.warn('failed to decode', err);
|
||||
}
|
||||
const input = document.getElementById('text');
|
||||
input.value = initialCode;
|
||||
const evaluate = () => {
|
||||
try {
|
||||
const pattern = eval(input.value);
|
||||
scheduler.setPattern(pattern);
|
||||
window.location.hash = '#' + encodeURIComponent(btoa(input.value)); // update url hash
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
};
|
||||
evaluate();
|
||||
input.addEventListener('input', () => evaluate());
|
||||
document.getElementById('start').addEventListener('click', async () => {
|
||||
await ctx.resume();
|
||||
scheduler.start();
|
||||
});
|
||||
document.getElementById('stop').addEventListener('click', () => scheduler.stop());
|
||||
document.getElementById('slower').addEventListener('click', () => scheduler.setCps(scheduler.cps - 0.1));
|
||||
document.getElementById('faster').addEventListener('click', () => scheduler.setCps(scheduler.cps + 0.1));
|
||||
</script>
|
||||
<!--
|
||||
sequence(1,2).mul(55/2) // frequencies
|
||||
.mul(slowcat(1,2))
|
||||
.mul(slowcat(1,3/2,4/3,5/3).slow(8))
|
||||
.fast(3)
|
||||
.freq()
|
||||
.velocity(.5)
|
||||
.s('sawtooth')
|
||||
.cutoff(800)
|
||||
.out()
|
||||
-->
|
||||
@@ -0,0 +1,44 @@
|
||||
<input
|
||||
type="text"
|
||||
id="text"
|
||||
value="seq('c3','eb3','g3').note().s('sawtooth').out()"
|
||||
style="width: 100%; font-size: 2em; outline: none; margin-bottom: 10px"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<button id="start">play</button>
|
||||
<div id="output"></div>
|
||||
<script type="module">
|
||||
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@latest');
|
||||
|
||||
const controls = await import('https://cdn.skypack.dev/@strudel.cycles/core@latest/controls.mjs');
|
||||
const { getAudioContext, Scheduler } = await import('https://cdn.skypack.dev/@strudel.cycles/webaudio@latest');
|
||||
let scheduler;
|
||||
|
||||
const audioContext = getAudioContext();
|
||||
const latency = 0.2;
|
||||
|
||||
Object.assign(window, strudel);
|
||||
Object.assign(window, controls.default);
|
||||
scheduler = new Scheduler({
|
||||
audioContext,
|
||||
interval: 0.1,
|
||||
latency,
|
||||
onEvent: (hap) => {
|
||||
if (!hap.context.onTrigger) {
|
||||
console.warn('no output chosen. use one of .out() .webdirt() .osc()');
|
||||
}
|
||||
},
|
||||
});
|
||||
let started;
|
||||
document.getElementById('start').addEventListener('click', async () => {
|
||||
const code = document.getElementById('text').value;
|
||||
const pattern = eval(code);
|
||||
const events = pattern._firstCycleValues;
|
||||
console.log(code, '->', events);
|
||||
scheduler.setPattern(pattern);
|
||||
if (!started) {
|
||||
scheduler.start();
|
||||
started = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,96 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Buildless Vanilla Strudel REPL</title>
|
||||
</head>
|
||||
<body style="margin: 0; background: #222">
|
||||
<div style="display: grid; height: 100vh">
|
||||
<textarea
|
||||
id="text"
|
||||
style="font-size: 2em; border: 0; color: white; background: transparent; outline: none; padding: 20px"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
</div>
|
||||
<button
|
||||
id="start"
|
||||
style="
|
||||
position: absolute;
|
||||
border-radius: 10px;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 20px;
|
||||
border: 2px solid white;
|
||||
background: transparent;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
"
|
||||
>
|
||||
evaluate
|
||||
</button>
|
||||
<div id="output"></div>
|
||||
<script type="module">
|
||||
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel.cycles/core@0.3.2';
|
||||
import { mini } from 'https://cdn.skypack.dev/@strudel.cycles/mini@0.3.2';
|
||||
import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.3.2';
|
||||
import { getAudioContext, webaudioOutput } from 'https://cdn.skypack.dev/@strudel.cycles/webaudio@0.3.3';
|
||||
|
||||
const ctx = getAudioContext();
|
||||
const input = document.getElementById('text');
|
||||
input.innerHTML = getTune();
|
||||
|
||||
evalScope(
|
||||
controls,
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/core@0.3.2'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/mini@0.3.2'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/tonal@0.3.3'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/webaudio@0.3.3'),
|
||||
);
|
||||
|
||||
const { evaluate } = repl({
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime: () => ctx.currentTime,
|
||||
transpiler,
|
||||
});
|
||||
document.getElementById('start').addEventListener('click', () => {
|
||||
ctx.resume();
|
||||
evaluate(input.value);
|
||||
});
|
||||
|
||||
function getTune() {
|
||||
return `await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
|
||||
stack(
|
||||
// amen
|
||||
n("0 1 2 3 4 5 6 7")
|
||||
.sometimes(x=>x.ply(2))
|
||||
.rarely(x=>x.speed("2 | -2"))
|
||||
.sometimesBy(.4, x=>x.delay(".5"))
|
||||
.s("amencutup")
|
||||
.slow(2)
|
||||
.room(.5)
|
||||
,
|
||||
// bass
|
||||
sine.add(saw.slow(4)).range(0,7).segment(8)
|
||||
.superimpose(x=>x.add(.1))
|
||||
.scale('G0 minor').note()
|
||||
.s("sawtooth").decay(.1).sustain(0)
|
||||
.gain(.4).cutoff(perlin.range(300,3000).slow(8)).resonance(10)
|
||||
.degradeBy("0 0.1 .5 .1")
|
||||
.rarely(add(note("12")))
|
||||
,
|
||||
// chord
|
||||
note("Bb3,D4".superimpose(x=>x.add(.2)))
|
||||
.s('sawtooth').cutoff(1000).struct("<~@3 [~ x]>")
|
||||
.decay(.05).sustain(.0).delay(.8).delaytime(.125).room(.8)
|
||||
,
|
||||
// alien
|
||||
s("breath").room(1).shape(.6).chop(16).rev().mask("<x ~@7>")
|
||||
,
|
||||
n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5))
|
||||
).reset("<x@7 x(5,8)>")`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
!dist
|
||||
@@ -0,0 +1,10 @@
|
||||
# vite-vanilla-repl
|
||||
|
||||
This folder demonstrates how to set up a strudel repl using vite and vanilla JS. Run it using:
|
||||
|
||||
```sh
|
||||
npm i
|
||||
npm run dev
|
||||
```
|
||||
|
||||
or view it [live on githack](https://rawcdn.githack.com/tidalcycles/strudel/5fb36acb046ead7cd6ad3cd10f532e7f585f536a/packages/core/examples/vite-vanilla-repl/dist/index.html)
|
||||
@@ -0,0 +1 @@
|
||||
import{b as s,h as i,m,a as n,c as p,p as t}from"./index.4cbc0a10.js";export{s as SyntaxError,i as h,m as mini,n as minify,p as parse,t as patternifyAST};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{g as s,f as d,i as t,n as r,l as u,j as o,d as f,k as p,r as g,s as i,w as l,e as m}from"./index.4cbc0a10.js";export{s as getAudioContext,d as getCachedBuffer,t as getLoadedBuffer,r as getLoadedSamples,u as loadBuffer,o as loadGithubSamples,f as panic,p as resetLoadedSamples,g as reverseBuffer,i as samples,l as webaudioOutput,m as webaudioOutputTrigger};
|
||||
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite Vanilla Strudel REPL</title>
|
||||
<script type="module" crossorigin src="/tidalcycles/strudel/use-acorn/packages/core/examples/vite-vanilla-repl/dist/assets/index.4cbc0a10.js"></script>
|
||||
</head>
|
||||
<body style="margin: 0; background: #222">
|
||||
<div style="display: grid; height: 100vh">
|
||||
<textarea
|
||||
id="text"
|
||||
style="font-size: 2em; border: 0; color: white; background: transparent; outline: none; padding: 20px"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
</div>
|
||||
<button
|
||||
id="start"
|
||||
style="
|
||||
position: absolute;
|
||||
border-radius: 10px;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 20px;
|
||||
border: 2px solid white;
|
||||
background: transparent;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
"
|
||||
>
|
||||
evaluate
|
||||
</button>
|
||||
<div id="output"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite Vanilla Strudel REPL</title>
|
||||
</head>
|
||||
<body style="margin: 0; background: #222">
|
||||
<div style="display: grid; height: 100vh">
|
||||
<textarea
|
||||
id="text"
|
||||
style="font-size: 2em; border: 0; color: white; background: transparent; outline: none; padding: 20px"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
</div>
|
||||
<button
|
||||
id="start"
|
||||
style="
|
||||
position: absolute;
|
||||
border-radius: 10px;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 20px;
|
||||
border: 2px solid white;
|
||||
background: transparent;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
"
|
||||
>
|
||||
evaluate
|
||||
</button>
|
||||
<div id="output"></div>
|
||||
<script type="module" src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
import { controls, repl, evalScope, setStringParser } from '@strudel.cycles/core';
|
||||
import { mini } from '@strudel.cycles/mini';
|
||||
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
// import { transpiler } from '@strudel.cycles/transpiler';
|
||||
import tune from './tune.mjs';
|
||||
|
||||
const ctx = getAudioContext();
|
||||
const input = document.getElementById('text');
|
||||
input.innerHTML = tune;
|
||||
|
||||
evalScope(
|
||||
controls,
|
||||
import('@strudel.cycles/core'),
|
||||
import('@strudel.cycles/mini'),
|
||||
import('@strudel.cycles/webaudio'),
|
||||
import('@strudel.cycles/tonal'),
|
||||
);
|
||||
|
||||
setStringParser(mini);
|
||||
|
||||
const { evaluate } = repl({
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime: () => ctx.currentTime,
|
||||
// transpiler,
|
||||
});
|
||||
document.getElementById('start').addEventListener('click', () => {
|
||||
ctx.resume();
|
||||
evaluate(input.value);
|
||||
});
|
||||
@@ -0,0 +1,885 @@
|
||||
{
|
||||
"name": "vite-vanilla-repl",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vite-vanilla-repl",
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"vite": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz",
|
||||
"integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz",
|
||||
"integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz",
|
||||
"integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/android-arm": "0.15.13",
|
||||
"@esbuild/linux-loong64": "0.15.13",
|
||||
"esbuild-android-64": "0.15.13",
|
||||
"esbuild-android-arm64": "0.15.13",
|
||||
"esbuild-darwin-64": "0.15.13",
|
||||
"esbuild-darwin-arm64": "0.15.13",
|
||||
"esbuild-freebsd-64": "0.15.13",
|
||||
"esbuild-freebsd-arm64": "0.15.13",
|
||||
"esbuild-linux-32": "0.15.13",
|
||||
"esbuild-linux-64": "0.15.13",
|
||||
"esbuild-linux-arm": "0.15.13",
|
||||
"esbuild-linux-arm64": "0.15.13",
|
||||
"esbuild-linux-mips64le": "0.15.13",
|
||||
"esbuild-linux-ppc64le": "0.15.13",
|
||||
"esbuild-linux-riscv64": "0.15.13",
|
||||
"esbuild-linux-s390x": "0.15.13",
|
||||
"esbuild-netbsd-64": "0.15.13",
|
||||
"esbuild-openbsd-64": "0.15.13",
|
||||
"esbuild-sunos-64": "0.15.13",
|
||||
"esbuild-windows-32": "0.15.13",
|
||||
"esbuild-windows-64": "0.15.13",
|
||||
"esbuild-windows-arm64": "0.15.13"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz",
|
||||
"integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-arm64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz",
|
||||
"integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz",
|
||||
"integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-arm64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz",
|
||||
"integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz",
|
||||
"integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-arm64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz",
|
||||
"integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-32": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz",
|
||||
"integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz",
|
||||
"integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz",
|
||||
"integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz",
|
||||
"integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-mips64le": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz",
|
||||
"integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-ppc64le": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz",
|
||||
"integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-riscv64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz",
|
||||
"integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-s390x": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz",
|
||||
"integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-netbsd-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz",
|
||||
"integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-openbsd-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz",
|
||||
"integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sunos-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz",
|
||||
"integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-32": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz",
|
||||
"integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz",
|
||||
"integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-arm64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz",
|
||||
"integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
|
||||
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has": "^1.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.18",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
|
||||
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.4",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.9.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "2.79.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.2.tgz",
|
||||
"integrity": "sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.15.9",
|
||||
"postcss": "^8.4.18",
|
||||
"resolve": "^1.22.1",
|
||||
"rollup": "^2.79.1"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"less": "*",
|
||||
"sass": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@esbuild/android-arm": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz",
|
||||
"integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-loong64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz",
|
||||
"integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz",
|
||||
"integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@esbuild/android-arm": "0.15.13",
|
||||
"@esbuild/linux-loong64": "0.15.13",
|
||||
"esbuild-android-64": "0.15.13",
|
||||
"esbuild-android-arm64": "0.15.13",
|
||||
"esbuild-darwin-64": "0.15.13",
|
||||
"esbuild-darwin-arm64": "0.15.13",
|
||||
"esbuild-freebsd-64": "0.15.13",
|
||||
"esbuild-freebsd-arm64": "0.15.13",
|
||||
"esbuild-linux-32": "0.15.13",
|
||||
"esbuild-linux-64": "0.15.13",
|
||||
"esbuild-linux-arm": "0.15.13",
|
||||
"esbuild-linux-arm64": "0.15.13",
|
||||
"esbuild-linux-mips64le": "0.15.13",
|
||||
"esbuild-linux-ppc64le": "0.15.13",
|
||||
"esbuild-linux-riscv64": "0.15.13",
|
||||
"esbuild-linux-s390x": "0.15.13",
|
||||
"esbuild-netbsd-64": "0.15.13",
|
||||
"esbuild-openbsd-64": "0.15.13",
|
||||
"esbuild-sunos-64": "0.15.13",
|
||||
"esbuild-windows-32": "0.15.13",
|
||||
"esbuild-windows-64": "0.15.13",
|
||||
"esbuild-windows-arm64": "0.15.13"
|
||||
}
|
||||
},
|
||||
"esbuild-android-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz",
|
||||
"integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-android-arm64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz",
|
||||
"integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz",
|
||||
"integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-arm64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz",
|
||||
"integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz",
|
||||
"integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-arm64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz",
|
||||
"integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-32": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz",
|
||||
"integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz",
|
||||
"integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz",
|
||||
"integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz",
|
||||
"integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-mips64le": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz",
|
||||
"integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-ppc64le": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz",
|
||||
"integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-riscv64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz",
|
||||
"integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-s390x": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz",
|
||||
"integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-netbsd-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz",
|
||||
"integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-openbsd-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz",
|
||||
"integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-sunos-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz",
|
||||
"integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-32": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz",
|
||||
"integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz",
|
||||
"integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-arm64": {
|
||||
"version": "0.15.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz",
|
||||
"integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"is-core-module": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
|
||||
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
|
||||
"dev": true
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.4.18",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
|
||||
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nanoid": "^3.3.4",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-core-module": "^2.9.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "2.79.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true
|
||||
},
|
||||
"vite": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.2.tgz",
|
||||
"integrity": "sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.15.9",
|
||||
"fsevents": "~2.3.2",
|
||||
"postcss": "^8.4.18",
|
||||
"resolve": "^1.22.1",
|
||||
"rollup": "^2.79.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "vite-vanilla-repl",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build-githack": "vite build --base /tidalcycles/strudel/use-acorn/packages/core/examples/vite-vanilla-repl/dist/",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^3.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* export default `await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
|
||||
stack(
|
||||
// amen
|
||||
n("0 1 2 3 4 5 6 7")
|
||||
.sometimes(x=>x.ply(2))
|
||||
.rarely(x=>x.speed("2 | -2"))
|
||||
.sometimesBy(.4, x=>x.delay(".5"))
|
||||
.s("amencutup")
|
||||
.slow(2)
|
||||
.room(.5)
|
||||
,
|
||||
// bass
|
||||
sine.add(saw.slow(4)).range(0,7).segment(8)
|
||||
.superimpose(x=>x.add(.1))
|
||||
.scale('G0 minor').note()
|
||||
.s("sawtooth").decay(.1).sustain(0)
|
||||
.gain(.4).cutoff(perlin.range(300,3000).slow(8)).resonance(10)
|
||||
.degradeBy("0 0.1 .5 .1")
|
||||
.rarely(add(note("12")))
|
||||
,
|
||||
// chord
|
||||
note("Bb3,D4".superimpose(x=>x.add(.2)))
|
||||
.s('sawtooth').cutoff(1000).struct("<~@3 [~ x]>")
|
||||
.decay(.05).sustain(.0).delay(.8).delaytime(.125).room(.8)
|
||||
,
|
||||
// alien
|
||||
s("breath").room(1).shape(.6).chop(16).rev().mask("<x ~@7>")
|
||||
,
|
||||
n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5))
|
||||
).reset("<x@7 x(5,8)>")`;
|
||||
*/
|
||||
|
||||
export default `stack(
|
||||
n("c3 [eb3,g3]")
|
||||
.delay("<0 .5>")
|
||||
.delaytime(".16 | .33")
|
||||
.delayfeedback(".6 | .8")
|
||||
).sometimes(x=>x.speed("-1"))`;
|
||||
@@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Buildless Vanilla Strudel REPL</title>
|
||||
</head>
|
||||
<body style="margin: 0; background: #222">
|
||||
<div style="display: grid; height: 100vh">
|
||||
<textarea
|
||||
id="text"
|
||||
style="font-size: 2em; border: 0; color: white; background: transparent; outline: none; padding: 20px"
|
||||
spellcheck="false"
|
||||
>
|
||||
// LOADING</textarea
|
||||
>
|
||||
</div>
|
||||
<button
|
||||
id="start"
|
||||
style="
|
||||
position: absolute;
|
||||
border-radius: 10px;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 20px;
|
||||
border: 2px solid white;
|
||||
background: transparent;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
"
|
||||
>
|
||||
evaluate
|
||||
</button>
|
||||
<div id="output"></div>
|
||||
<script type="module">
|
||||
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel.cycles/core@0.4.1';
|
||||
import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.4.1';
|
||||
|
||||
const modules = [
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/core@0.4.1'),
|
||||
import('https://cdn.skypack.dev/@strudel.cycles/mini@0.4.1'),
|
||||
];
|
||||
|
||||
const input = document.getElementById('text');
|
||||
|
||||
Promise.all(modules).then(() => {
|
||||
input.innerHTML = `note("<c3 [d3 e3]>").cutoff(1000)`;
|
||||
document.getElementById('start').addEventListener('click', () => {
|
||||
evaluate(input.value);
|
||||
});
|
||||
});
|
||||
evalScope(controls, ...modules);
|
||||
|
||||
const { evaluate } = repl({
|
||||
defaultOutput: (hap, deadline, duration) => {
|
||||
console.log(deadline, duration, hap.value);
|
||||
},
|
||||
getTime: () => Date.now() / 1000,
|
||||
transpiler,
|
||||
beforeEval: (code) => console.log('evaluate', code),
|
||||
afterEval: (code) => {},
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
fraction.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/fraction.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Fraction from 'fraction.js';
|
||||
import { TimeSpan } from './timespan.mjs';
|
||||
|
||||
// Returns the start of the cycle.
|
||||
Fraction.prototype.sam = function () {
|
||||
return this.floor();
|
||||
};
|
||||
|
||||
// Returns the start of the next cycle.
|
||||
Fraction.prototype.nextSam = function () {
|
||||
return this.sam().add(1);
|
||||
};
|
||||
|
||||
// Returns a TimeSpan representing the begin and end of the Time value's cycle
|
||||
Fraction.prototype.wholeCycle = function () {
|
||||
return new TimeSpan(this.sam(), this.nextSam());
|
||||
};
|
||||
|
||||
// The position of a time value relative to the start of its cycle.
|
||||
Fraction.prototype.cyclePos = function () {
|
||||
return this.sub(this.sam());
|
||||
};
|
||||
|
||||
Fraction.prototype.lt = function (other) {
|
||||
return this.compare(other) < 0;
|
||||
};
|
||||
|
||||
Fraction.prototype.gt = function (other) {
|
||||
return this.compare(other) > 0;
|
||||
};
|
||||
|
||||
Fraction.prototype.lte = function (other) {
|
||||
return this.compare(other) <= 0;
|
||||
};
|
||||
|
||||
Fraction.prototype.gte = function (other) {
|
||||
return this.compare(other) >= 0;
|
||||
};
|
||||
|
||||
Fraction.prototype.eq = function (other) {
|
||||
return this.compare(other) == 0;
|
||||
};
|
||||
|
||||
Fraction.prototype.max = function (other) {
|
||||
return this.gt(other) ? this : other;
|
||||
};
|
||||
|
||||
Fraction.prototype.min = function (other) {
|
||||
return this.lt(other) ? this : other;
|
||||
};
|
||||
|
||||
Fraction.prototype.show = function (/* excludeWhole = false */) {
|
||||
// return this.toFraction(excludeWhole);
|
||||
return this.s * this.n + '/' + this.d;
|
||||
};
|
||||
|
||||
Fraction.prototype.or = function (other) {
|
||||
return this.eq(0) ? other : this;
|
||||
};
|
||||
|
||||
const fraction = (n) => {
|
||||
if (typeof n === 'number') {
|
||||
/*
|
||||
https://github.com/infusion/Fraction.js/#doubles
|
||||
„If you pass a double as it is, Fraction.js will perform a number analysis based on Farey Sequences."
|
||||
„If you want to keep the number as it is, convert it to a string, as the string parser will not perform any further observations“
|
||||
|
||||
-> those farey sequences turn out to make pattern querying ~20 times slower! always use strings!
|
||||
-> still, some optimizations could be done: .mul .div .add .sub calls still use numbers
|
||||
*/
|
||||
// n = String(n); // this is actually faster but imprecise...
|
||||
}
|
||||
return Fraction(n);
|
||||
};
|
||||
|
||||
export const gcd = (...fractions) => {
|
||||
return fractions.reduce((gcd, fraction) => gcd.gcd(fraction), fraction(1));
|
||||
};
|
||||
|
||||
fraction._original = Fraction;
|
||||
|
||||
export default fraction;
|
||||
|
||||
// "If you concern performance, cache Fraction.js objects and pass arrays/objects.“
|
||||
// -> tested memoized version, but it's slower than unmemoized, even with repeated evaluation
|
||||
/* const memo = {};
|
||||
const memoizedFraction = (n) => {
|
||||
if (typeof n === 'number') {
|
||||
n = String(n);
|
||||
}
|
||||
if (memo[n] !== undefined) {
|
||||
return memo[n];
|
||||
}
|
||||
memo[n] = Fraction(n);
|
||||
return memo[n];
|
||||
}; */
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
gist.js - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/gist.js>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// this is a shortcut to eval code from a gist
|
||||
// why? to be able to shorten strudel code + e.g. be able to change instruments after links have been generated
|
||||
export default (route, cache = true) =>
|
||||
fetch(`https://gist.githubusercontent.com/${route}?cachebust=${cache ? '' : Date.now()}`)
|
||||
.then((res) => res.text())
|
||||
.then((code) => eval(code));
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
hap.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/hap.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export class Hap {
|
||||
/*
|
||||
Event class, representing a value active during the timespan
|
||||
'part'. This might be a fragment of an event, in which case the
|
||||
timespan will be smaller than the 'whole' timespan, otherwise the
|
||||
two timespans will be the same. The 'part' must never extend outside of the
|
||||
'whole'. If the event represents a continuously changing value
|
||||
then the whole will be returned as None, in which case the given
|
||||
value will have been sampled from the point halfway between the
|
||||
start and end of the 'part' timespan.
|
||||
The context is to store a list of source code locations causing the event.
|
||||
|
||||
The word 'Event' is more or less a reserved word in javascript, hence this
|
||||
class is named called 'Hap'.
|
||||
*/
|
||||
|
||||
constructor(whole, part, value, context = {}, stateful = false) {
|
||||
this.whole = whole;
|
||||
this.part = part;
|
||||
this.value = value;
|
||||
this.context = context;
|
||||
this.stateful = stateful;
|
||||
if (stateful) {
|
||||
console.assert(typeof this.value === 'function', 'Stateful values must be functions');
|
||||
}
|
||||
}
|
||||
|
||||
get duration() {
|
||||
return this.whole.end.sub(this.whole.begin);
|
||||
}
|
||||
|
||||
wholeOrPart() {
|
||||
return this.whole ? this.whole : this.part;
|
||||
}
|
||||
|
||||
withSpan(func) {
|
||||
// Returns a new hap with the function f applies to the hap timespan.
|
||||
const whole = this.whole ? func(this.whole) : undefined;
|
||||
return new Hap(whole, func(this.part), this.value, this.context);
|
||||
}
|
||||
|
||||
withValue(func) {
|
||||
// Returns a new hap with the function f applies to the hap value.
|
||||
return new Hap(this.whole, this.part, func(this.value), this.context);
|
||||
}
|
||||
|
||||
hasOnset() {
|
||||
// Test whether the hap contains the onset, i.e that
|
||||
// the beginning of the part is the same as that of the whole timespan."""
|
||||
return this.whole != undefined && this.whole.begin.equals(this.part.begin);
|
||||
}
|
||||
|
||||
resolveState(state) {
|
||||
if (this.stateful && this.hasOnset()) {
|
||||
console.log('stateful');
|
||||
const func = this.value;
|
||||
const [newState, newValue] = func(state);
|
||||
return [newState, new Hap(this.whole, this.part, newValue, this.context, false)];
|
||||
}
|
||||
return [state, this];
|
||||
}
|
||||
|
||||
spanEquals(other) {
|
||||
return (this.whole == undefined && other.whole == undefined) || this.whole.equals(other.whole);
|
||||
}
|
||||
|
||||
equals(other) {
|
||||
return (
|
||||
this.spanEquals(other) &&
|
||||
this.part.equals(other.part) &&
|
||||
// TODO would == be better ??
|
||||
this.value === other.value
|
||||
);
|
||||
}
|
||||
|
||||
show(compact = false) {
|
||||
const value =
|
||||
typeof this.value === 'object'
|
||||
? compact
|
||||
? JSON.stringify(this.value).slice(1, -1).replaceAll('"', '').replaceAll(',', ' ')
|
||||
: JSON.stringify(this.value)
|
||||
: this.value;
|
||||
var spans = '';
|
||||
if (this.whole == undefined) {
|
||||
spans = '~' + this.part.show;
|
||||
} else {
|
||||
var is_whole = this.whole.begin.equals(this.part.begin) && this.whole.end.equals(this.part.end);
|
||||
if (!this.whole.begin.equals(this.part.begin)) {
|
||||
spans = this.whole.begin.show() + ' ⇜ ';
|
||||
}
|
||||
if (!is_whole) {
|
||||
spans += '(';
|
||||
}
|
||||
spans += this.part.show();
|
||||
if (!is_whole) {
|
||||
spans += ')';
|
||||
}
|
||||
if (!this.whole.end.equals(this.part.end)) {
|
||||
spans += ' ⇝ ' + this.whole.end.show();
|
||||
}
|
||||
}
|
||||
return '[ ' + spans + ' | ' + value + ' ]';
|
||||
}
|
||||
|
||||
showWhole(compact = false) {
|
||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${
|
||||
typeof this.value === 'object'
|
||||
? compact
|
||||
? JSON.stringify(this.value).slice(1, -1).replaceAll('"', '').replaceAll(',', ' ')
|
||||
: JSON.stringify(this.value)
|
||||
: this.value
|
||||
}`;
|
||||
}
|
||||
|
||||
combineContext(b) {
|
||||
const a = this;
|
||||
return { ...a.context, ...b.context, locations: (a.context.locations || []).concat(b.context.locations || []) };
|
||||
}
|
||||
|
||||
setContext(context) {
|
||||
return new Hap(this.whole, this.part, this.value, context);
|
||||
}
|
||||
}
|
||||
|
||||
export default Hap;
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
index.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/index.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import controls from './controls.mjs';
|
||||
export * from './euclid.mjs';
|
||||
import Fraction from './fraction.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
export { Fraction, controls };
|
||||
export * from './hap.mjs';
|
||||
export * from './pattern.mjs';
|
||||
export * from './signal.mjs';
|
||||
export * from './state.mjs';
|
||||
export * from './timespan.mjs';
|
||||
export * from './util.mjs';
|
||||
export * from './speak.mjs';
|
||||
export * from './evaluate.mjs';
|
||||
export * from './repl.mjs';
|
||||
export * from './logger.mjs';
|
||||
export * from './time.mjs';
|
||||
export * from './draw.mjs';
|
||||
export * from './animate.mjs';
|
||||
export * from './pianoroll.mjs';
|
||||
export * from './ui.mjs';
|
||||
export { default as drawLine } from './drawLine.mjs';
|
||||
export { default as gist } from './gist.js';
|
||||
// below won't work with runtime.mjs (json import fails)
|
||||
/* import * as p from './package.json';
|
||||
export const version = p.version; */
|
||||
logger('🌀 @strudel.cycles/core loaded 🌀');
|
||||
if (globalThis._strudelLoaded) {
|
||||
console.warn(
|
||||
`@strudel.cycles/core was loaded more than once...
|
||||
This might happen when you have multiple versions of strudel installed.
|
||||
Please check with "npm ls @strudel.cycles/core".`,
|
||||
);
|
||||
}
|
||||
globalThis._strudelLoaded = true;
|
||||
@@ -0,0 +1,18 @@
|
||||
export const logKey = 'strudel.log';
|
||||
|
||||
export function logger(message, type, data = {}) {
|
||||
console.log(`%c${message}`, 'background-color: black;color:white;border-radius:15px');
|
||||
if (typeof document !== 'undefined' && typeof CustomEvent !== 'undefined') {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(logKey, {
|
||||
detail: {
|
||||
message,
|
||||
type,
|
||||
data,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
logger.key = logKey;
|
||||
Generated
+1639
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@strudel.cycles/core",
|
||||
"version": "0.5.0",
|
||||
"description": "Port of Tidal Cycles to JavaScript",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "vitest run"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Alex McLean <alex@slab.org> (https://slab.org)",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://strudel.tidalcycles.org",
|
||||
"dependencies": {
|
||||
"fraction.js": "^4.2.0"
|
||||
},
|
||||
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
pianoroll.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/tone/pianoroll.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern, toMidi, getDrawContext, freqToMidi, isNote } from './index.mjs';
|
||||
|
||||
const scale = (normalized, min, max) => normalized * (max - min) + min;
|
||||
const getValue = (e) => {
|
||||
let { value } = e;
|
||||
if (typeof e.value !== 'object') {
|
||||
value = { value };
|
||||
}
|
||||
let { note, n, freq, s } = value;
|
||||
if (freq) {
|
||||
return freqToMidi(freq);
|
||||
}
|
||||
note = note ?? n;
|
||||
if (typeof note === 'string') {
|
||||
return toMidi(note);
|
||||
}
|
||||
if (typeof note === 'number') {
|
||||
return note;
|
||||
}
|
||||
if (s) {
|
||||
return '_' + s;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
Pattern.prototype.pianoroll = function ({
|
||||
cycles = 4,
|
||||
playhead = 0.5,
|
||||
overscan = 1,
|
||||
flipTime = 0,
|
||||
flipValues = 0,
|
||||
hideNegative = false,
|
||||
// inactive = '#C9E597',
|
||||
// inactive = '#FFCA28',
|
||||
inactive = '#7491D2',
|
||||
active = '#FFCA28',
|
||||
// background = '#2A3236',
|
||||
background = 'transparent',
|
||||
smear = 0,
|
||||
playheadColor = 'white',
|
||||
minMidi = 10,
|
||||
maxMidi = 90,
|
||||
autorange = 0,
|
||||
timeframe: timeframeProp,
|
||||
fold = 0,
|
||||
vertical = 0,
|
||||
} = {}) {
|
||||
const ctx = getDrawContext();
|
||||
const w = ctx.canvas.width;
|
||||
const h = ctx.canvas.height;
|
||||
let from = -cycles * playhead;
|
||||
let to = cycles * (1 - playhead);
|
||||
|
||||
if (timeframeProp) {
|
||||
console.warn('timeframe is deprecated! use from/to instead');
|
||||
from = 0;
|
||||
to = timeframeProp;
|
||||
}
|
||||
const timeAxis = vertical ? h : w;
|
||||
const valueAxis = vertical ? w : h;
|
||||
let timeRange = vertical ? [timeAxis, 0] : [0, timeAxis]; // pixel range for time
|
||||
const timeExtent = to - from; // number of seconds that fit inside the canvas frame
|
||||
const valueRange = vertical ? [0, valueAxis] : [valueAxis, 0]; // pixel range for values
|
||||
let valueExtent = maxMidi - minMidi + 1; // number of "slots" for values, overwritten if autorange true
|
||||
let barThickness = valueAxis / valueExtent; // pixels per value, overwritten if autorange true
|
||||
let foldValues = [];
|
||||
flipTime && timeRange.reverse();
|
||||
flipValues && valueRange.reverse();
|
||||
|
||||
this.draw(
|
||||
(ctx, events, t) => {
|
||||
ctx.fillStyle = background;
|
||||
ctx.globalAlpha = 1; // reset!
|
||||
if (!smear) {
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
}
|
||||
const inFrame = (event) =>
|
||||
(!hideNegative || event.whole.begin >= 0) && event.whole.begin <= t + to && event.whole.end >= t + from;
|
||||
events.filter(inFrame).forEach((event) => {
|
||||
const isActive = event.whole.begin <= t && event.whole.end > t;
|
||||
ctx.fillStyle = event.context?.color || inactive;
|
||||
ctx.strokeStyle = event.context?.color || active;
|
||||
ctx.globalAlpha = event.context.velocity ?? 1;
|
||||
const timePx = scale((event.whole.begin - (flipTime ? to : from)) / timeExtent, ...timeRange);
|
||||
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
|
||||
const value = getValue(event);
|
||||
const valuePx = scale(
|
||||
fold ? foldValues.indexOf(value) / foldValues.length : (Number(value) - minMidi) / valueExtent,
|
||||
...valueRange,
|
||||
);
|
||||
let margin = 0;
|
||||
const offset = scale(t / timeExtent, ...timeRange);
|
||||
let coords;
|
||||
if (vertical) {
|
||||
coords = [
|
||||
valuePx + 1 - (flipValues ? barThickness : 0), // x
|
||||
timeAxis - offset + timePx + margin + 1 - (flipTime ? 0 : durationPx), // y
|
||||
barThickness - 2, // width
|
||||
durationPx - 2, // height
|
||||
];
|
||||
} else {
|
||||
coords = [
|
||||
timePx - offset + margin + 1 - (flipTime ? durationPx : 0), // x
|
||||
valuePx + 1 - (flipValues ? 0 : barThickness), // y
|
||||
durationPx - 2, // widith
|
||||
barThickness - 2, // height
|
||||
];
|
||||
}
|
||||
isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords);
|
||||
});
|
||||
ctx.globalAlpha = 1; // reset!
|
||||
const playheadPosition = scale(-from / timeExtent, ...timeRange);
|
||||
// draw playhead
|
||||
ctx.strokeStyle = playheadColor;
|
||||
ctx.beginPath();
|
||||
if (vertical) {
|
||||
ctx.moveTo(0, playheadPosition);
|
||||
ctx.lineTo(valueAxis, playheadPosition);
|
||||
} else {
|
||||
ctx.moveTo(playheadPosition, 0);
|
||||
ctx.lineTo(playheadPosition, valueAxis);
|
||||
}
|
||||
ctx.stroke();
|
||||
},
|
||||
{
|
||||
from: from - overscan,
|
||||
to: to + overscan,
|
||||
onQuery: (events) => {
|
||||
const { min, max, values } = events.reduce(
|
||||
({ min, max, values }, e) => {
|
||||
const v = getValue(e);
|
||||
return {
|
||||
min: v < min ? v : min,
|
||||
max: v > max ? v : max,
|
||||
values: values.includes(v) ? values : [...values, v],
|
||||
};
|
||||
},
|
||||
{ min: Infinity, max: -Infinity, values: [] },
|
||||
);
|
||||
if (autorange) {
|
||||
minMidi = min;
|
||||
maxMidi = max;
|
||||
valueExtent = maxMidi - minMidi + 1;
|
||||
}
|
||||
foldValues = values.sort((a, b) => String(a).localeCompare(String(b)));
|
||||
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
|
||||
},
|
||||
},
|
||||
);
|
||||
return this;
|
||||
};
|
||||
|
||||
// this function allows drawing a pianoroll without ties to Pattern.prototype
|
||||
// it will probably replace the above in the future
|
||||
export function pianoroll({
|
||||
time,
|
||||
haps,
|
||||
cycles = 4,
|
||||
playhead = 0.5,
|
||||
flipTime = 0,
|
||||
flipValues = 0,
|
||||
hideNegative = false,
|
||||
// inactive = '#C9E597',
|
||||
// inactive = '#FFCA28',
|
||||
inactive = '#7491D2',
|
||||
active = '#FFCA28',
|
||||
// background = '#2A3236',
|
||||
background = 'transparent',
|
||||
smear = 0,
|
||||
playheadColor = 'white',
|
||||
minMidi = 10,
|
||||
maxMidi = 90,
|
||||
autorange = 0,
|
||||
timeframe: timeframeProp,
|
||||
fold = 0,
|
||||
vertical = 0,
|
||||
ctx,
|
||||
} = {}) {
|
||||
const w = ctx.canvas.width;
|
||||
const h = ctx.canvas.height;
|
||||
let from = -cycles * playhead;
|
||||
let to = cycles * (1 - playhead);
|
||||
|
||||
if (timeframeProp) {
|
||||
console.warn('timeframe is deprecated! use from/to instead');
|
||||
from = 0;
|
||||
to = timeframeProp;
|
||||
}
|
||||
const timeAxis = vertical ? h : w;
|
||||
const valueAxis = vertical ? w : h;
|
||||
let timeRange = vertical ? [timeAxis, 0] : [0, timeAxis]; // pixel range for time
|
||||
const timeExtent = to - from; // number of seconds that fit inside the canvas frame
|
||||
const valueRange = vertical ? [0, valueAxis] : [valueAxis, 0]; // pixel range for values
|
||||
let valueExtent = maxMidi - minMidi + 1; // number of "slots" for values, overwritten if autorange true
|
||||
let barThickness = valueAxis / valueExtent; // pixels per value, overwritten if autorange true
|
||||
let foldValues = [];
|
||||
flipTime && timeRange.reverse();
|
||||
flipValues && valueRange.reverse();
|
||||
|
||||
// onQuery
|
||||
const { min, max, values } = haps.reduce(
|
||||
({ min, max, values }, e) => {
|
||||
const v = getValue(e);
|
||||
return {
|
||||
min: v < min ? v : min,
|
||||
max: v > max ? v : max,
|
||||
values: values.includes(v) ? values : [...values, v],
|
||||
};
|
||||
},
|
||||
{ min: Infinity, max: -Infinity, values: [] },
|
||||
);
|
||||
if (autorange) {
|
||||
minMidi = min;
|
||||
maxMidi = max;
|
||||
valueExtent = maxMidi - minMidi + 1;
|
||||
}
|
||||
// foldValues = values.sort((a, b) => a - b);
|
||||
foldValues = values.sort((a, b) => String(a).localeCompare(String(b)));
|
||||
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
|
||||
|
||||
ctx.fillStyle = background;
|
||||
ctx.globalAlpha = 1; // reset!
|
||||
if (!smear) {
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
}
|
||||
/* const inFrame = (event) =>
|
||||
(!hideNegative || event.whole.begin >= 0) && event.whole.begin <= time + to && event.whole.end >= time + from; */
|
||||
haps
|
||||
// .filter(inFrame)
|
||||
.forEach((event) => {
|
||||
const isActive = event.whole.begin <= time && event.whole.end > time;
|
||||
const color = event.value?.color || event.context?.color;
|
||||
ctx.fillStyle = color || inactive;
|
||||
ctx.strokeStyle = color || active;
|
||||
ctx.globalAlpha = event.context.velocity ?? 1;
|
||||
const timePx = scale((event.whole.begin - (flipTime ? to : from)) / timeExtent, ...timeRange);
|
||||
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
|
||||
const value = getValue(event);
|
||||
const valuePx = scale(
|
||||
fold ? foldValues.indexOf(value) / foldValues.length : (Number(value) - minMidi) / valueExtent,
|
||||
...valueRange,
|
||||
);
|
||||
let margin = 0;
|
||||
const offset = scale(time / timeExtent, ...timeRange);
|
||||
let coords;
|
||||
if (vertical) {
|
||||
coords = [
|
||||
valuePx + 1 - (flipValues ? barThickness : 0), // x
|
||||
timeAxis - offset + timePx + margin + 1 - (flipTime ? 0 : durationPx), // y
|
||||
barThickness - 2, // width
|
||||
durationPx - 2, // height
|
||||
];
|
||||
} else {
|
||||
coords = [
|
||||
timePx - offset + margin + 1 - (flipTime ? durationPx : 0), // x
|
||||
valuePx + 1 - (flipValues ? 0 : barThickness), // y
|
||||
durationPx - 2, // widith
|
||||
barThickness - 2, // height
|
||||
];
|
||||
}
|
||||
isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords);
|
||||
});
|
||||
ctx.globalAlpha = 1; // reset!
|
||||
const playheadPosition = scale(-from / timeExtent, ...timeRange);
|
||||
// draw playhead
|
||||
ctx.strokeStyle = playheadColor;
|
||||
ctx.beginPath();
|
||||
if (vertical) {
|
||||
ctx.moveTo(0, playheadPosition);
|
||||
ctx.lineTo(valueAxis, playheadPosition);
|
||||
} else {
|
||||
ctx.moveTo(playheadPosition, 0);
|
||||
ctx.lineTo(playheadPosition, valueAxis);
|
||||
}
|
||||
ctx.stroke();
|
||||
return this;
|
||||
}
|
||||
|
||||
function getOptions(drawTime, options = {}) {
|
||||
let [lookbehind, lookahead] = drawTime;
|
||||
lookbehind = Math.abs(lookbehind);
|
||||
const cycles = lookahead + lookbehind;
|
||||
const playhead = lookbehind / cycles;
|
||||
return { fold: 1, ...options, cycles, playhead };
|
||||
}
|
||||
|
||||
Pattern.prototype.punchcard = function (options) {
|
||||
return this.onPaint((ctx, time, haps, drawTime) => pianoroll({ ctx, time, haps, ...getOptions(drawTime, options) }));
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Cyclist } from './cyclist.mjs';
|
||||
import { evaluate as _evaluate } from './evaluate.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
import { setTime } from './time.mjs';
|
||||
|
||||
export function repl({
|
||||
interval,
|
||||
defaultOutput,
|
||||
onSchedulerError,
|
||||
onEvalError,
|
||||
beforeEval,
|
||||
afterEval,
|
||||
getTime,
|
||||
transpiler,
|
||||
onToggle,
|
||||
editPattern,
|
||||
}) {
|
||||
const scheduler = new Cyclist({
|
||||
interval,
|
||||
onTrigger: async (hap, deadline, duration) => {
|
||||
try {
|
||||
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
||||
await defaultOutput(hap, deadline, duration);
|
||||
}
|
||||
if (hap.context.onTrigger) {
|
||||
const cps = 1;
|
||||
// call signature of output / onTrigger is different...
|
||||
await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps);
|
||||
}
|
||||
} catch (err) {
|
||||
logger(`[cyclist] error: ${err.message}`, 'error');
|
||||
}
|
||||
},
|
||||
onError: onSchedulerError,
|
||||
getTime,
|
||||
onToggle,
|
||||
});
|
||||
setTime(() => scheduler.now()); // TODO: refactor?
|
||||
const evaluate = async (code, autostart = true) => {
|
||||
if (!code) {
|
||||
throw new Error('no code to evaluate');
|
||||
}
|
||||
try {
|
||||
beforeEval?.({ code });
|
||||
let { pattern } = await _evaluate(code, transpiler);
|
||||
|
||||
logger(`[eval] code updated`);
|
||||
pattern = editPattern?.(pattern) || pattern;
|
||||
scheduler.setPattern(pattern, autostart);
|
||||
afterEval?.({ code, pattern });
|
||||
return pattern;
|
||||
} catch (err) {
|
||||
// console.warn(`[repl] eval error: ${err.message}`);
|
||||
logger(`[eval] error: ${err.message}`, 'error');
|
||||
onEvalError?.(err);
|
||||
}
|
||||
};
|
||||
const stop = () => scheduler.stop();
|
||||
const start = () => scheduler.start();
|
||||
const pause = () => scheduler.pause();
|
||||
return { scheduler, evaluate, start, stop, pause };
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
signal.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Hap } from './hap.mjs';
|
||||
import { Pattern, fastcat, reify, silence, stack, register } from './pattern.mjs';
|
||||
import Fraction from './fraction.mjs';
|
||||
import { id } from './util.mjs';
|
||||
|
||||
export function steady(value) {
|
||||
// A continuous value
|
||||
return new Pattern((state) => [new Hap(undefined, state.span, value)]);
|
||||
}
|
||||
|
||||
export const signal = (func) => {
|
||||
const query = (state) => [new Hap(undefined, state.span, func(state.span.midpoint()))];
|
||||
return new Pattern(query);
|
||||
};
|
||||
|
||||
export const isaw = signal((t) => 1 - (t % 1));
|
||||
export const isaw2 = isaw.toBipolar();
|
||||
|
||||
/**
|
||||
* A sawtooth signal between 0 and 1.
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* "c3 [eb3,g3] g2 [g3,bb3]".legato(saw.slow(4)).note()
|
||||
* @example
|
||||
* saw.range(0,8).segment(8).scale('C major').slow(4).note()
|
||||
*
|
||||
*/
|
||||
export const saw = signal((t) => t % 1);
|
||||
export const saw2 = saw.toBipolar();
|
||||
|
||||
export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
|
||||
|
||||
/**
|
||||
* A sine signal between 0 and 1.
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* sine.segment(16).range(0,15).slow(2).scale('C minor').note()
|
||||
*
|
||||
*/
|
||||
export const sine = sine2.fromBipolar();
|
||||
|
||||
/**
|
||||
* A cosine signal between 0 and 1.
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* stack(sine,cosine).segment(16).range(0,15).slow(2).scale('C minor').note()
|
||||
*
|
||||
*/
|
||||
export const cosine = sine._early(Fraction(1).div(4));
|
||||
export const cosine2 = sine2._early(Fraction(1).div(4));
|
||||
|
||||
/**
|
||||
* A square signal between 0 and 1.
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* square.segment(2).range(0,7).scale('C minor').note()
|
||||
*
|
||||
*/
|
||||
export const square = signal((t) => Math.floor((t * 2) % 2));
|
||||
export const square2 = square.toBipolar();
|
||||
|
||||
/**
|
||||
* A triangle signal between 0 and 1.
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* tri.segment(8).range(0,7).scale('C minor').note()
|
||||
*
|
||||
*/
|
||||
export const tri = fastcat(isaw, saw);
|
||||
export const tri2 = fastcat(isaw2, saw2);
|
||||
|
||||
export const time = signal(id);
|
||||
|
||||
// random signals
|
||||
|
||||
const xorwise = (x) => {
|
||||
const a = (x << 13) ^ x;
|
||||
const b = (a >> 17) ^ a;
|
||||
return (b << 5) ^ b;
|
||||
};
|
||||
|
||||
// stretch 300 cycles over the range of [0,2**29 == 536870912) then apply the xorshift algorithm
|
||||
const _frac = (x) => x - Math.trunc(x);
|
||||
|
||||
const timeToIntSeed = (x) => xorwise(Math.trunc(_frac(x / 300) * 536870912));
|
||||
|
||||
const intSeedToRand = (x) => (x % 536870912) / 536870912;
|
||||
|
||||
const timeToRand = (x) => Math.abs(intSeedToRand(timeToIntSeed(x)));
|
||||
|
||||
const timeToRandsPrime = (seed, n) => {
|
||||
const result = [];
|
||||
for (let i = 0; i < n; ++n) {
|
||||
result.push(intSeedToRand(seed));
|
||||
seed = xorwise(seed);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* A continuous pattern of random numbers, between 0 and 1.
|
||||
*
|
||||
* @name rand
|
||||
* @example
|
||||
* // randomly change the cutoff
|
||||
* s("bd sd,hh*4").cutoff(rand.range(500,2000))
|
||||
*
|
||||
*/
|
||||
export const rand = signal(timeToRand);
|
||||
/**
|
||||
* A continuous pattern of random numbers, between -1 and 1
|
||||
*/
|
||||
export const rand2 = rand.toBipolar();
|
||||
|
||||
export const _brandBy = (p) => rand.fmap((x) => x < p);
|
||||
export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin();
|
||||
export const brand = _brandBy(0.5);
|
||||
|
||||
export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
|
||||
|
||||
/**
|
||||
* A continuous pattern of random integers, between 0 and n-1.
|
||||
*
|
||||
* @name irand
|
||||
* @param {number} n max value (exclusive)
|
||||
* @example
|
||||
* // randomly select scale notes from 0 - 7 (= C to C)
|
||||
* irand(8).struct("x(3,8)").scale('C minor').note()
|
||||
*
|
||||
*/
|
||||
export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin();
|
||||
|
||||
export const __chooseWith = (pat, xs) => {
|
||||
xs = xs.map(reify);
|
||||
if (xs.length == 0) {
|
||||
return silence;
|
||||
}
|
||||
return pat.range(0, xs.length).fmap((i) => xs[Math.floor(i)]);
|
||||
};
|
||||
/**
|
||||
* Choose from the list of values (or patterns of values) using the given
|
||||
* pattern of numbers, which should be in the range of 0..1
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const chooseWith = (pat, xs) => {
|
||||
return __chooseWith(pat, xs).outerJoin();
|
||||
};
|
||||
|
||||
/**
|
||||
* As with {chooseWith}, but the structure comes from the chosen values, rather
|
||||
* than the pattern you're using to choose with.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const chooseInWith = (pat, xs) => {
|
||||
return __chooseWith(pat, xs).innerJoin();
|
||||
};
|
||||
|
||||
/**
|
||||
* Chooses randomly from the given list of elements.
|
||||
* @param {...any} xs values / patterns to choose from.
|
||||
* @returns {Pattern} - a continuous pattern.
|
||||
*/
|
||||
export const choose = (...xs) => chooseWith(rand, xs);
|
||||
|
||||
/**
|
||||
* Chooses from the given list of values (or patterns of values), according
|
||||
* to the pattern that the method is called on. The pattern should be in
|
||||
* the range 0 .. 1.
|
||||
* @param {...any} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
Pattern.prototype.choose = function (...xs) {
|
||||
return chooseWith(this, xs);
|
||||
};
|
||||
|
||||
/**
|
||||
* As with choose, but the pattern that this method is called on should be
|
||||
* in the range -1 .. 1
|
||||
* @param {...any} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
Pattern.prototype.choose2 = function (...xs) {
|
||||
return chooseWith(this.fromBipolar(), xs);
|
||||
};
|
||||
|
||||
/**
|
||||
* Picks one of the elements at random each cycle.
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* chooseCycles("bd", "hh", "sd").s().fast(4)
|
||||
* @example
|
||||
* "bd | hh | sd".s().fast(4)
|
||||
*/
|
||||
export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
|
||||
|
||||
export const randcat = chooseCycles;
|
||||
|
||||
const _wchooseWith = function (pat, ...pairs) {
|
||||
const values = pairs.map((pair) => reify(pair[0]));
|
||||
const weights = [];
|
||||
let accum = 0;
|
||||
for (const pair of pairs) {
|
||||
accum += pair[1];
|
||||
weights.push(accum);
|
||||
}
|
||||
const total = accum;
|
||||
const match = function (r) {
|
||||
const find = r * total;
|
||||
return values[weights.findIndex((x) => x > find, weights)];
|
||||
};
|
||||
return pat.fmap(match);
|
||||
};
|
||||
|
||||
const wchooseWith = (...args) => _wchooseWith(...args).outerJoin();
|
||||
|
||||
export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
|
||||
|
||||
export const wchooseCycles = (...pairs) => _wchooseWith(rand, ...pairs).innerJoin();
|
||||
|
||||
// this function expects pat to be a pattern of floats...
|
||||
export const perlinWith = (pat) => {
|
||||
const pata = pat.fmap(Math.floor);
|
||||
const patb = pat.fmap((t) => Math.floor(t) + 1);
|
||||
const smootherStep = (x) => 6.0 * x ** 5 - 15.0 * x ** 4 + 10.0 * x ** 3;
|
||||
const interp = (x) => (a) => (b) => a + smootherStep(x) * (b - a);
|
||||
return pat.sub(pata).fmap(interp).appBoth(pata.fmap(timeToRand)).appBoth(patb.fmap(timeToRand));
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a continuous pattern of [perlin noise](https://en.wikipedia.org/wiki/Perlin_noise), in the range 0..1.
|
||||
*
|
||||
* @name perlin
|
||||
* @example
|
||||
* // randomly change the cutoff
|
||||
* s("bd sd,hh*4").cutoff(perlin.range(500,2000))
|
||||
*
|
||||
*/
|
||||
export const perlin = perlinWith(time.fmap((v) => Number(v)));
|
||||
|
||||
export const degradeByWith = register('degradeByWith', (withPat, x, pat) =>
|
||||
pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
|
||||
);
|
||||
|
||||
/**
|
||||
* Randomly removes events from the pattern by a given amount.
|
||||
* 0 = 0% chance of removal
|
||||
* 1 = 100% chance of removal
|
||||
*
|
||||
* @name degradeBy
|
||||
* @memberof Pattern
|
||||
* @param {number} amount - a number between 0 and 1
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").degradeBy(0.2)
|
||||
* @example
|
||||
* s("[hh?0.2]*8")
|
||||
*/
|
||||
export const degradeBy = register('degradeBy', function (x, pat) {
|
||||
return pat._degradeByWith(rand, x);
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* Randomly removes 50% of events from the pattern. Shorthand for `.degradeBy(0.5)`
|
||||
*
|
||||
* @name degrade
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").degrade()
|
||||
* @example
|
||||
* s("[hh?]*8")
|
||||
*/
|
||||
export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));
|
||||
|
||||
/**
|
||||
* Inverse of {@link Pattern#degradeBy}: Randomly removes events from the pattern by a given amount.
|
||||
* 0 = 100% chance of removal
|
||||
* 1 = 0% chance of removal
|
||||
* Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example).
|
||||
*
|
||||
* @name undegradeBy
|
||||
* @memberof Pattern
|
||||
* @param {number} amount - a number between 0 and 1
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").undegradeBy(0.2)
|
||||
*/
|
||||
export const undegradeBy = register('undegradeBy', function (x, pat) {
|
||||
return pat._degradeByWith(
|
||||
rand.fmap((r) => 1 - r),
|
||||
x,
|
||||
);
|
||||
});
|
||||
|
||||
export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5));
|
||||
|
||||
/**
|
||||
*
|
||||
* Randomly applies the given function by the given probability.
|
||||
* Similar to {@link Pattern#someCyclesBy}
|
||||
*
|
||||
* @name sometimesBy
|
||||
* @memberof Pattern
|
||||
* @param {number | Pattern} probability - a number between 0 and 1
|
||||
* @param {function} function - the transformation to apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5"))
|
||||
*/
|
||||
|
||||
export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
|
||||
return reify(patx)
|
||||
.fmap((x) => stack(pat._degradeBy(x), func(pat._undegradeBy(1 - x))))
|
||||
.innerJoin();
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* Applies the given function with a 50% chance
|
||||
*
|
||||
* @name sometimes
|
||||
* @memberof Pattern
|
||||
* @param {function} function - the transformation to apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*4").sometimes(x=>x.speed("0.5"))
|
||||
*/
|
||||
export const sometimes = register('sometimes', function (func, pat) {
|
||||
return pat._sometimesBy(0.5, func);
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* Randomly applies the given function by the given probability on a cycle by cycle basis.
|
||||
* Similar to {@link Pattern#sometimesBy}
|
||||
*
|
||||
* @name someCyclesBy
|
||||
* @memberof Pattern
|
||||
* @param {number | Pattern} probability - a number between 0 and 1
|
||||
* @param {function} function - the transformation to apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5"))
|
||||
*/
|
||||
|
||||
export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) {
|
||||
return reify(patx)
|
||||
.fmap((x) =>
|
||||
stack(
|
||||
pat._degradeByWith(rand._segment(1), x),
|
||||
func(pat._degradeByWith(rand.fmap((r) => 1 - r)._segment(1), 1 - x)),
|
||||
),
|
||||
)
|
||||
.innerJoin();
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* Shorthand for `.someCyclesBy(0.5, fn)`
|
||||
*
|
||||
* @name someCycles
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh(3,8)").someCycles(x=>x.speed("0.5"))
|
||||
*/
|
||||
export const someCycles = register('someCycles', function (func, pat) {
|
||||
return pat._someCyclesBy(0.5, func);
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* Shorthand for `.sometimesBy(0.75, fn)`
|
||||
*
|
||||
* @name often
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").often(x=>x.speed("0.5"))
|
||||
*/
|
||||
export const often = register('often', function (func, pat) {
|
||||
return pat.sometimesBy(0.75, func);
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* Shorthand for `.sometimesBy(0.25, fn)`
|
||||
*
|
||||
* @name rarely
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").rarely(x=>x.speed("0.5"))
|
||||
*/
|
||||
export const rarely = register('rarely', function (func, pat) {
|
||||
return pat.sometimesBy(0.25, func);
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* Shorthand for `.sometimesBy(0.1, fn)`
|
||||
*
|
||||
* @name almostNever
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").almostNever(x=>x.speed("0.5"))
|
||||
*/
|
||||
export const almostNever = register('almostNever', function (func, pat) {
|
||||
return pat.sometimesBy(0.1, func);
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* Shorthand for `.sometimesBy(0.9, fn)`
|
||||
*
|
||||
* @name almostAlways
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").almostAlways(x=>x.speed("0.5"))
|
||||
*/
|
||||
export const almostAlways = register('almostAlways', function (func, pat) {
|
||||
return pat.sometimesBy(0.9, func);
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* Shorthand for `.sometimesBy(0, fn)` (never calls fn)
|
||||
*
|
||||
* @name never
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").never(x=>x.speed("0.5"))
|
||||
*/
|
||||
export const never = register('never', function (_, pat) {
|
||||
return pat;
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* Shorthand for `.sometimesBy(1, fn)` (always calls fn)
|
||||
*
|
||||
* @name always
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").always(x=>x.speed("0.5"))
|
||||
*/
|
||||
export const always = register('always', function (func, pat) {
|
||||
return func(pat);
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
speak.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/speak.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { register } from './index.mjs';
|
||||
|
||||
let synth;
|
||||
try {
|
||||
synth = window?.speechSynthesis;
|
||||
} catch (err) {
|
||||
console.warn('cannot use window: not in browser?');
|
||||
}
|
||||
|
||||
let allVoices = synth?.getVoices();
|
||||
// console.log('voices', allVoices);
|
||||
|
||||
function triggerSpeech(words, lang, voice) {
|
||||
synth.cancel();
|
||||
const utterance = new SpeechSynthesisUtterance(words);
|
||||
utterance.lang = lang;
|
||||
allVoices = synth.getVoices();
|
||||
const voices = allVoices.filter((v) => v.lang.includes(lang));
|
||||
if (typeof voice === 'number') {
|
||||
utterance.voice = voices[voice % voices.length];
|
||||
} else if (typeof voice === 'string') {
|
||||
utterance.voice = voices.find((voice) => voice.name === voice);
|
||||
}
|
||||
// console.log(utterance.voice?.name, utterance.voice?.lang);
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
|
||||
export const speak = register('speak', function (lang, voice, pat) {
|
||||
return pat.onTrigger((_, hap) => {
|
||||
triggerSpeech(hap.value, lang, voice);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
state.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/state.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export class State {
|
||||
constructor(span, controls = {}) {
|
||||
this.span = span;
|
||||
this.controls = controls;
|
||||
}
|
||||
|
||||
// Returns new State with different span
|
||||
setSpan(span) {
|
||||
return new State(span, this.controls);
|
||||
}
|
||||
|
||||
withSpan(func) {
|
||||
return this.setSpan(func(this.span));
|
||||
}
|
||||
|
||||
// Returns new State with different controls
|
||||
setControls(controls) {
|
||||
return new State(this.span, controls);
|
||||
}
|
||||
}
|
||||
|
||||
export default State;
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
drawLine.test.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/drawLine.test.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { fastcat, stack, slowcat, silence, pure } from '../pattern.mjs';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import drawLine from '../drawLine.mjs';
|
||||
|
||||
describe('drawLine', () => {
|
||||
it('supports equal lengths', () => {
|
||||
expect(drawLine(fastcat(0), 4)).toEqual('|0|0');
|
||||
expect(drawLine(fastcat(0, 1), 4)).toEqual('|01|01');
|
||||
expect(drawLine(fastcat(0, 1, 2), 6)).toEqual('|012|012');
|
||||
});
|
||||
it('supports unequal lengths', () => {
|
||||
expect(drawLine(fastcat(0, [1, 2]), 10)).toEqual('|0-12|0-12');
|
||||
expect(drawLine(fastcat(0, [1, 2, 3]), 10)).toEqual('|0--123|0--123');
|
||||
expect(drawLine(fastcat(0, 1, [2, 3]), 10)).toEqual('|0-1-23|0-1-23');
|
||||
});
|
||||
it('supports unequal silence', () => {
|
||||
expect(drawLine(fastcat(0, silence, [1, 2]), 10)).toEqual('|0-..12|0-..12');
|
||||
});
|
||||
it('supports polyrhythms', () => {
|
||||
// assert.equal(drawLine(fastcat(pure(0).fast(2), pure(1).fast(3)), 10), '|0--0--1-1-1-');
|
||||
expect(drawLine(fastcat(pure(0).fast(2), pure(1).fast(3)), 10)).toEqual('|0--0--1-1-1-');
|
||||
});
|
||||
it('supports multiple lines', () => {
|
||||
expect(drawLine(fastcat(0, stack(1, 2)), 10)).toEqual(`|01|01|01|01
|
||||
|.2|.2|.2|.2`);
|
||||
|
||||
expect(drawLine(fastcat(0, 1, stack(2, 3)), 10)).toEqual(`|012|012|012
|
||||
|..3|..3|..3`);
|
||||
|
||||
expect(drawLine(fastcat(0, stack(1, 2, 3)), 10)).toEqual(`|01|01|01|01
|
||||
|.2|.2|.2|.2
|
||||
|.3|.3|.3|.3`);
|
||||
expect(drawLine(fastcat(0, 1, stack(2, 3, 4)), 10)).toEqual(`|012|012|012
|
||||
|..3|..3|..3
|
||||
|..4|..4|..4`);
|
||||
});
|
||||
it('supports unequal cycle lengths', () => {
|
||||
expect(drawLine(slowcat(0, [1, 2]), 10)).toEqual(`|0|12|0|12`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
fraction.test.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/fraction.test.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Fraction, { gcd } from '../fraction.mjs';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('gcd', () => {
|
||||
it('should work', () => {
|
||||
const F = Fraction._original;
|
||||
expect(gcd(F(1 / 6), F(1 / 4)).toFraction()).toEqual('1/12');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,939 @@
|
||||
/*
|
||||
pattern.test.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/pattern.test.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Fraction from 'fraction.js';
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
import {
|
||||
TimeSpan,
|
||||
Hap,
|
||||
State,
|
||||
Pattern,
|
||||
pure,
|
||||
stack,
|
||||
fastcat,
|
||||
firstOf,
|
||||
slowcat,
|
||||
cat,
|
||||
sequence,
|
||||
polymeter,
|
||||
polymeterSteps,
|
||||
polyrhythm,
|
||||
silence,
|
||||
fast,
|
||||
timeCat,
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
div,
|
||||
saw,
|
||||
saw2,
|
||||
isaw,
|
||||
isaw2,
|
||||
sine,
|
||||
sine2,
|
||||
square,
|
||||
square2,
|
||||
tri,
|
||||
tri2,
|
||||
id,
|
||||
ply,
|
||||
rev,
|
||||
time,
|
||||
} from '../index.mjs';
|
||||
|
||||
import { steady } from '../signal.mjs';
|
||||
|
||||
import controls from '../controls.mjs';
|
||||
|
||||
const { n, s } = controls;
|
||||
const st = (begin, end) => new State(ts(begin, end));
|
||||
const ts = (begin, end) => new TimeSpan(Fraction(begin), Fraction(end));
|
||||
const hap = (whole, part, value, context = {}) => new Hap(whole, part, value, context);
|
||||
|
||||
const third = Fraction(1, 3);
|
||||
const twothirds = Fraction(2, 3);
|
||||
|
||||
const sameFirst = (a, b) => {
|
||||
return expect(a.sortHapsByPart().firstCycle()).toStrictEqual(b.sortHapsByPart().firstCycle());
|
||||
};
|
||||
|
||||
describe('TimeSpan', () => {
|
||||
describe('equals()', () => {
|
||||
it('Should be equal to the same value', () => {
|
||||
expect(new TimeSpan(0, 4).equals(new TimeSpan(0, 4))).toBe(true);
|
||||
});
|
||||
});
|
||||
describe('splitCycles', () => {
|
||||
it('Should split two cycles into two', () => {
|
||||
expect(new TimeSpan(Fraction(0), Fraction(2)).spanCycles.length).toBe(2);
|
||||
});
|
||||
});
|
||||
describe('intersection_e', () => {
|
||||
var a = new TimeSpan(Fraction(0), Fraction(2));
|
||||
var b = new TimeSpan(Fraction(1), Fraction(3));
|
||||
var c = new TimeSpan(Fraction(1), Fraction(2));
|
||||
var d = new TimeSpan(Fraction(1), Fraction(2));
|
||||
it('Should create an intersection', () => {
|
||||
expect(a.intersection_e(b).equals(c)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Hap', () => {
|
||||
describe('hasOnset()', () => {
|
||||
it('True if part includes onset from whole', () => {
|
||||
expect(new Hap(new TimeSpan(0, 1), new TimeSpan(0, 1), 'thing').hasOnset()).toBe(true);
|
||||
});
|
||||
});
|
||||
var a = new Hap(new TimeSpan(Fraction(0), Fraction(0.5)), new TimeSpan(Fraction(0), Fraction(0.5)), 'a');
|
||||
var b = new Hap(new TimeSpan(Fraction(0), Fraction(0.5)), new TimeSpan(Fraction(0), Fraction(0.5)), 'b');
|
||||
var c = new Hap(new TimeSpan(Fraction(0), Fraction(0.25)), new TimeSpan(Fraction(0), Fraction(0.5)), 'c');
|
||||
var d = new Hap(undefined, new TimeSpan(Fraction(0), Fraction(0.5)), 'd');
|
||||
var e = new Hap(undefined, new TimeSpan(Fraction(0), Fraction(0.5)), 'e');
|
||||
describe('spanEquals', () => {
|
||||
it('True if two haps have the same whole and part', () => {
|
||||
expect(a.spanEquals(b)).toBe(true);
|
||||
});
|
||||
it("False if two haps don't the same whole and part", () => {
|
||||
expect(a.spanEquals(c)).toBe(false);
|
||||
});
|
||||
it('True if two haps have the same part and undefined wholes', () => {
|
||||
expect(d.spanEquals(e)).toBe(true);
|
||||
});
|
||||
});
|
||||
describe('resolveState()', () => {
|
||||
it('Can increment some state', () => {
|
||||
const stateful_value = (state) => {
|
||||
const newValue = state['incrementme'];
|
||||
// TODO Does the state *need* duplicating here?
|
||||
const newState = { ...state };
|
||||
newState['incrementme']++;
|
||||
return [newState, newValue];
|
||||
};
|
||||
const state = { incrementme: 10 };
|
||||
const ev1 = new Hap(ts(0, 1), ts(0, 1), stateful_value, {}, true);
|
||||
const [state2, ev2] = ev1.resolveState(state);
|
||||
const [state3, ev3] = ev1.resolveState(state2);
|
||||
expect(ev3).toStrictEqual(new Hap(ts(0, 1), ts(0, 1), 11, {}, false));
|
||||
expect(state3).toStrictEqual({ incrementme: 12 });
|
||||
});
|
||||
});
|
||||
describe('wholeOrPart()', () => {
|
||||
const ts1 = new TimeSpan(Fraction(0), Fraction(1));
|
||||
const ts0_5 = new TimeSpan(Fraction(0), Fraction(0.5));
|
||||
const continuousHap = new Hap(undefined, ts1, 'hello');
|
||||
const discreteHap = new Hap(ts1, ts0_5, 'hello');
|
||||
it('Can pick a whole', () => {
|
||||
expect(discreteHap.wholeOrPart()).toStrictEqual(ts1);
|
||||
});
|
||||
it('Can pick a part', () => {
|
||||
expect(continuousHap.wholeOrPart()).toStrictEqual(ts1);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Pattern', () => {
|
||||
describe('pure', () => {
|
||||
it('Can make a pattern', () => {
|
||||
expect(pure('hello').query(st(0.5, 2.5)).length).toBe(3);
|
||||
});
|
||||
it('Supports zero-width queries', () => {
|
||||
expect(pure('hello').queryArc(0, 0).length).toBe(1);
|
||||
});
|
||||
});
|
||||
describe('fmap()', () => {
|
||||
it('Can add things', () => {
|
||||
expect(
|
||||
pure(3)
|
||||
.fmap((x) => x + 4)
|
||||
.firstCycle()[0].value,
|
||||
).toBe(7);
|
||||
});
|
||||
});
|
||||
describe('out()', () => {
|
||||
it('is an alias for set.out()', () => {
|
||||
sameFirst(sequence(1, 2).out(5, 6, 7, 8), sequence(1, 2).set.out(5, 6, 7, 8));
|
||||
});
|
||||
});
|
||||
describe('add()', () => {
|
||||
it('works as toplevel function', () => {
|
||||
expect(add(pure(4), pure(5)).query(st(0, 1))[0].value).toBe(9);
|
||||
});
|
||||
it('works as toplevel function, with bare values for arguments', () => {
|
||||
expect(add(4, 5).query(st(0, 1))[0].value).toBe(9);
|
||||
});
|
||||
it('can structure In()', () => {
|
||||
expect(pure(3).add(pure(4)).query(st(0, 1))[0].value).toBe(7);
|
||||
expect(pure(3).add.in(pure(4)).query(st(0, 1))[0].value).toBe(7);
|
||||
});
|
||||
it('can structure Out()', () => {
|
||||
sameFirst(sequence(1, 2).add.out(4), sequence(5, 6).struct(true));
|
||||
});
|
||||
it('can Mix() structure', () => {
|
||||
expect(sequence(1, 2).add.mix(silence, 5, silence).firstCycle()).toStrictEqual([
|
||||
new Hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 6),
|
||||
new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 7),
|
||||
]);
|
||||
});
|
||||
it('can Trig() structure', () => {
|
||||
sameFirst(
|
||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).add.trig(20, 30).early(2),
|
||||
sequence(26, 27, 36, 37),
|
||||
);
|
||||
});
|
||||
it('can Trigzero() structure', () => {
|
||||
sameFirst(
|
||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).add.trigzero(20, 30).early(2),
|
||||
sequence(21, 22, 31, 32),
|
||||
);
|
||||
});
|
||||
it('can Squeeze() structure', () => {
|
||||
sameFirst(
|
||||
sequence(1, [2, 3]).add.squeeze(sequence(10, 20, 30)),
|
||||
sequence(
|
||||
[11, 21, 31],
|
||||
[
|
||||
[12, 22, 32],
|
||||
[13, 23, 33],
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
it('can SqueezeOut() structure', () => {
|
||||
sameFirst(
|
||||
sequence(1, [2, 3]).add.squeezeout(10, 20, 30),
|
||||
sequence([11, [12, 13]], [21, [22, 23]], [31, [32, 33]]),
|
||||
);
|
||||
});
|
||||
it('can add object patterns', () => {
|
||||
sameFirst(n(sequence(1, [2, 3])).add(n(10)), n(sequence(11, [12, 13])));
|
||||
});
|
||||
});
|
||||
describe('keep()', () => {
|
||||
it('can structure In()', () => {
|
||||
expect(pure(3).keep(pure(4)).query(st(0, 1))[0].value).toBe(3);
|
||||
expect(pure(3).keep.in(pure(4)).query(st(0, 1))[0].value).toBe(3);
|
||||
});
|
||||
it('can structure Out()', () => {
|
||||
sameFirst(sequence(1, 2).keep.out(4), sequence(1, 2).struct(true));
|
||||
});
|
||||
it('can Mix() structure', () => {
|
||||
expect(sequence(1, 2).keep.mix(silence, 5, silence).firstCycle()).toStrictEqual([
|
||||
new Hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 1),
|
||||
new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2),
|
||||
]);
|
||||
});
|
||||
it('can Trig() structure', () => {
|
||||
sameFirst(
|
||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keep.trig(20, 30).early(2),
|
||||
sequence(6, 7, 6, 7),
|
||||
);
|
||||
});
|
||||
it('can Trigzero() structure', () => {
|
||||
sameFirst(
|
||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keep.trigzero(20, 30).early(2),
|
||||
sequence(1, 2, 1, 2),
|
||||
);
|
||||
});
|
||||
it('can Squeeze() structure', () => {
|
||||
sameFirst(
|
||||
sequence(1, [2, 3]).keep.squeeze(sequence(10, 20, 30)),
|
||||
sequence(
|
||||
[1, 1, 1],
|
||||
[
|
||||
[2, 2, 2],
|
||||
[3, 3, 3],
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
it('can SqueezeOut() structure', () => {
|
||||
sameFirst(sequence(1, [2, 3]).keep.squeezeout(10, 20, 30), sequence([1, [2, 3]], [1, [2, 3]], [1, [2, 3]]));
|
||||
});
|
||||
});
|
||||
describe('keepif()', () => {
|
||||
it('can structure In()', () => {
|
||||
sameFirst(sequence(3, 4).keepif(true, false), sequence(3, silence));
|
||||
sameFirst(sequence(3, 4).keepif.in(true, false), sequence(3, silence));
|
||||
});
|
||||
it('can structure Out()', () => {
|
||||
sameFirst(pure(1).keepif.out(true, false), sequence(1, silence));
|
||||
});
|
||||
it('can Mix() structure', () => {
|
||||
expect(sequence(1, 2).keepif.mix(false, true, false).firstCycle()).toStrictEqual([
|
||||
new Hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 1),
|
||||
new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2),
|
||||
]);
|
||||
});
|
||||
it('can Trig() structure', () => {
|
||||
sameFirst(
|
||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepif.trig(false, true).early(2),
|
||||
sequence(silence, silence, 6, 7),
|
||||
);
|
||||
});
|
||||
it('can Trigzero() structure', () => {
|
||||
sameFirst(
|
||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepif.trigzero(false, true).early(2),
|
||||
sequence(silence, silence, 1, 2),
|
||||
);
|
||||
});
|
||||
it('can Squeeze() structure', () => {
|
||||
sameFirst(
|
||||
sequence(1, [2, 3]).keepif.squeeze(sequence(true, true, false)),
|
||||
sequence(
|
||||
[1, 1, silence],
|
||||
[
|
||||
[2, 2, silence],
|
||||
[3, 3, silence],
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
it('can SqueezeOut() structure', () => {
|
||||
sameFirst(sequence(1, [2, 3]).keepif.squeezeout(true, true, false), sequence([1, [2, 3]], [1, [2, 3]], silence));
|
||||
});
|
||||
});
|
||||
describe('sub()', () => {
|
||||
it('Can subtract things', () => {
|
||||
expect(pure(3).sub(pure(4)).query(st(0, 1))[0].value).toBe(-1);
|
||||
});
|
||||
});
|
||||
describe('mul()', () => {
|
||||
it('Can multiply things', () => {
|
||||
expect(pure(3).mul(pure(2)).firstCycle()[0].value).toBe(6);
|
||||
});
|
||||
});
|
||||
describe('div()', () => {
|
||||
it('Can divide things', () => {
|
||||
expect(pure(3).div(pure(2)).firstCycle()[0].value).toBe(1.5);
|
||||
});
|
||||
});
|
||||
describe('set()', () => {
|
||||
it('Can set things in objects', () => {
|
||||
expect(
|
||||
pure({ a: 4, b: 6 })
|
||||
.set(pure({ c: 7 }))
|
||||
.firstCycle()[0].value,
|
||||
).toStrictEqual({
|
||||
a: 4,
|
||||
b: 6,
|
||||
c: 7,
|
||||
});
|
||||
|
||||
sameFirst(
|
||||
sequence({ a: 1, b: 2 }, { a: 2, b: 2 }, { a: 3, b: 2 }).set({ a: 4, c: 5 }),
|
||||
sequence({ a: 4, b: 2, c: 5 }).fast(3),
|
||||
);
|
||||
});
|
||||
it('Can set things with plain values', () => {
|
||||
sameFirst(sequence(1, 2, 3).set(4), sequence(4).fast(3));
|
||||
});
|
||||
describe('setOut()', () => {
|
||||
it('Can set things with structure from second pattern', () => {
|
||||
sameFirst(sequence(1, 2).set.out(4), pure(4).mask(true, true));
|
||||
});
|
||||
});
|
||||
describe('setSqueeze()', () => {
|
||||
it('Can squeeze one pattern inside the haps of another', () => {
|
||||
sameFirst(
|
||||
sequence(1, [2, 3]).set.squeeze(sequence('a', 'b', 'c')),
|
||||
sequence(
|
||||
['a', 'b', 'c'],
|
||||
[
|
||||
['a', 'b', 'c'],
|
||||
['a', 'b', 'c'],
|
||||
],
|
||||
),
|
||||
);
|
||||
sameFirst(
|
||||
sequence(1, [2, 3]).set.squeeze('a', 'b', 'c'),
|
||||
sequence(
|
||||
['a', 'b', 'c'],
|
||||
[
|
||||
['a', 'b', 'c'],
|
||||
['a', 'b', 'c'],
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('stack()', () => {
|
||||
it('Can stack things', () => {
|
||||
expect(
|
||||
stack(pure('a'), pure('b'), pure('c'))
|
||||
.firstCycle()
|
||||
.map((h) => h.value),
|
||||
).toStrictEqual(['a', 'b', 'c']);
|
||||
});
|
||||
it('Can stack subpatterns', () => {
|
||||
sameFirst(stack('a', ['b', 'c']), stack('a', sequence('b', 'c')));
|
||||
});
|
||||
});
|
||||
describe('_fast()', () => {
|
||||
it('Makes things faster', () => {
|
||||
expect(pure('a')._fast(2).firstCycle().length).toBe(2);
|
||||
});
|
||||
});
|
||||
describe('_fastGap()', () => {
|
||||
it('Makes things faster, with a gap', () => {
|
||||
expect(sequence('a', 'b', 'c')._fastGap(2).firstCycle()).toStrictEqual(
|
||||
sequence(['a', 'b', 'c'], silence).firstCycle(),
|
||||
);
|
||||
expect(sequence('a', 'b', 'c')._fastGap(3).firstCycle()).toStrictEqual(
|
||||
sequence(['a', 'b', 'c'], silence, silence).firstCycle(),
|
||||
);
|
||||
});
|
||||
it('Makes things faster, with a gap, when speeded up further', () => {
|
||||
expect(sequence('a', 'b', 'c')._fastGap(2).fast(2).firstCycle()).toStrictEqual(
|
||||
sequence(['a', 'b', 'c'], silence, ['a', 'b', 'c'], silence).firstCycle(),
|
||||
);
|
||||
});
|
||||
it('copes with breaking up events across cycles', () => {
|
||||
expect(pure('a').slow(2)._fastGap(2).setContext({}).query(st(0, 2))).toStrictEqual([
|
||||
hap(ts(0, 1), ts(0, 0.5), 'a'),
|
||||
hap(ts(0.5, 1.5), ts(1, 1.5), 'a'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('_compressSpan()', () => {
|
||||
it('Can squash cycles of a pattern into a given timespan', () => {
|
||||
expect(pure('a')._compressSpan(ts(0.25, 0.5)).firstCycle()).toStrictEqual(
|
||||
sequence(silence, 'a', silence, silence).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('fast()', () => {
|
||||
it('Makes things faster', () => {
|
||||
expect(pure('a').fast(2).firstCycle().length).toBe(2);
|
||||
});
|
||||
it('Makes things faster, with a pattern of factors', () => {
|
||||
expect(pure('a').fast(sequence(1, 4)).firstCycle().length).toBe(3);
|
||||
expect(pure('a').fast(sequence(1, 4)).firstCycle()).toStrictEqual(
|
||||
stack(pure('a').fast(sequence(1, silence)), sequence(silence, ['a', 'a'])).firstCycle(),
|
||||
);
|
||||
});
|
||||
it('defaults to accepting sequences', () => {
|
||||
expect(sequence('a', 'b', 'c').fast(sequence(1.5, 2)).sortHapsByPart().firstCycle()).toStrictEqual(
|
||||
sequence('a', 'b', 'c').fast(1.5, 2).sortHapsByPart().firstCycle(),
|
||||
);
|
||||
});
|
||||
it('works as a static function', () => {
|
||||
expect(sequence(1, 2, 3).fast(1, 2).firstCycle()).toStrictEqual(
|
||||
fast(sequence(1, 2), sequence(1, 2, 3)).firstCycle(),
|
||||
);
|
||||
});
|
||||
it('works as a curried static function', () => {
|
||||
expect(sequence(1, 2, 3).fast(1, 2).firstCycle()).toStrictEqual(
|
||||
fast(sequence(1, 2))(sequence(1, 2, 3)).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_slow()', () => {
|
||||
it('Makes things slower', () => {
|
||||
expect(pure('a')._slow(2).firstCycle()[0]).toStrictEqual(hap(ts(0, 2), ts(0, 1), 'a'));
|
||||
|
||||
const pat = sequence(pure('c3'), pure('eb3')._slow(2)); // => try mini('c3 eb3/2') in repl
|
||||
|
||||
expect(pat.query(st(0, 1))[1]).toStrictEqual(hap(ts(0.5, 1.5), ts(1 / 2, 1), 'eb3'));
|
||||
|
||||
// the following test fails
|
||||
// assert.deepStrictEqual(
|
||||
// pat.query(ts(1,2))[1], undefined
|
||||
// )
|
||||
// expecting [c3 eb3] [c3 ~]
|
||||
// what happens [c3 eb3] [c3 eb3]
|
||||
// notable examples:
|
||||
// mini('[c3 g3]/2 eb3') always plays [c3 eb3]
|
||||
// mini('eb3 [c3 g3]/2 ') always plays [c3 g3]
|
||||
});
|
||||
it('Supports zero-length queries', () => {
|
||||
expect(steady('a')._slow(1).queryArc(0, 0)).toStrictEqual(steady('a').queryArc(0, 0));
|
||||
});
|
||||
});
|
||||
describe('slow()', () => {
|
||||
it('Supports zero-length queries', () => {
|
||||
expect(steady('a').slow(1).setContext({}).queryArc(0, 0)).toStrictEqual(
|
||||
steady('a').setContext({}).queryArc(0, 0),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('inside', () => {
|
||||
it('can rev inside a cycle', () => {
|
||||
sameFirst(sequence('a', 'b', 'c', 'd').inside(2, rev), sequence('b', 'a', 'd', 'c'));
|
||||
});
|
||||
});
|
||||
describe('outside', () => {
|
||||
it('can rev outside a cycle', () => {
|
||||
sameFirst(sequence('a', 'b', 'c', 'd')._slow(2).outside(2, rev), sequence('d', 'c'));
|
||||
});
|
||||
});
|
||||
describe('_filterValues()', () => {
|
||||
it('Filters true', () => {
|
||||
expect(
|
||||
pure(true)
|
||||
.filterValues((x) => x)
|
||||
.firstCycle().length,
|
||||
).toBe(1);
|
||||
});
|
||||
});
|
||||
describe('when()', () => {
|
||||
it('Always faster', () => {
|
||||
expect(
|
||||
pure('a')
|
||||
.when(pure(true), (x) => x._fast(2))
|
||||
.firstCycle().length,
|
||||
).toBe(2);
|
||||
});
|
||||
it('Never faster', () => {
|
||||
expect(
|
||||
pure('a')
|
||||
.when(pure(false), (x) => x._fast(2))
|
||||
.firstCycle().length,
|
||||
).toBe(1);
|
||||
});
|
||||
it('Can alternate', () => {
|
||||
expect(
|
||||
pure(10)
|
||||
.when(slowcat(true, false), (x) => x.add(3))
|
||||
.fast(4)
|
||||
.sortHapsByPart()
|
||||
.firstCycle(),
|
||||
).toStrictEqual(fastcat(13, 10, 13, 10).firstCycle());
|
||||
});
|
||||
});
|
||||
describe('fastcat()', () => {
|
||||
it('Can concatenate two things', () => {
|
||||
expect(
|
||||
fastcat(pure('a'), pure('b'))
|
||||
.firstCycle()
|
||||
.map((x) => x.value),
|
||||
).toStrictEqual(['a', 'b']);
|
||||
});
|
||||
});
|
||||
describe('fastcat()', () => {
|
||||
it('Can go into negative time', () => {
|
||||
sameFirst(fastcat('a', 'b', 'c').late(1000000), fastcat('a', 'b', 'c'));
|
||||
});
|
||||
});
|
||||
describe('slowcat()', () => {
|
||||
it('Can concatenate things slowly', () => {
|
||||
expect(
|
||||
slowcat('a', 'b')
|
||||
.firstCycle()
|
||||
.map((x) => x.value),
|
||||
).toStrictEqual(['a']);
|
||||
|
||||
expect(
|
||||
slowcat('a', 'b')
|
||||
._early(1)
|
||||
.firstCycle()
|
||||
.map((x) => x.value),
|
||||
).toStrictEqual(['b']);
|
||||
|
||||
expect(
|
||||
slowcat('a', slowcat('b', 'c'))
|
||||
._early(1)
|
||||
.firstCycle()
|
||||
.map((x) => x.value),
|
||||
).toStrictEqual(['b']);
|
||||
|
||||
expect(
|
||||
slowcat('a', slowcat('b', 'c'))
|
||||
._early(3)
|
||||
.firstCycle()
|
||||
.map((x) => x.value),
|
||||
).toStrictEqual(['c']);
|
||||
});
|
||||
it('Can cat subpatterns', () => {
|
||||
sameFirst(slowcat('a', ['b', 'c']).fast(4), sequence('a', ['b', 'c']).fast(2));
|
||||
});
|
||||
});
|
||||
describe('rev()', () => {
|
||||
it('Can reverse things', () => {
|
||||
expect(
|
||||
fastcat('a', 'b', 'c')
|
||||
.rev()
|
||||
.firstCycle()
|
||||
.sort((a, b) => a.part.begin.sub(b.part.begin))
|
||||
.map((a) => a.value),
|
||||
).toStrictEqual(['c', 'b', 'a']);
|
||||
});
|
||||
});
|
||||
describe('sequence()', () => {
|
||||
it('Can work like fastcat', () => {
|
||||
expect(sequence(1, 2, 3).firstCycle()).toStrictEqual(fastcat(1, 2, 3).firstCycle());
|
||||
});
|
||||
});
|
||||
describe('polyrhythm()', () => {
|
||||
it('Can layer up cycles', () => {
|
||||
expect(polyrhythm(['a', 'b'], ['c']).firstCycle()).toStrictEqual(
|
||||
stack(fastcat(pure('a'), pure('b')), pure('c')).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('polymeter()', () => {
|
||||
it('Can layer up cycles, stepwise', () => {
|
||||
expect(polymeterSteps(3, ['d', 'e']).firstCycle()).toStrictEqual(
|
||||
fastcat(pure('d'), pure('e'), pure('d')).firstCycle(),
|
||||
);
|
||||
|
||||
expect(polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual(
|
||||
stack(sequence('a', 'b', 'c', 'a', 'b', 'c'), sequence('d', 'e', 'd', 'e', 'd', 'e')).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('firstOf()', () => {
|
||||
it('Can apply a function every 3rd time', () => {
|
||||
expect(
|
||||
pure('a')
|
||||
.firstOf(3, (x) => x._fast(2))
|
||||
._fast(3)
|
||||
.firstCycle(),
|
||||
).toStrictEqual(sequence(sequence('a', 'a'), 'a', 'a').firstCycle());
|
||||
});
|
||||
it('Works as a toplevel function', () => {
|
||||
expect(firstOf(3, fast(2), pure('a'))._fast(3).firstCycle()).toStrictEqual(
|
||||
sequence(sequence('a', 'a'), 'a', 'a').firstCycle(),
|
||||
);
|
||||
});
|
||||
it('Works as a toplevel function, with a patterned first argument', () => {
|
||||
expect(firstOf(pure(3), fast(2), pure('a'))._fast(3).firstCycle()).toStrictEqual(
|
||||
sequence(sequence('a', 'a'), 'a', 'a').firstCycle(),
|
||||
);
|
||||
});
|
||||
it('works with currying', () => {
|
||||
expect(pure('a').firstOf(3, fast(2))._fast(3).firstCycle()).toStrictEqual(
|
||||
sequence(sequence('a', 'a'), 'a', 'a').firstCycle(),
|
||||
);
|
||||
expect(sequence(3, 4, 5).firstOf(3, add(3)).fast(5).firstCycle()).toStrictEqual(
|
||||
sequence(6, 7, 8, 3, 4, 5, 3, 4, 5, 6, 7, 8, 3, 4, 5).firstCycle(),
|
||||
);
|
||||
expect(sequence(3, 4, 5).firstOf(2, sub(1)).fast(5).firstCycle()).toStrictEqual(
|
||||
sequence(2, 3, 4, 3, 4, 5, 2, 3, 4, 3, 4, 5, 2, 3, 4).firstCycle(),
|
||||
);
|
||||
expect(sequence(3, 4, 5).firstOf(3, add(3)).firstOf(2, sub(1)).fast(2).firstCycle()).toStrictEqual(
|
||||
sequence(5, 6, 7, 3, 4, 5).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('brak()', () => {
|
||||
it('Can make something a bit breakbeaty', () => {
|
||||
sameFirst(sequence('a', 'b').brak()._fast(2), sequence('a', 'b', fastcat(silence, 'a'), fastcat('b', silence)));
|
||||
});
|
||||
});
|
||||
describe('timeCat()', () => {
|
||||
it('Can concatenate patterns with different relative durations', () => {
|
||||
expect(sequence('a', ['a', 'a']).firstCycle()).toStrictEqual(
|
||||
timeCat([1, 'a'], [0.5, 'a'], [0.5, 'a']).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('struct()', () => {
|
||||
it('Can restructure a discrete pattern', () => {
|
||||
expect(sequence('a', 'b').struct(sequence(true, true, true)).firstCycle()).toStrictEqual([
|
||||
hap(ts(0, third), ts(0, third), 'a'),
|
||||
hap(ts(third, twothirds), ts(third, 0.5), 'a'),
|
||||
hap(ts(third, twothirds), ts(0.5, twothirds), 'b'),
|
||||
hap(ts(twothirds, 1), ts(twothirds, 1), 'b'),
|
||||
]);
|
||||
|
||||
expect(
|
||||
pure('a')
|
||||
.struct(sequence(true, [true, false], true))
|
||||
.firstCycle(),
|
||||
).toStrictEqual(sequence('a', ['a', silence], 'a').firstCycle());
|
||||
|
||||
expect(
|
||||
pure('a')
|
||||
.struct(sequence(true, [true, false], true).invert())
|
||||
.firstCycle(),
|
||||
).toStrictEqual(sequence(silence, [silence, 'a'], silence).firstCycle());
|
||||
|
||||
expect(
|
||||
pure('a')
|
||||
.struct(sequence(true, [true, silence], true))
|
||||
.firstCycle(),
|
||||
).toStrictEqual(sequence('a', ['a', silence], 'a').firstCycle());
|
||||
});
|
||||
it('Can structure a continuous pattern', () => {
|
||||
expect(steady('a').struct(true, [true, true]).firstCycle()).toStrictEqual(sequence('a', ['a', 'a']).firstCycle());
|
||||
});
|
||||
});
|
||||
describe('mask()', () => {
|
||||
it('Can fragment a pattern', () => {
|
||||
expect(sequence('a', 'b').mask(sequence(true, true, true)).firstCycle()).toStrictEqual([
|
||||
hap(ts(0, 0.5), ts(0, third), 'a'),
|
||||
hap(ts(0, 0.5), ts(third, 0.5), 'a'),
|
||||
hap(ts(0.5, 1), ts(0.5, twothirds), 'b'),
|
||||
hap(ts(0.5, 1), ts(twothirds, 1), 'b'),
|
||||
]);
|
||||
});
|
||||
it('Can mask off parts of a pattern', () => {
|
||||
expect(sequence(['a', 'b'], 'c').mask(sequence(true, false)).firstCycle()).toStrictEqual(
|
||||
sequence(['a', 'b'], silence).firstCycle(),
|
||||
);
|
||||
|
||||
expect(sequence('a').mask(sequence(true, false)).firstCycle()).toStrictEqual([hap(ts(0, 1), ts(0, 0.5), 'a')]);
|
||||
});
|
||||
});
|
||||
describe('invert()', () => {
|
||||
it('Can invert a binary pattern', () => {
|
||||
expect(sequence(true, false, [true, false]).invert().firstCycle()).toStrictEqual(
|
||||
sequence(false, true, [false, true]).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('signal()', () => {
|
||||
it('Can make saw/saw2', () => {
|
||||
expect(saw.struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(1 / 8, 3 / 8, 5 / 8, 7 / 8).firstCycle(),
|
||||
);
|
||||
|
||||
expect(saw2.struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(-3 / 4, -1 / 4, 1 / 4, 3 / 4).firstCycle(),
|
||||
);
|
||||
});
|
||||
it('Can make isaw/isaw2', () => {
|
||||
expect(isaw.struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(7 / 8, 5 / 8, 3 / 8, 1 / 8).firstCycle(),
|
||||
);
|
||||
|
||||
expect(isaw2.struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(3 / 4, 1 / 4, -1 / 4, -3 / 4).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('_setContext()', () => {
|
||||
it('Can set the hap context', () => {
|
||||
expect(
|
||||
pure('a')
|
||||
.setContext([
|
||||
[
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
],
|
||||
])
|
||||
.firstCycle(true),
|
||||
).toStrictEqual([
|
||||
hap(ts(0, 1), ts(0, 1), 'a', [
|
||||
[
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('_withContext()', () => {
|
||||
it('Can update the hap context', () => {
|
||||
expect(
|
||||
pure('a')
|
||||
.setContext([
|
||||
[
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
],
|
||||
])
|
||||
.withContext((c) => [
|
||||
...c,
|
||||
[
|
||||
[3, 4],
|
||||
[3, 4],
|
||||
],
|
||||
])
|
||||
.firstCycle(true),
|
||||
).toStrictEqual([
|
||||
hap(ts(0, 1), ts(0, 1), 'a', [
|
||||
[
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
],
|
||||
[
|
||||
[3, 4],
|
||||
[3, 4],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('apply', () => {
|
||||
it('Can apply a function', () => {
|
||||
expect(sequence('a', 'b').apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle());
|
||||
}),
|
||||
it('Can apply a pattern of functions', () => {
|
||||
expect(sequence('a', 'b').apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle());
|
||||
expect(sequence('a', 'b').apply(fast(2), fast(3)).firstCycle()).toStrictEqual(
|
||||
sequence('a', 'b').fast(2, 3).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('layer', () => {
|
||||
it('Can layer up multiple functions', () => {
|
||||
expect(
|
||||
sequence(1, 2, 3)
|
||||
.layer(fast(2), (pat) => pat.add(3, 4))
|
||||
.firstCycle(),
|
||||
).toStrictEqual(stack(sequence(1, 2, 3).fast(2), sequence(1, 2, 3).add(3, 4)).firstCycle());
|
||||
});
|
||||
});
|
||||
describe('early', () => {
|
||||
it('Can shift a hap earlier', () => {
|
||||
expect(pure(30)._late(0.25).query(st(1, 2))).toStrictEqual([
|
||||
hap(ts(1 / 4, 5 / 4), ts(1, 5 / 4), 30),
|
||||
hap(ts(5 / 4, 9 / 4), ts(5 / 4, 2), 30),
|
||||
]);
|
||||
});
|
||||
it('Can shift a hap earlier, into negative time', () => {
|
||||
expect(pure(30)._late(0.25).query(st(0, 1))).toStrictEqual([
|
||||
hap(ts(-3 / 4, 1 / 4), ts(0, 1 / 4), 30),
|
||||
hap(ts(1 / 4, 5 / 4), ts(1 / 4, 1), 30),
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('off', () => {
|
||||
it('Can offset a transformed pattern from the original', () => {
|
||||
expect(pure(30).off(0.25, add(2)).firstCycle()).toStrictEqual(
|
||||
stack(pure(30), pure(30).late(0.25).add(2)).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('jux', () => {
|
||||
it('Can juxtapose', () => {
|
||||
expect(pure({ a: 1 }).jux(fast(2)).sortHapsByPart().firstCycle()).toStrictEqual(
|
||||
stack(pure({ a: 1, pan: 0 }), pure({ a: 1, pan: 1 }).fast(2))
|
||||
.sortHapsByPart()
|
||||
.firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('juxBy', () => {
|
||||
it('Can juxtapose by half', () => {
|
||||
expect(pure({ a: 1 }).juxBy(0.5, fast(2)).sortHapsByPart().firstCycle()).toStrictEqual(
|
||||
stack(pure({ a: 1, pan: 0.25 }), pure({ a: 1, pan: 0.75 }).fast(2))
|
||||
.sortHapsByPart()
|
||||
.firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('_squeezeJoin', () => {
|
||||
it('Can squeeze', () => {
|
||||
expect(
|
||||
sequence('a', ['a', 'a'])
|
||||
.fmap((a) => fastcat('b', 'c'))
|
||||
.squeezeJoin()
|
||||
.firstCycle(),
|
||||
).toStrictEqual(
|
||||
sequence(
|
||||
['b', 'c'],
|
||||
[
|
||||
['b', 'c'],
|
||||
['b', 'c'],
|
||||
],
|
||||
).firstCycle(),
|
||||
);
|
||||
});
|
||||
it('Squeezes to the correct cycle', () => {
|
||||
expect(
|
||||
pure(time.struct(true))
|
||||
.squeezeJoin()
|
||||
.queryArc(3, 4)
|
||||
.map((x) => x.value),
|
||||
).toStrictEqual([Fraction(3.5)]);
|
||||
});
|
||||
});
|
||||
describe('ply', () => {
|
||||
it('Can ply(3)', () => {
|
||||
expect(sequence('a', ['b', 'c']).ply(3).firstCycle()).toStrictEqual(
|
||||
sequence(pure('a').fast(3), [pure('b').fast(3), pure('c').fast(3)]).firstCycle(),
|
||||
);
|
||||
});
|
||||
it('Doesnt drop haps in the 9th cycle', () => {
|
||||
// fixed with https://github.com/tidalcycles/strudel/commit/72eeaf446e3d5e186d63cc0d2276f0723cde017a
|
||||
expect(sequence(1, 2, 3).ply(2).early(8).firstCycle().length).toBe(6);
|
||||
});
|
||||
});
|
||||
describe('striate', () => {
|
||||
it('Can striate(2)', () => {
|
||||
sameFirst(
|
||||
sequence({ sound: 'a' }).striate(2),
|
||||
sequence({ sound: 'a', begin: 0, end: 0.5 }, { sound: 'a', begin: 0.5, end: 1 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('chop', () => {
|
||||
it('Can _chop(2)', () => {
|
||||
expect(sequence({ sound: 'a' }, { sound: 'b' })._chop(2).firstCycle()).toStrictEqual(
|
||||
sequence(
|
||||
{ sound: 'a', begin: 0, end: 0.5 },
|
||||
{ sound: 'a', begin: 0.5, end: 1 },
|
||||
{ sound: 'b', begin: 0, end: 0.5 },
|
||||
{ sound: 'b', begin: 0.5, end: 1 },
|
||||
).firstCycle(),
|
||||
);
|
||||
});
|
||||
it('Can chop(2,3)', () => {
|
||||
expect(pure({ sound: 'a' }).fast(2).chop(2, 3).sortHapsByPart().firstCycle()).toStrictEqual(
|
||||
sequence(
|
||||
[
|
||||
{ sound: 'a', begin: 0, end: 0.5 },
|
||||
{ sound: 'a', begin: 0.5, end: 1 },
|
||||
],
|
||||
[
|
||||
{ sound: 'a', begin: 0, end: 1 / 3 },
|
||||
{ sound: 'a', begin: 1 / 3, end: 2 / 3 },
|
||||
{ sound: 'a', begin: 2 / 3, end: 1 },
|
||||
],
|
||||
)
|
||||
.sortHapsByPart()
|
||||
.firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('range', () => {
|
||||
it('Can be patterned', () => {
|
||||
expect(sequence(0, 0).range(sequence(0, 0.5), 1).firstCycle()).toStrictEqual(sequence(0, 0.5).firstCycle());
|
||||
});
|
||||
});
|
||||
describe('range2', () => {
|
||||
it('Can change the range of a bipolar pattern', () => {
|
||||
expect(sequence(-1, -0.5, 0, 0.5).range2(1000, 1100).firstCycle()).toStrictEqual(
|
||||
sequence(1000, 1025, 1050, 1075).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('linger', () => {
|
||||
it('Can linger on the first quarter of a cycle', () => {
|
||||
expect(sequence(0, 1, 2, 3, 4, 5, 6, 7).linger(0.25).firstCycle()).toStrictEqual(
|
||||
sequence(0, 1, 0, 1, 0, 1, 0, 1).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('alignments', () => {
|
||||
it('Can squeeze arguments', () => {
|
||||
expect(sequence(1, 2).add.squeeze(4, 5).firstCycle()).toStrictEqual(sequence(5, 6, 6, 7).firstCycle());
|
||||
});
|
||||
});
|
||||
describe('defragmentHaps', () => {
|
||||
it('Can merge two touching haps with same whole and value', () => {
|
||||
expect(stack(pure('a').mask(1, 0), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(1);
|
||||
});
|
||||
it('Doesnt merge two overlapping haps', () => {
|
||||
expect(stack(pure('a').mask(1, 1, 0), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(
|
||||
2,
|
||||
);
|
||||
});
|
||||
it('Doesnt merge two touching haps with different values', () => {
|
||||
expect(stack(pure('a').mask(1, 0), pure('b').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(2);
|
||||
});
|
||||
it('Doesnt merge two touching haps with different wholes', () => {
|
||||
expect(stack(sequence('a', silence), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
util.test.mjs - Tests for the core 'util' module
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/util.test.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { pure } from '../pattern.mjs';
|
||||
import {
|
||||
isNote,
|
||||
tokenizeNote,
|
||||
toMidi,
|
||||
fromMidi,
|
||||
freqToMidi,
|
||||
_mod,
|
||||
compose,
|
||||
getFrequency,
|
||||
getPlayableNoteValue,
|
||||
parseNumeral,
|
||||
parseFractional,
|
||||
numeralArgs,
|
||||
fractionalArgs,
|
||||
} from '../util.mjs';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('isNote', () => {
|
||||
it('should recognize notes without accidentals', () => {
|
||||
'C3 D3 E3 F3 G3 A3 B3 C4 D5 c5 d5 e5'.split(' ').forEach((note) => {
|
||||
expect(isNote(note)).toBe(true);
|
||||
});
|
||||
});
|
||||
it('should recognize notes with accidentals', () => {
|
||||
'C#3 D##3 Eb3 Fbb3 Bb5'.split(' ').forEach((note) => {
|
||||
expect(isNote(note)).toBe(true);
|
||||
});
|
||||
});
|
||||
it('should recognize notes without octave', () => {
|
||||
expect(isNote('C')).toBe(true);
|
||||
expect(isNote('Bb')).toBe(true);
|
||||
});
|
||||
it('should not recognize invalid notes', () => {
|
||||
expect(isNote('H5')).toBe(false);
|
||||
expect(isNote('X')).toBe(false);
|
||||
expect(isNote(1)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNote', () => {
|
||||
it('should tokenize notes without accidentals', () => {
|
||||
expect(tokenizeNote('C3')).toStrictEqual(['C', '', 3]);
|
||||
expect(tokenizeNote('D3')).toStrictEqual(['D', '', 3]);
|
||||
expect(tokenizeNote('E3')).toStrictEqual(['E', '', 3]);
|
||||
expect(tokenizeNote('F3')).toStrictEqual(['F', '', 3]);
|
||||
expect(tokenizeNote('G3')).toStrictEqual(['G', '', 3]);
|
||||
expect(tokenizeNote('A3')).toStrictEqual(['A', '', 3]);
|
||||
expect(tokenizeNote('B3')).toStrictEqual(['B', '', 3]);
|
||||
expect(tokenizeNote('C4')).toStrictEqual(['C', '', 4]);
|
||||
expect(tokenizeNote('D5')).toStrictEqual(['D', '', 5]);
|
||||
});
|
||||
it('should tokenize notes with accidentals', () => {
|
||||
expect(tokenizeNote('C#3')).toStrictEqual(['C', '#', 3]);
|
||||
expect(tokenizeNote('D##3')).toStrictEqual(['D', '##', 3]);
|
||||
expect(tokenizeNote('Eb3')).toStrictEqual(['E', 'b', 3]);
|
||||
expect(tokenizeNote('Fbb3')).toStrictEqual(['F', 'bb', 3]);
|
||||
expect(tokenizeNote('Bb5')).toStrictEqual(['B', 'b', 5]);
|
||||
});
|
||||
it('should tokenize notes without octave', () => {
|
||||
expect(tokenizeNote('C')).toStrictEqual(['C', '', undefined]);
|
||||
expect(tokenizeNote('C#')).toStrictEqual(['C', '#', undefined]);
|
||||
expect(tokenizeNote('Bb')).toStrictEqual(['B', 'b', undefined]);
|
||||
expect(tokenizeNote('Bbb')).toStrictEqual(['B', 'bb', undefined]);
|
||||
});
|
||||
it('should not tokenize invalid notes', () => {
|
||||
expect(tokenizeNote('X')).toStrictEqual([]);
|
||||
expect(tokenizeNote('asfasf')).toStrictEqual([]);
|
||||
expect(tokenizeNote(123)).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
describe('toMidi', () => {
|
||||
it('should turn notes into midi', () => {
|
||||
expect(toMidi('A4')).toEqual(69);
|
||||
expect(toMidi('C4')).toEqual(60);
|
||||
expect(toMidi('Db4')).toEqual(61);
|
||||
expect(toMidi('C3')).toEqual(48);
|
||||
expect(toMidi('Cb3')).toEqual(47);
|
||||
expect(toMidi('Cbb3')).toEqual(46);
|
||||
expect(toMidi('C#3')).toEqual(49);
|
||||
expect(toMidi('C#3')).toEqual(49);
|
||||
expect(toMidi('C##3')).toEqual(50);
|
||||
});
|
||||
it('should throw an error when given a non-note', () => {
|
||||
expect(() => toMidi('Q')).toThrowError(`not a note: "Q"`);
|
||||
expect(() => toMidi('Z')).toThrowError(`not a note: "Z"`);
|
||||
});
|
||||
});
|
||||
describe('fromMidi', () => {
|
||||
it('should turn midi into frequency', () => {
|
||||
expect(fromMidi(69)).toEqual(440);
|
||||
expect(fromMidi(57)).toEqual(220);
|
||||
});
|
||||
});
|
||||
describe('freqToMidi', () => {
|
||||
it('should turn frequency into midi', () => {
|
||||
expect(freqToMidi(440)).toEqual(69);
|
||||
expect(freqToMidi(220)).toEqual(57);
|
||||
});
|
||||
});
|
||||
describe('getFrequency', () => {
|
||||
const happify = (val, context = {}) => pure(val).firstCycle()[0].setContext(context);
|
||||
it('should turn note into frequency', () => {
|
||||
expect(getFrequency(happify('a4'))).toEqual(440);
|
||||
expect(getFrequency(happify('a3'))).toEqual(220);
|
||||
});
|
||||
it('should turn midi into frequency', () => {
|
||||
expect(getFrequency(happify(69, { type: 'midi' }))).toEqual(440);
|
||||
expect(getFrequency(happify(57, { type: 'midi' }))).toEqual(220);
|
||||
});
|
||||
it('should return frequencies unchanged', () => {
|
||||
expect(getFrequency(happify(440, { type: 'frequency' }))).toEqual(440);
|
||||
expect(getFrequency(happify(432, { type: 'frequency' }))).toEqual(432);
|
||||
});
|
||||
it('should turn object with a "freq" property into frequency', () => {
|
||||
expect(getFrequency(happify({ freq: 220 }))).toEqual(220);
|
||||
expect(getFrequency(happify({ freq: 440 }))).toEqual(440);
|
||||
});
|
||||
it('should throw an error when given a non-note', () => {
|
||||
expect(() => getFrequency(happify('Q'))).toThrowError(`not a note or frequency: Q`);
|
||||
expect(() => getFrequency(happify('Z'))).toThrowError(`not a note or frequency: Z`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_mod', () => {
|
||||
it('should work like regular modulo with positive numbers', () => {
|
||||
expect(_mod(0, 3)).toEqual(0);
|
||||
expect(_mod(1, 3)).toEqual(1);
|
||||
expect(_mod(2, 3)).toEqual(2);
|
||||
expect(_mod(3, 3)).toEqual(0);
|
||||
expect(_mod(4, 3)).toEqual(1);
|
||||
expect(_mod(4, 2)).toEqual(0);
|
||||
});
|
||||
it('should work with negative numbers', () => {
|
||||
expect(_mod(-1, 3)).toEqual(2);
|
||||
expect(_mod(-2, 3)).toEqual(1);
|
||||
expect(_mod(-3, 3)).toEqual(0);
|
||||
expect(_mod(-4, 3)).toEqual(2);
|
||||
expect(_mod(-5, 3)).toEqual(1);
|
||||
expect(_mod(-3, 2)).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('compose', () => {
|
||||
const add1 = (a) => a + 1;
|
||||
it('should compose', () => {
|
||||
expect(compose(add1, add1)(0)).toEqual(2);
|
||||
expect(compose(add1)(0)).toEqual(1);
|
||||
});
|
||||
const addS = (s) => (a) => a + s;
|
||||
it('should compose left to right', () => {
|
||||
expect(compose(addS('a'), addS('b'))('')).toEqual('ab');
|
||||
expect(compose(addS('a'), addS('b'))('x')).toEqual('xab');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPlayableNoteValue', () => {
|
||||
const happify = (val, context = {}) => pure(val).firstCycle()[0].setContext(context);
|
||||
it('should return object "note" property', () => {
|
||||
expect(getPlayableNoteValue(happify({ note: 'a4' }))).toEqual('a4');
|
||||
});
|
||||
it('should return object "n" property', () => {
|
||||
expect(getPlayableNoteValue(happify({ n: 'a4' }))).toEqual('a4');
|
||||
});
|
||||
it('should return object "value" property', () => {
|
||||
expect(getPlayableNoteValue(happify({ value: 'a4' }))).toEqual('a4');
|
||||
});
|
||||
it('should turn midi into frequency', () => {
|
||||
expect(getPlayableNoteValue(happify(57, { type: 'midi' }))).toEqual(220);
|
||||
});
|
||||
it('should return frequency value', () => {
|
||||
expect(getPlayableNoteValue(happify(220, { type: 'frequency' }))).toEqual(220);
|
||||
});
|
||||
it('should throw an error if value is not an object, number, or string', () => {
|
||||
expect(() => getPlayableNoteValue(happify(false))).toThrowError(`not a note: false`);
|
||||
expect(() => getPlayableNoteValue(happify(undefined))).toThrowError(`not a note: undefined`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseNumeral', () => {
|
||||
it('should parse numbers as is', () => {
|
||||
expect(parseNumeral(4)).toBe(4);
|
||||
expect(parseNumeral(0)).toBe(0);
|
||||
expect(parseNumeral(20)).toBe(20);
|
||||
expect(parseNumeral('20')).toBe(20);
|
||||
expect(parseNumeral(1.5)).toBe(1.5);
|
||||
});
|
||||
it('should parse notes', () => {
|
||||
expect(parseNumeral('c')).toBe(48);
|
||||
expect(parseNumeral('c4')).toBe(60);
|
||||
expect(parseNumeral('c#4')).toBe(61);
|
||||
expect(parseNumeral('db4')).toBe(61);
|
||||
});
|
||||
it('should throw an error for unknown strings', () => {
|
||||
expect(() => parseNumeral('xyz')).toThrowError('cannot parse as numeral: "xyz"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseFractional', () => {
|
||||
it('should parse numbers as is', () => {
|
||||
expect(parseFractional(4)).toBe(4);
|
||||
expect(parseFractional(0)).toBe(0);
|
||||
expect(parseFractional(20)).toBe(20);
|
||||
expect(parseFractional('20')).toBe(20);
|
||||
expect(parseFractional(1.5)).toBe(1.5);
|
||||
});
|
||||
it('should parse fractional shorthands values', () => {
|
||||
expect(parseFractional('w')).toBe(1);
|
||||
expect(parseFractional('h')).toBe(0.5);
|
||||
expect(parseFractional('q')).toBe(0.25);
|
||||
expect(parseFractional('e')).toBe(0.125);
|
||||
});
|
||||
it('should throw an error for unknown strings', () => {
|
||||
expect(() => parseFractional('xyz')).toThrowError('cannot parse as fractional: "xyz"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('numeralArgs', () => {
|
||||
it('should convert function arguments to numbers', () => {
|
||||
const add = numeralArgs((a, b) => a + b);
|
||||
expect(add('c4', 2)).toBe(62);
|
||||
});
|
||||
});
|
||||
describe('fractionalArgs', () => {
|
||||
it('should convert function arguments to numbers', () => {
|
||||
const add = fractionalArgs((a, b) => a + b);
|
||||
expect(add('q', 2)).toBe(2.25);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
value.test.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/value.test.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { map, valued, mul } from '../value.mjs';
|
||||
import controls from '../controls.mjs';
|
||||
const { n } = controls;
|
||||
|
||||
describe('Value', () => {
|
||||
it('unionWith', () => {
|
||||
const { value } = valued({ freq: 2000, distortion: 1.2 }).unionWith({ distortion: 2 }, mul);
|
||||
expect(value).toStrictEqual({ freq: 2000, distortion: 2.4 });
|
||||
});
|
||||
|
||||
it('experiments', () => {
|
||||
expect(map(mul(5), valued(3)).value).toEqual(15);
|
||||
expect(map(mul(null), valued(3)).value).toEqual(0);
|
||||
expect(map(mul(3), valued(null)).value).toEqual(null);
|
||||
expect(valued(3).map(mul).ap(3).value).toEqual(9);
|
||||
expect(valued(mul).ap(3).ap(3).value).toEqual(9);
|
||||
expect(valued(3).mul(3).value).toEqual(9);
|
||||
});
|
||||
it('union bare numbers for numeral props', () => {
|
||||
expect(n(3).cutoff(500).add(10).firstCycleValues).toEqual([{ n: 13, cutoff: 510 }]);
|
||||
expect(n(3).cutoff(500).mul(2).firstCycleValues).toEqual([{ n: 6, cutoff: 1000 }]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
let time;
|
||||
export function getTime() {
|
||||
if (!time) {
|
||||
throw new Error('no time set! use setTime to define a time source');
|
||||
}
|
||||
return time();
|
||||
}
|
||||
|
||||
export function setTime(func) {
|
||||
time = func;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
timespan.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/timespan.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Fraction from './fraction.mjs';
|
||||
|
||||
export class TimeSpan {
|
||||
constructor(begin, end) {
|
||||
this.begin = Fraction(begin);
|
||||
this.end = Fraction(end);
|
||||
}
|
||||
|
||||
get spanCycles() {
|
||||
const spans = [];
|
||||
var begin = this.begin;
|
||||
const end = this.end;
|
||||
const end_sam = end.sam();
|
||||
|
||||
// Support zero-width timespans
|
||||
if (begin.equals(end)) {
|
||||
return [new TimeSpan(begin, end)];
|
||||
}
|
||||
|
||||
while (end.gt(begin)) {
|
||||
// If begin and end are in the same cycle, we're done.
|
||||
if (begin.sam().equals(end_sam)) {
|
||||
spans.push(new TimeSpan(begin, this.end));
|
||||
break;
|
||||
}
|
||||
// add a timespan up to the next sam
|
||||
const next_begin = begin.nextSam();
|
||||
spans.push(new TimeSpan(begin, next_begin));
|
||||
|
||||
// continue with the next cycle
|
||||
begin = next_begin;
|
||||
}
|
||||
return spans;
|
||||
}
|
||||
|
||||
get duration() {
|
||||
return this.end.sub(this.begin);
|
||||
}
|
||||
|
||||
cycleArc() {
|
||||
// Shifts a timespan to one of equal duration that starts within cycle zero.
|
||||
// (Note that the output timespan probably does not start *at* Time 0 --
|
||||
// that only happens when the input Arc starts at an integral Time.)
|
||||
const b = this.begin.cyclePos();
|
||||
const e = b.add(this.duration);
|
||||
return new TimeSpan(b, e);
|
||||
}
|
||||
|
||||
withTime(func_time) {
|
||||
// Applies given function to both the begin and end time of the timespan"""
|
||||
return new TimeSpan(func_time(this.begin), func_time(this.end));
|
||||
}
|
||||
|
||||
withEnd(func_time) {
|
||||
// Applies given function to the end time of the timespan"""
|
||||
return new TimeSpan(this.begin, func_time(this.end));
|
||||
}
|
||||
|
||||
withCycle(func_time) {
|
||||
// Like withTime, but time is relative to relative to the cycle (i.e. the
|
||||
// sam of the start of the timespan)
|
||||
const sam = this.begin.sam();
|
||||
const b = sam.add(func_time(this.begin.sub(sam)));
|
||||
const e = sam.add(func_time(this.end.sub(sam)));
|
||||
return new TimeSpan(b, e);
|
||||
}
|
||||
|
||||
intersection(other) {
|
||||
// Intersection of two timespans, returns None if they don't intersect.
|
||||
const intersect_begin = this.begin.max(other.begin);
|
||||
const intersect_end = this.end.min(other.end);
|
||||
|
||||
if (intersect_begin.gt(intersect_end)) {
|
||||
return undefined;
|
||||
}
|
||||
if (intersect_begin.equals(intersect_end)) {
|
||||
// Zero-width (point) intersection - doesn't intersect if it's at the end of a
|
||||
// non-zero-width timespan.
|
||||
if (intersect_begin.equals(this.end) && this.begin.lt(this.end)) {
|
||||
return undefined;
|
||||
}
|
||||
if (intersect_begin.equals(other.end) && other.begin.lt(other.end)) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return new TimeSpan(intersect_begin, intersect_end);
|
||||
}
|
||||
|
||||
intersection_e(other) {
|
||||
// Like 'sect', but raises an exception if the timespans don't intersect.
|
||||
const result = this.intersection(other);
|
||||
if (result == undefined) {
|
||||
throw 'TimeSpans do not intersect';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
midpoint() {
|
||||
return this.begin.add(this.duration.div(Fraction(2)));
|
||||
}
|
||||
|
||||
equals(other) {
|
||||
return this.begin.equals(other.begin) && this.end.equals(other.end);
|
||||
}
|
||||
|
||||
show() {
|
||||
return this.begin.show() + ' → ' + this.end.show();
|
||||
}
|
||||
}
|
||||
|
||||
export default TimeSpan;
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
ui.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/tone/ui.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getTime } from './time.mjs';
|
||||
|
||||
function frame(callback) {
|
||||
if (window.strudelAnimation) {
|
||||
cancelAnimationFrame(window.strudelAnimation);
|
||||
}
|
||||
const animate = (animationTime) => {
|
||||
callback(animationTime, getTime());
|
||||
window.strudelAnimation = requestAnimationFrame(animate);
|
||||
};
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
export const backgroundImage = function (src, animateOptions = {}) {
|
||||
const container = document.getElementById('code');
|
||||
const bg = 'background-image:url(' + src + ');background-size:contain;';
|
||||
container.style = bg;
|
||||
const { className: initialClassName } = container;
|
||||
const handleOption = (option, value) => {
|
||||
({
|
||||
style: () => (container.style = bg + ';' + value),
|
||||
className: () => (container.className = value + ' ' + initialClassName),
|
||||
}[option]());
|
||||
};
|
||||
const funcOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'function');
|
||||
const stringOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'string');
|
||||
stringOptions.forEach(([option, value]) => handleOption(option, value));
|
||||
|
||||
if (funcOptions.length === 0) {
|
||||
return;
|
||||
}
|
||||
frame((_, t) =>
|
||||
funcOptions.forEach(([option, value]) => {
|
||||
handleOption(option, value(t));
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const cleanupUi = () => {
|
||||
const container = document.getElementById('code');
|
||||
if (container) {
|
||||
container.style = '';
|
||||
// TODO: find a way to remove that duplication..
|
||||
container.className = 'grow flex text-gray-100 relative overflow-auto cursor-text pb-0'; // has to match App.tsx
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
util.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/util.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern } from './pattern.mjs';
|
||||
|
||||
// returns true if the given string is a note
|
||||
export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name);
|
||||
export const isNote = (name) => /^[a-gA-G][#bs]*[0-9]?$/.test(name);
|
||||
export const tokenizeNote = (note) => {
|
||||
if (typeof note !== 'string') {
|
||||
return [];
|
||||
}
|
||||
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bs]*)([0-9])?$/)?.slice(1) || [];
|
||||
if (!pc) {
|
||||
return [];
|
||||
}
|
||||
return [pc, acc, oct ? Number(oct) : undefined];
|
||||
};
|
||||
|
||||
// turns the given note into its midi number representation
|
||||
export const toMidi = (note) => {
|
||||
const [pc, acc, oct = 3] = tokenizeNote(note);
|
||||
if (!pc) {
|
||||
throw new Error('not a note: "' + note + '"');
|
||||
}
|
||||
const chroma = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }[pc.toLowerCase()];
|
||||
const offset = acc?.split('').reduce((o, char) => o + { '#': 1, b: -1, s: 1 }[char], 0) || 0;
|
||||
return (Number(oct) + 1) * 12 + chroma + offset;
|
||||
};
|
||||
export const fromMidi = (n) => {
|
||||
return Math.pow(2, (n - 69) / 12) * 440;
|
||||
};
|
||||
|
||||
export const freqToMidi = (freq) => {
|
||||
return (12 * Math.log(freq / 440)) / Math.LN2 + 69;
|
||||
};
|
||||
|
||||
export const valueToMidi = (value, fallbackValue) => {
|
||||
if (typeof value !== 'object') {
|
||||
throw new Error('valueToMidi: expected object value');
|
||||
}
|
||||
let { freq, note } = value;
|
||||
if (typeof freq === 'number') {
|
||||
return freqToMidi(freq);
|
||||
}
|
||||
if (typeof note === 'string') {
|
||||
return toMidi(note);
|
||||
}
|
||||
if (typeof note === 'number') {
|
||||
return note;
|
||||
}
|
||||
if (!fallbackValue) {
|
||||
throw new Error('valueToMidi: expected freq or note to be set');
|
||||
}
|
||||
return fallbackValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
|
||||
*/
|
||||
export const getFreq = (noteOrMidi) => {
|
||||
if (typeof noteOrMidi === 'number') {
|
||||
return fromMidi(noteOrMidi);
|
||||
}
|
||||
return fromMidi(toMidi(noteOrMidi));
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
|
||||
*/
|
||||
export const midi2note = (n) => {
|
||||
const oct = Math.floor(n / 12) - 1;
|
||||
const pc = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'][n % 12];
|
||||
return pc + oct;
|
||||
};
|
||||
|
||||
// modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does)
|
||||
export const _mod = (n, m) => ((n % m) + m) % m;
|
||||
|
||||
export const getPlayableNoteValue = (hap) => {
|
||||
let { value, context } = hap;
|
||||
let note = value;
|
||||
if (typeof note === 'object' && !Array.isArray(note)) {
|
||||
note = note.note || note.n || note.value;
|
||||
if (note === undefined) {
|
||||
throw new Error(`cannot find a playable note for ${JSON.stringify(value)}`);
|
||||
}
|
||||
}
|
||||
// if value is number => interpret as midi number as long as its not marked as frequency
|
||||
if (typeof note === 'number' && context.type !== 'frequency') {
|
||||
note = fromMidi(hap.value);
|
||||
} else if (typeof note === 'number' && context.type === 'frequency') {
|
||||
note = hap.value; // legacy workaround.. will be removed in the future
|
||||
} else if (typeof note !== 'string' || !isNote(note)) {
|
||||
throw new Error('not a note: ' + JSON.stringify(note));
|
||||
}
|
||||
return note;
|
||||
};
|
||||
|
||||
export const getFrequency = (hap) => {
|
||||
let { value, context } = hap;
|
||||
// if value is number => interpret as midi number as long as its not marked as frequency
|
||||
if (typeof value === 'object') {
|
||||
if (value.freq) {
|
||||
return value.freq;
|
||||
}
|
||||
return getFreq(value.note || value.n || value.value);
|
||||
}
|
||||
if (typeof value === 'number' && context.type !== 'frequency') {
|
||||
value = fromMidi(hap.value);
|
||||
} else if (typeof value === 'string' && isNote(value)) {
|
||||
value = fromMidi(toMidi(hap.value));
|
||||
} else if (typeof value !== 'number') {
|
||||
throw new Error('not a note or frequency: ' + value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
// rotate array by n steps (to the left)
|
||||
export const rotate = (arr, n) => arr.slice(n).concat(arr.slice(0, n));
|
||||
|
||||
export const pipe = (...funcs) => {
|
||||
return funcs.reduce(
|
||||
(f, g) =>
|
||||
(...args) =>
|
||||
f(g(...args)),
|
||||
(x) => x,
|
||||
);
|
||||
};
|
||||
|
||||
export const compose = (...funcs) => pipe(...funcs.reverse());
|
||||
|
||||
// Removes 'None' values from given list
|
||||
export const removeUndefineds = (xs) => xs.filter((x) => x != undefined);
|
||||
|
||||
export const flatten = (arr) => [].concat(...arr);
|
||||
|
||||
export const id = (a) => a;
|
||||
export const constant = (a, b) => a;
|
||||
|
||||
export const listRange = (min, max) => Array.from({ length: max - min + 1 }, (_, i) => i + min);
|
||||
|
||||
export function curry(func, overload, arity = func.length) {
|
||||
const fn = function curried(...args) {
|
||||
if (args.length >= arity) {
|
||||
return func.apply(this, args);
|
||||
} else {
|
||||
const partial = function (...args2) {
|
||||
return curried.apply(this, args.concat(args2));
|
||||
};
|
||||
|
||||
if (args.length == arity - 1) {
|
||||
// The penultimate arg.. so add some composition magic
|
||||
// TODO - To make this useful, we also need to add stub functions for every pattern method to
|
||||
// do the actual composing
|
||||
for (const r of Pattern.__registered) {
|
||||
partial[r] = function (...args) {
|
||||
const result = new Pattern(() => []);
|
||||
result.__compose = function (pat) {
|
||||
return partial(pat)[r](...args);
|
||||
};
|
||||
return result;
|
||||
};
|
||||
}
|
||||
partial.__compose = function (pat) {
|
||||
return partial(pat);
|
||||
};
|
||||
}
|
||||
|
||||
if (overload) {
|
||||
overload(partial, args);
|
||||
}
|
||||
return partial;
|
||||
}
|
||||
};
|
||||
if (overload) {
|
||||
// overload function without args... needed for chordBass.transpose(2)
|
||||
overload(fn, []);
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
export function parseNumeral(numOrString) {
|
||||
const asNumber = Number(numOrString);
|
||||
if (!isNaN(asNumber)) {
|
||||
return asNumber;
|
||||
}
|
||||
if (isNote(numOrString)) {
|
||||
return toMidi(numOrString);
|
||||
}
|
||||
throw new Error(`cannot parse as numeral: "${numOrString}"`);
|
||||
}
|
||||
|
||||
export function mapArgs(fn, mapFn) {
|
||||
return (...args) => fn(...args.map(mapFn));
|
||||
}
|
||||
|
||||
export function numeralArgs(fn) {
|
||||
return mapArgs(fn, parseNumeral);
|
||||
}
|
||||
|
||||
export function parseFractional(numOrString) {
|
||||
const asNumber = Number(numOrString);
|
||||
if (!isNaN(asNumber)) {
|
||||
return asNumber;
|
||||
}
|
||||
const specialValue = {
|
||||
pi: Math.PI,
|
||||
w: 1,
|
||||
h: 0.5,
|
||||
q: 0.25,
|
||||
e: 0.125,
|
||||
s: 0.0625,
|
||||
t: 1 / 3,
|
||||
f: 0.2,
|
||||
x: 1 / 6,
|
||||
}[numOrString];
|
||||
if (typeof specialValue !== 'undefined') {
|
||||
return specialValue;
|
||||
}
|
||||
throw new Error(`cannot parse as fractional: "${numOrString}"`);
|
||||
}
|
||||
|
||||
export const fractionalArgs = (fn) => mapArgs(fn, parseFractional);
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
value.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/value.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { curry } from './util.mjs';
|
||||
|
||||
export function unionWithObj(a, b, func) {
|
||||
if (typeof b?.value === 'number') {
|
||||
// https://github.com/tidalcycles/strudel/issues/262
|
||||
const numKeys = Object.keys(a).filter((k) => typeof a[k] === 'number');
|
||||
const numerals = Object.fromEntries(numKeys.map((k) => [k, b.value]));
|
||||
b = Object.assign(b, numerals);
|
||||
delete b.value;
|
||||
}
|
||||
const common = Object.keys(a).filter((k) => Object.keys(b).includes(k));
|
||||
return Object.assign({}, a, b, Object.fromEntries(common.map((k) => [k, func(a[k], b[k])])));
|
||||
}
|
||||
|
||||
export const mul = curry((a, b) => a * b);
|
||||
|
||||
export const valued = (value) => {
|
||||
if (value?.constructor?.name === 'Value') {
|
||||
return value;
|
||||
}
|
||||
return Value.of(value);
|
||||
};
|
||||
|
||||
export class Value {
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
static of(x) {
|
||||
return new Value(x);
|
||||
}
|
||||
get isNothing() {
|
||||
return this.value === null || this.value === undefined;
|
||||
}
|
||||
map(f) {
|
||||
if (this.isNothing) {
|
||||
return this;
|
||||
}
|
||||
return Value.of(f(this.value));
|
||||
}
|
||||
mul(n) {
|
||||
return this.map(mul).ap(n);
|
||||
}
|
||||
ap(other) {
|
||||
return valued(other).map(this.value);
|
||||
}
|
||||
unionWith(other, func) {
|
||||
const type = typeof this.value;
|
||||
other = valued(other);
|
||||
if (type !== typeof other.value) {
|
||||
throw new Error('unionWith: both Values must have same type!');
|
||||
}
|
||||
if (Array.isArray(type) || type !== 'object') {
|
||||
throw new Error('unionWith: expected objects');
|
||||
}
|
||||
return this.map((v) => unionWithObj(v, other.value, func));
|
||||
}
|
||||
}
|
||||
|
||||
export const map = curry((f, anyFunctor) => anyFunctor.map(f));
|
||||
@@ -0,0 +1,49 @@
|
||||
// will move to https://github.com/felixroos/zyklus
|
||||
// TODO: started flag
|
||||
|
||||
function createClock(
|
||||
getTime,
|
||||
callback, // called slightly before each cycle
|
||||
duration = 0.05, // duration of each cycle
|
||||
interval = 0.1, // interval between callbacks
|
||||
overlap = 0.1, // overlap between callbacks
|
||||
) {
|
||||
let tick = 0; // counts callbacks
|
||||
let phase = 0; // next callback time
|
||||
let precision = 10 ** 4; // used to round phase
|
||||
let minLatency = 0.01;
|
||||
const setDuration = (setter) => (duration = setter(duration));
|
||||
overlap = overlap || interval / 2;
|
||||
const onTick = () => {
|
||||
const t = getTime();
|
||||
const lookahead = t + interval + overlap; // the time window for this tick
|
||||
if (phase === 0) {
|
||||
phase = t + minLatency;
|
||||
}
|
||||
// callback as long as we're inside the lookahead
|
||||
while (phase < lookahead) {
|
||||
phase = Math.round(phase * precision) / precision;
|
||||
phase >= t && callback(phase, duration, tick);
|
||||
phase < t && console.log('TOO LATE', phase); // what if latency is added from outside?
|
||||
phase += duration; // increment phase by duration
|
||||
tick++;
|
||||
}
|
||||
};
|
||||
let intervalID;
|
||||
const start = () => {
|
||||
clear(); // just in case start was called more than once
|
||||
onTick();
|
||||
intervalID = setInterval(onTick, interval * 1000);
|
||||
};
|
||||
const clear = () => intervalID !== undefined && clearInterval(intervalID);
|
||||
const pause = () => clear();
|
||||
const stop = () => {
|
||||
tick = 0;
|
||||
phase = 0;
|
||||
clear();
|
||||
};
|
||||
const getPhase = () => phase;
|
||||
// setCallback
|
||||
return { setDuration, start, stop, pause, duration, getPhase, minLatency };
|
||||
}
|
||||
export default createClock;
|
||||
@@ -0,0 +1,170 @@
|
||||
import { getFrequency, logger, register } from '@strudel.cycles/core';
|
||||
import { getAudioContext } from '@strudel.cycles/webaudio';
|
||||
import csd from './project.csd?raw';
|
||||
// import livecodeOrc from './livecode.orc?raw';
|
||||
import presetsOrc from './presets.orc?raw';
|
||||
|
||||
let csoundLoader, _csound;
|
||||
|
||||
// initializes csound + can be used to reevaluate given instrument code
|
||||
export async function loadCSound(code = '') {
|
||||
await init();
|
||||
if (code) {
|
||||
code = `${code}`;
|
||||
// ^ ^
|
||||
// wrapping in backticks makes sure it works when calling as templated function
|
||||
await _csound?.evalCode(code);
|
||||
}
|
||||
}
|
||||
export const loadcsound = loadCSound;
|
||||
export const loadCsound = loadCSound;
|
||||
|
||||
export const csound = register('csound', (instrument, pat) => {
|
||||
instrument = instrument || 'triangle';
|
||||
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
|
||||
// TODO: find a alternative way to wait for csound to load (to wait with first time playback)
|
||||
return pat.onTrigger((time, hap) => {
|
||||
if (!_csound) {
|
||||
logger('[csound] not loaded yet', 'warning');
|
||||
return;
|
||||
}
|
||||
if (typeof hap.value !== 'object') {
|
||||
throw new Error('csound only support objects as hap values');
|
||||
}
|
||||
let { gain = 0.8 } = hap.value;
|
||||
gain *= 0.2;
|
||||
|
||||
const freq = Math.round(getFrequency(hap));
|
||||
const controls = Object.entries({ ...hap.value, freq })
|
||||
.flat()
|
||||
.join('/');
|
||||
// TODO: find out how to send a precise ctx based time
|
||||
// http://www.csounds.com/manual/html/i.html
|
||||
const params = [
|
||||
`"${instrument}"`, // p1: instrument name
|
||||
time - getAudioContext().currentTime, //.toFixed(precision), // p2: starting time in arbitrary unit called beats
|
||||
hap.duration + 0, // p3: duration in beats
|
||||
// instrument specific params:
|
||||
freq, //.toFixed(precision), // p4: frequency
|
||||
gain, // p5: gain
|
||||
`"${controls}"`, // p6 controls as string (like superdirt osc message)
|
||||
];
|
||||
const msg = `i ${params.join(' ')}`;
|
||||
_csound.inputMessage(msg);
|
||||
});
|
||||
});
|
||||
|
||||
function eventLogger(type, args) {
|
||||
const [msg] = args;
|
||||
if (
|
||||
type === 'message' &&
|
||||
(['[commit: HEAD]'].includes(msg) ||
|
||||
msg.startsWith('--Csound version') ||
|
||||
msg.startsWith('libsndfile') ||
|
||||
msg.startsWith('sr =') ||
|
||||
msg.startsWith('0dBFS') ||
|
||||
msg.startsWith('audio buffered') ||
|
||||
msg.startsWith('writing') ||
|
||||
msg.startsWith('SECTION 1:'))
|
||||
) {
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
let logType = 'info';
|
||||
if (msg.startsWith('error:')) {
|
||||
logType = 'error';
|
||||
}
|
||||
logger(`[csound] ${msg || ''}`, logType);
|
||||
}
|
||||
|
||||
async function load() {
|
||||
if (window.__csound__) {
|
||||
// Allows using some other csound instance.
|
||||
// In that case, the external Csound is responsible
|
||||
// for compiling an orchestra and starting to perform.
|
||||
logger('[load] Using external Csound', 'warning');
|
||||
_csound = window.__csound__;
|
||||
return _csound;
|
||||
} else {
|
||||
const { Csound } = await import('@csound/browser');
|
||||
_csound = await Csound({ audioContext: getAudioContext() });
|
||||
_csound.removeAllListeners('message');
|
||||
['message'].forEach((k) => _csound.on(k, (...args) => eventLogger(k, args)));
|
||||
await _csound.setOption('-m0d'); // see -m flag https://csound.com/docs/manual/CommandFlags.html
|
||||
await _csound.setOption('--sample-accurate');
|
||||
await _csound.compileCsdText(csd);
|
||||
// await _csound.compileOrc(livecodeOrc);
|
||||
await _csound.compileOrc(presetsOrc);
|
||||
await _csound.start();
|
||||
return _csound;
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
csoundLoader = csoundLoader || load();
|
||||
return csoundLoader;
|
||||
}
|
||||
|
||||
let orcCache = {};
|
||||
export async function loadOrc(url) {
|
||||
await init();
|
||||
if (typeof url !== 'string') {
|
||||
throw new Error('loadOrc: expected url string');
|
||||
}
|
||||
if (url.startsWith('github:')) {
|
||||
const [_, path] = url.split('github:');
|
||||
url = `https://raw.githubusercontent.com/${path}`;
|
||||
}
|
||||
if (!orcCache[url]) {
|
||||
orcCache[url] = fetch(url)
|
||||
.then((res) => res.text())
|
||||
.then((code) => _csound.compileOrc(code));
|
||||
}
|
||||
await orcCache[url];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends notes to Csound for rendering with MIDI semantics. The hap value is
|
||||
* translated to these Csound pfields:
|
||||
*
|
||||
* p1 -- Csound instrument either as a number (1-based, can be a fraction),
|
||||
* or as a string name.
|
||||
* p2 -- time in beats (usually seconds) from start of performance.
|
||||
* p3 -- duration in beats (usually seconds).
|
||||
* p4 -- MIDI key number (as a real number, not an integer but in [0, 127].
|
||||
* p5 -- MIDI velocity (as a real number, not an integer but in [0, 127].
|
||||
* p6 -- Strudel controls, as a string.
|
||||
*/
|
||||
export const csoundm = register('csoundm', (instrument, pat) => {
|
||||
let p1 = instrument;
|
||||
if (typeof instrument === 'string') {
|
||||
p1 = `"{instrument}"`;
|
||||
}
|
||||
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
|
||||
return pat.onTrigger((tidal_time, hap) => {
|
||||
if (!_csound) {
|
||||
logger('[csound] not loaded yet', 'warning');
|
||||
return;
|
||||
}
|
||||
if (typeof hap.value !== 'object') {
|
||||
throw new Error('csound only support objects as hap values');
|
||||
}
|
||||
// Time in seconds counting from now.
|
||||
const p2 = tidal_time - getAudioContext().currentTime;
|
||||
const p3 = hap.duration.valueOf() + 0;
|
||||
const frequency = getFrequency(hap);
|
||||
// Translate frequency to MIDI key number _without_ rounding.
|
||||
const C4 = 261.62558;
|
||||
let octave = Math.log(frequency / C4) / Math.log(2.0) + 8.0;
|
||||
const p4 = octave * 12.0 - 36.0;
|
||||
// We prefer floating point precision, but over the MIDI range [0, 127].
|
||||
const p5 = 127 * (hap.context?.velocity ?? 0.9);
|
||||
// The Strudel controls as a string.
|
||||
const p6 = Object.entries({ ...hap.value, frequency })
|
||||
.flat()
|
||||
.join('/');
|
||||
const i_statement = `i ${p1} ${p2} ${p3} ${p4} ${p5} "${p6}"`;
|
||||
console.log('[csoundm]:', i_statement);
|
||||
_csound.inputMessage(i_statement);
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "@strudel.cycles/csound",
|
||||
"version": "0.5.1",
|
||||
"description": "csound bindings for strudel",
|
||||
"main": "csound.mjs",
|
||||
"scripts": {
|
||||
"test": "echo \"No tests present.\" && exit 0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Felix Roos <flix91@gmail.com>",
|
||||
"contributors": [
|
||||
"Alex McLean <alex@slab.org>"
|
||||
],
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@csound/browser": "^6.18.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
; returns value of given key in given "string map"
|
||||
; keymap("freq", "note/c3/freq/220/gain/0.5")
|
||||
; yields "220"
|
||||
opcode keymap, S, SS
|
||||
Skey, Smap xin
|
||||
idelimiter = strindex(Smap, strcat(Skey, "/"))
|
||||
ifrom = idelimiter + strlen(Skey) + 1
|
||||
Svalue = strsub(Smap, ifrom, strlen(Smap))
|
||||
Svalue = strsub(Svalue, 0, strindex(Svalue, "/"))
|
||||
xout Svalue
|
||||
endop
|
||||
|
||||
; TODO add incredibly dope synths
|
||||
instr organ
|
||||
iduration = p3
|
||||
ifreq = p4
|
||||
igain = p5
|
||||
ioct = octcps(ifreq)
|
||||
|
||||
asig = vco2(igain, ifreq, 12, .5) ; my edit
|
||||
kpwm = oscili(.1, 5)
|
||||
asig = vco2(igain, ifreq, 4, .5 + kpwm)
|
||||
asig += vco2(igain/4, ifreq * 2)
|
||||
|
||||
; filter
|
||||
; idepth = 2
|
||||
; acut = transegr:a(0, .005, 0, idepth, .06, -4.2, 0.001, .01, -4.2, 0) ; filter envelope
|
||||
; asig = zdf_2pole(asig, cpsoct(ioct + acut), 0.5)
|
||||
|
||||
; amp envelope
|
||||
iattack = .001
|
||||
irelease = .05
|
||||
asig *= linsegr:a(0, iattack, 1, iduration, 1, irelease, 0)
|
||||
|
||||
out(asig, asig)
|
||||
|
||||
endin
|
||||
|
||||
instr triangle
|
||||
iduration = p3
|
||||
ifreq = p4
|
||||
igain = p5
|
||||
ioct = octcps(ifreq)
|
||||
|
||||
asig = vco2(igain, ifreq, 12, .5)
|
||||
|
||||
; amp envelope
|
||||
iattack = .001
|
||||
irelease = .05
|
||||
asig *= linsegr:a(0, iattack, 1, iduration, 1, irelease, 0)
|
||||
|
||||
out(asig, asig)
|
||||
endin
|
||||
|
||||
instr pad
|
||||
iduration = p3
|
||||
ifreq = p4
|
||||
igain = p5
|
||||
ioct = octcps(ifreq)
|
||||
|
||||
asig = vco2(igain, ifreq, 0)
|
||||
|
||||
; amp envelope
|
||||
iattack = .5
|
||||
irelease = .1
|
||||
asig *= linsegr:a(0, iattack, 1, iduration, 1, irelease, 0)
|
||||
|
||||
idepth = 2
|
||||
acut = transegr:a(0, .005, 0, idepth, .06, -4.2, 0.001, .01, -4.2, 0)
|
||||
asig = zdf_2pole(asig, 1000, 2)
|
||||
|
||||
out(asig, asig)
|
||||
endin
|
||||
|
||||
|
||||
gisine ftgen 0, 0, 4096, 10, 1
|
||||
|
||||
instr bow
|
||||
kpres = 2
|
||||
krat = 0.16
|
||||
kvibf = 6.12723
|
||||
|
||||
kvib linseg 0, 0.5, 0, 1, 1, p3-0.5, 1
|
||||
kvamp = kvib * 0.01
|
||||
asig wgbow .7, p4, kpres, krat, kvibf, kvamp, gisine
|
||||
asig = asig*p5
|
||||
outs asig, asig
|
||||
endin
|
||||
|
||||
|
||||
instr Meta
|
||||
Smap = strget(p6)
|
||||
Sinstrument = keymap("s", Smap)
|
||||
schedule(Sinstrument, 0, p3, p4, p5)
|
||||
; TODO find a way to pipe Sinstrument through effects
|
||||
endin
|
||||
@@ -0,0 +1,10 @@
|
||||
<CsoundSynthesizer>
|
||||
<CsInstruments>
|
||||
|
||||
sr=48000
|
||||
ksmps=64
|
||||
nchnls=2
|
||||
0dbfs=1
|
||||
|
||||
</CsInstruments>
|
||||
</CsoundSynthesizer>
|
||||
@@ -0,0 +1,33 @@
|
||||
# @strudel.cycles/embed
|
||||
|
||||
This package contains a embeddable web component for the Strudel REPL.
|
||||
|
||||
## Usage
|
||||
|
||||
Either install with `npm i @strudel.cycles/embed` or just use a cdn to import the script:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@strudel.cycles/embed@latest"></script>
|
||||
<strudel-repl>
|
||||
<!--
|
||||
note(`[[e5 [b4 c5] d5 [c5 b4]]
|
||||
[a4 [a4 c5] e5 [d5 c5]]
|
||||
[b4 [~ c5] d5 e5]
|
||||
[c5 a4 a4 ~]
|
||||
[[~ d5] [~ f5] a5 [g5 f5]]
|
||||
[e5 [~ c5] e5 [d5 c5]]
|
||||
[b4 [b4 c5] d5 e5]
|
||||
[c5 a4 a4 ~]],
|
||||
[[e2 e3]*4]
|
||||
[[a2 a3]*4]
|
||||
[[g#2 g#3]*2 [e2 e3]*2]
|
||||
[a2 a3 a2 a3 a2 a3 b1 c2]
|
||||
[[d2 d3]*4]
|
||||
[[c2 c3]*4]
|
||||
[[b1 b2]*2 [e2 e3]*2]
|
||||
[[a1 a2]*4]`).slow(16)
|
||||
-->
|
||||
</strudel-repl>
|
||||
```
|
||||
|
||||
Note that the Code is placed inside HTML comments to prevent the browser from treating it as HTML.
|
||||
@@ -0,0 +1,18 @@
|
||||
class Strudel extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
connectedCallback() {
|
||||
setTimeout(() => {
|
||||
const code = (this.innerHTML + '').replace('<!--', '').replace('-->', '').trim();
|
||||
const iframe = document.createElement('iframe');
|
||||
const src = `https://strudel.tidalcycles.org/#${encodeURIComponent(btoa(code))}`;
|
||||
// const src = `http://localhost:3000/#${encodeURIComponent(btoa(code))}`;
|
||||
iframe.setAttribute('src', src);
|
||||
iframe.setAttribute('width', '600');
|
||||
iframe.setAttribute('height', '400');
|
||||
this.appendChild(iframe);
|
||||
});
|
||||
}
|
||||
}
|
||||
customElements.define('strudel-repl', Strudel);
|
||||
@@ -0,0 +1,22 @@
|
||||
<script src="https://unpkg.com/@strudel.cycles/embed@latest"></script>
|
||||
<!-- <script src="./embed.js"></script> -->
|
||||
<strudel-repl>
|
||||
<!--
|
||||
note(`[[e5 [b4 c5] d5 [c5 b4]]
|
||||
[a4 [a4 c5] e5 [d5 c5]]
|
||||
[b4 [~ c5] d5 e5]
|
||||
[c5 a4 a4 ~]
|
||||
[[~ d5] [~ f5] a5 [g5 f5]]
|
||||
[e5 [~ c5] e5 [d5 c5]]
|
||||
[b4 [b4 c5] d5 e5]
|
||||
[c5 a4 a4 ~]],
|
||||
[[e2 e3]*4]
|
||||
[[a2 a3]*4]
|
||||
[[g#2 g#3]*2 [e2 e3]*2]
|
||||
[a2 a3 a2 a3 a2 a3 b1 c2]
|
||||
[[d2 d3]*4]
|
||||
[[c2 c3]*4]
|
||||
[[b1 b2]*2 [e2 e3]*2]
|
||||
[[a1 a2]*4]`).slow(16)
|
||||
-->
|
||||
</strudel-repl>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@strudel.cycles/embed",
|
||||
"version": "0.2.0",
|
||||
"description": "Embeddable Web Component to load a Strudel REPL into an iframe",
|
||||
"main": "embed.js",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Felix Roos <flix91@gmail.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
shift-parser
|
||||
shift-reducer
|
||||
!shift-traverser
|
||||
@@ -0,0 +1,46 @@
|
||||
# @strudel.cycles/eval
|
||||
|
||||
This package contains the strudel code transformer and evaluator.
|
||||
It allows creating strudel patterns from input code that is optimized for minimal keystrokes and human readability.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm i @strudel.cycles/eval --save
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import { evalScope } from '@strudel.cycles/core';
|
||||
import { evaluate } from '@strudel.cycles/eval';
|
||||
|
||||
evalScope(
|
||||
import('@strudel.cycles/core'),
|
||||
// import other strudel packages here
|
||||
); // add strudel to eval scope
|
||||
|
||||
async function run(code) {
|
||||
const { pattern } = await evaluate(code);
|
||||
const events = pattern.firstCycle();
|
||||
console.log(events.map((e) => e.show()).join('\n'));
|
||||
}
|
||||
|
||||
run('sequence([a3, [b3, c4]])');
|
||||
```
|
||||
|
||||
yields:
|
||||
|
||||
```js
|
||||
(0/1 -> 1/2, 0/1 -> 1/2, a3)
|
||||
(1/2 -> 3/4, 1/2 -> 3/4, b3)
|
||||
(3/4 -> 1/1, 3/4 -> 1/1, c4)
|
||||
```
|
||||
|
||||
[play with @strudel.cycles/eval on codesandbox](https://codesandbox.io/s/strudel-eval-example-ndz1d8?file=/src/index.js)
|
||||
|
||||
## Dev Notes
|
||||
|
||||
shift-traverser is currently monkey patched because its package.json uses estraverse@^4.2.0,
|
||||
which does not support the spread operator (Error: Unknown node type SpreadProperty.).
|
||||
By monkey patched, I mean I copied the source of shift-traverser to a subfolder and installed the dependencies (shift-spec + estraverse@^5.3.0)
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
evaluate.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/eval/evaluate.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { evaluate as _evaluate } from '@strudel.cycles/core';
|
||||
import shapeshifter from './shapeshifter.mjs';
|
||||
|
||||
export const evaluate = async (code) => {
|
||||
return _evaluate(code, shapeshifter);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './evaluate.mjs';
|
||||
Generated
+297
@@ -0,0 +1,297 @@
|
||||
{
|
||||
"name": "@strudel.cycles/eval",
|
||||
"version": "0.5.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@strudel.cycles/eval",
|
||||
"version": "0.1.1",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"estraverse": "^5.3.0",
|
||||
"shift-ast": "^6.1.0",
|
||||
"shift-codegen": "^7.0.3",
|
||||
"shift-parser": "^7.0.3",
|
||||
"shift-spec": "^2018.0.2",
|
||||
"shift-traverser": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/estraverse": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esutils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/multimap": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/multimap/-/multimap-1.1.0.tgz",
|
||||
"integrity": "sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw=="
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/shift-ast": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/shift-ast/-/shift-ast-6.1.0.tgz",
|
||||
"integrity": "sha512-Vj4XUIJIFPIh6VcBGJ1hjH/kM88XGer94Pr7Rvxa+idEylDsrwtLw268HoxGo5xReL6T3DdRl/9/Pr1XihZ/8Q=="
|
||||
},
|
||||
"node_modules/shift-codegen": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/shift-codegen/-/shift-codegen-7.0.3.tgz",
|
||||
"integrity": "sha512-dfCVVdBF0qZ6pkajQ3bjxRdNEltyxEITVe7tBJkQt2eCI3znUkSxq0VSe/tTWq1LKHeAS4HuOiqYEuHMFkSq9w==",
|
||||
"dependencies": {
|
||||
"esutils": "^2.0.2",
|
||||
"object-assign": "^4.1.0",
|
||||
"shift-reducer": "6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/shift-parser": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/shift-parser/-/shift-parser-7.0.3.tgz",
|
||||
"integrity": "sha512-uYX2ORyZfKZrUc4iKKkO9KOhzUSxCrSBk7QK6ZmShId+BOo1gh1IwecVy97ynyOTpmhPWUttjC8BzsnQl65Zew==",
|
||||
"dependencies": {
|
||||
"multimap": "^1.0.2",
|
||||
"shift-ast": "6.0.0",
|
||||
"shift-reducer": "6.0.0",
|
||||
"shift-regexp-acceptor": "2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/shift-parser/node_modules/shift-ast": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shift-ast/-/shift-ast-6.0.0.tgz",
|
||||
"integrity": "sha512-XXxDcEBWVBzqWXfNYJlLyJ1/9kMvOXVRXiqPjkOrTCC5qRsBvEMJMRLLFhU3tn8ue56Y7IZyBE6bexFum5QLUw=="
|
||||
},
|
||||
"node_modules/shift-reducer": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shift-reducer/-/shift-reducer-6.0.0.tgz",
|
||||
"integrity": "sha512-2rJraRP8drIOjvaE/sALa+0tGJmMVUzlmS3wIJerJbaYuCjpFAiF0WjkTOFVtz1144Nm/ECmqeG+7yRhuMVsMg==",
|
||||
"dependencies": {
|
||||
"shift-ast": "6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/shift-reducer/node_modules/shift-ast": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shift-ast/-/shift-ast-6.0.0.tgz",
|
||||
"integrity": "sha512-XXxDcEBWVBzqWXfNYJlLyJ1/9kMvOXVRXiqPjkOrTCC5qRsBvEMJMRLLFhU3tn8ue56Y7IZyBE6bexFum5QLUw=="
|
||||
},
|
||||
"node_modules/shift-regexp-acceptor": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/shift-regexp-acceptor/-/shift-regexp-acceptor-2.0.3.tgz",
|
||||
"integrity": "sha512-sxL7e5JNUFxm+gutFRXktX2D6KVgDAHNuDsk5XHB9Z+N5yXooZG6pdZ1GEbo3Jz6lF7ETYLBC4WAjIFm2RKTmA==",
|
||||
"dependencies": {
|
||||
"unicode-match-property-ecmascript": "1.0.4",
|
||||
"unicode-match-property-value-ecmascript": "1.0.2",
|
||||
"unicode-property-aliases-ecmascript": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/shift-spec": {
|
||||
"version": "2018.0.2",
|
||||
"resolved": "https://registry.npmjs.org/shift-spec/-/shift-spec-2018.0.2.tgz",
|
||||
"integrity": "sha512-5CP/cKDEim4rZ6ViCSipTLY2U7HJr8q/kpDuCBmebFqbx/0DeozWO+9ienHmYjgGLDfHrqj+LBAN67FRK2vE6w=="
|
||||
},
|
||||
"node_modules/shift-traverser": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shift-traverser/-/shift-traverser-1.0.0.tgz",
|
||||
"integrity": "sha512-DMY3512wJbdC+IC+nhLH3/Stgr2BbxbNcg7qyZ6+e5qNnNs8TBQJWdMsRgHlX1JXwF4C0ONKS8VUxsPT0Tf7aw==",
|
||||
"dependencies": {
|
||||
"estraverse": "4.2.0",
|
||||
"shift-spec": "2018.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/shift-traverser/node_modules/estraverse": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
|
||||
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/shift-traverser/node_modules/shift-spec": {
|
||||
"version": "2018.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shift-spec/-/shift-spec-2018.0.0.tgz",
|
||||
"integrity": "sha512-/aiPOkj7dbe+CV2VZhIMTHQToZmgniofpRG7Yr7x2/0sO6CSVC++py1Wzf+s+rWSTDHKcLvziVAxjRRV4i4EoQ=="
|
||||
},
|
||||
"node_modules/unicode-canonical-property-names-ecmascript": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
|
||||
"integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/unicode-match-property-ecmascript": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
|
||||
"integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
|
||||
"dependencies": {
|
||||
"unicode-canonical-property-names-ecmascript": "^1.0.4",
|
||||
"unicode-property-aliases-ecmascript": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/unicode-match-property-value-ecmascript": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz",
|
||||
"integrity": "sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/unicode-property-aliases-ecmascript": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz",
|
||||
"integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"estraverse": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
|
||||
},
|
||||
"esutils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
|
||||
},
|
||||
"multimap": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/multimap/-/multimap-1.1.0.tgz",
|
||||
"integrity": "sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw=="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"shift-ast": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/shift-ast/-/shift-ast-6.1.0.tgz",
|
||||
"integrity": "sha512-Vj4XUIJIFPIh6VcBGJ1hjH/kM88XGer94Pr7Rvxa+idEylDsrwtLw268HoxGo5xReL6T3DdRl/9/Pr1XihZ/8Q=="
|
||||
},
|
||||
"shift-codegen": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/shift-codegen/-/shift-codegen-7.0.3.tgz",
|
||||
"integrity": "sha512-dfCVVdBF0qZ6pkajQ3bjxRdNEltyxEITVe7tBJkQt2eCI3znUkSxq0VSe/tTWq1LKHeAS4HuOiqYEuHMFkSq9w==",
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"object-assign": "^4.1.0",
|
||||
"shift-reducer": "6.0.0"
|
||||
}
|
||||
},
|
||||
"shift-parser": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/shift-parser/-/shift-parser-7.0.3.tgz",
|
||||
"integrity": "sha512-uYX2ORyZfKZrUc4iKKkO9KOhzUSxCrSBk7QK6ZmShId+BOo1gh1IwecVy97ynyOTpmhPWUttjC8BzsnQl65Zew==",
|
||||
"requires": {
|
||||
"multimap": "^1.0.2",
|
||||
"shift-ast": "6.0.0",
|
||||
"shift-reducer": "6.0.0",
|
||||
"shift-regexp-acceptor": "2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"shift-ast": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shift-ast/-/shift-ast-6.0.0.tgz",
|
||||
"integrity": "sha512-XXxDcEBWVBzqWXfNYJlLyJ1/9kMvOXVRXiqPjkOrTCC5qRsBvEMJMRLLFhU3tn8ue56Y7IZyBE6bexFum5QLUw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"shift-reducer": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shift-reducer/-/shift-reducer-6.0.0.tgz",
|
||||
"integrity": "sha512-2rJraRP8drIOjvaE/sALa+0tGJmMVUzlmS3wIJerJbaYuCjpFAiF0WjkTOFVtz1144Nm/ECmqeG+7yRhuMVsMg==",
|
||||
"requires": {
|
||||
"shift-ast": "6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"shift-ast": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shift-ast/-/shift-ast-6.0.0.tgz",
|
||||
"integrity": "sha512-XXxDcEBWVBzqWXfNYJlLyJ1/9kMvOXVRXiqPjkOrTCC5qRsBvEMJMRLLFhU3tn8ue56Y7IZyBE6bexFum5QLUw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"shift-regexp-acceptor": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/shift-regexp-acceptor/-/shift-regexp-acceptor-2.0.3.tgz",
|
||||
"integrity": "sha512-sxL7e5JNUFxm+gutFRXktX2D6KVgDAHNuDsk5XHB9Z+N5yXooZG6pdZ1GEbo3Jz6lF7ETYLBC4WAjIFm2RKTmA==",
|
||||
"requires": {
|
||||
"unicode-match-property-ecmascript": "1.0.4",
|
||||
"unicode-match-property-value-ecmascript": "1.0.2",
|
||||
"unicode-property-aliases-ecmascript": "1.0.4"
|
||||
}
|
||||
},
|
||||
"shift-spec": {
|
||||
"version": "2018.0.2",
|
||||
"resolved": "https://registry.npmjs.org/shift-spec/-/shift-spec-2018.0.2.tgz",
|
||||
"integrity": "sha512-5CP/cKDEim4rZ6ViCSipTLY2U7HJr8q/kpDuCBmebFqbx/0DeozWO+9ienHmYjgGLDfHrqj+LBAN67FRK2vE6w=="
|
||||
},
|
||||
"shift-traverser": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shift-traverser/-/shift-traverser-1.0.0.tgz",
|
||||
"integrity": "sha512-DMY3512wJbdC+IC+nhLH3/Stgr2BbxbNcg7qyZ6+e5qNnNs8TBQJWdMsRgHlX1JXwF4C0ONKS8VUxsPT0Tf7aw==",
|
||||
"requires": {
|
||||
"estraverse": "4.2.0",
|
||||
"shift-spec": "2018.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"estraverse": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
|
||||
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
|
||||
},
|
||||
"shift-spec": {
|
||||
"version": "2018.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shift-spec/-/shift-spec-2018.0.0.tgz",
|
||||
"integrity": "sha512-/aiPOkj7dbe+CV2VZhIMTHQToZmgniofpRG7Yr7x2/0sO6CSVC++py1Wzf+s+rWSTDHKcLvziVAxjRRV4i4EoQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"unicode-canonical-property-names-ecmascript": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
|
||||
"integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ=="
|
||||
},
|
||||
"unicode-match-property-ecmascript": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
|
||||
"integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
|
||||
"requires": {
|
||||
"unicode-canonical-property-names-ecmascript": "^1.0.4",
|
||||
"unicode-property-aliases-ecmascript": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"unicode-match-property-value-ecmascript": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz",
|
||||
"integrity": "sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ=="
|
||||
},
|
||||
"unicode-property-aliases-ecmascript": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz",
|
||||
"integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@strudel.cycles/eval",
|
||||
"version": "0.5.0",
|
||||
"description": "Code evaluator for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Felix Roos <flix91@gmail.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "^0.5.0",
|
||||
"estraverse": "^5.3.0",
|
||||
"shift-ast": "^7.0.0",
|
||||
"shift-codegen": "^8.1.0",
|
||||
"shift-parser": "^8.0.0",
|
||||
"shift-spec": "^2019.0.0",
|
||||
"shift-traverser": "^1.0.0"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user