Compare commits
802 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d6114beb6 | |||
| 1aaaaba619 | |||
| b7dd9b7d35 | |||
| d25851ac17 | |||
| d02450c0d9 | |||
| 400b2109fb | |||
| 2a09f9ef0b | |||
| 0052d349d9 | |||
| 7c8a8b8b70 | |||
| 85835087ba | |||
| 5aac042c80 | |||
| 9dbd073073 | |||
| 035f6c8098 | |||
| d2a06654c3 | |||
| c028686298 | |||
| 858a02634e | |||
| 2682fd38f8 | |||
| 3ecb701504 | |||
| ac359a0e9e | |||
| df636cf4f6 | |||
| 791a363694 | |||
| 06eb8b5be8 | |||
| 4b4290e087 | |||
| c89f91215c | |||
| 65d07dbff2 | |||
| 2e4d28b910 | |||
| 51c63fa996 | |||
| f0a582dc92 | |||
| bee36a7a4f | |||
| 0edd54dee3 | |||
| 4a16cd891f | |||
| 23dd9d4c01 | |||
| df277d6b7b | |||
| eed2c304e4 | |||
| bd5953317e | |||
| b88f58cc21 | |||
| 9cbf3079f3 | |||
| 682ac25111 | |||
| 07da9a9dd2 | |||
| 395ae4d05a | |||
| 2ef35a8583 | |||
| 5807dbfdac | |||
| c4f26c3cad | |||
| 26d87b691c | |||
| 52e2b90eef | |||
| 62eb9ce598 | |||
| a9efda3bc3 | |||
| 8fe4dca4c2 | |||
| a05d3a47c6 | |||
| e9265eff35 | |||
| 8fa7bf795a | |||
| f045fb44de | |||
| 0d06840a1b | |||
| 624e540cb4 | |||
| 353333653f | |||
| 83fec57400 | |||
| 7ad0ec1600 | |||
| ec611aac45 | |||
| d7752961b8 | |||
| 8c5837e78a | |||
| b3f8df1783 | |||
| 82380a0f4e | |||
| c030f456c3 | |||
| 694e44dc8a | |||
| 351da196b9 | |||
| e27ee03517 | |||
| 19db419c53 | |||
| ff60a89668 | |||
| 7cc17fefcb | |||
| 76ee9485f4 | |||
| 1d65f09f3c | |||
| cc97f6395d | |||
| 33eef619e4 | |||
| e9e3b97621 | |||
| 1f765e54ca | |||
| 2b81cc5d0c | |||
| ca054dbd74 | |||
| a8433f61a4 | |||
| 5850106eef | |||
| 95a9ab6182 | |||
| ba19c4ddce | |||
| b49b1c98ae | |||
| 7c00aa1a36 | |||
| 07d6bb3c44 | |||
| 3f2e2554be | |||
| 9ebb7edde9 | |||
| a89091cbc7 | |||
| 0d1a99deb2 | |||
| b71f8a33d7 | |||
| 0ecc6c196f | |||
| 77686547eb | |||
| 419cf6c12b | |||
| 326571b797 | |||
| c25c2693f5 | |||
| 8d8a1f552d | |||
| 638f5c7323 | |||
| 9ec5d33840 | |||
| 8d3ba2a8cd | |||
| 0c2838cc90 | |||
| 1e5edfca26 | |||
| b141e1ee68 | |||
| 2fd4836228 | |||
| 9ceab0f294 | |||
| d4c12dcef4 | |||
| 16bb920011 | |||
| 158fa85c46 | |||
| 34bd0e11e4 | |||
| 9e649a18c4 | |||
| 2bb45afe5f | |||
| 41a9098c80 | |||
| 36ef03a406 | |||
| fd6c713119 | |||
| 4a57d3ada1 | |||
| f16cb8f7d2 | |||
| af915be142 | |||
| 2633f4fcef | |||
| 6c2330984e | |||
| 1e1d5ef442 | |||
| 248757ac3f | |||
| cdbfb2c08c | |||
| d84c917f19 | |||
| 1c7c82358d | |||
| c085694a8b | |||
| a57c2b9fe5 | |||
| 9fc87fe20f | |||
| 4761e25fe1 | |||
| 3ce50d80de | |||
| 8754b023d5 | |||
| 22c9829fea | |||
| 0f7b3e9f84 | |||
| 1a456828df | |||
| 5958c69d64 | |||
| b14aa901ba | |||
| 535dfb5099 | |||
| 19f1aad88c | |||
| 9009eccc8e | |||
| 7f3cdedef9 | |||
| 05095805f1 | |||
| 5430147572 | |||
| 9b9f031fe1 | |||
| 2ad63247c2 | |||
| 87f37e3d87 | |||
| cb8edd9b8f | |||
| 072ae7280e | |||
| e99c299c6f | |||
| 46cdbd514f | |||
| a3e0189f0b | |||
| 8f4344f413 | |||
| 7e28de1d24 | |||
| 624e685212 | |||
| a23765a1cb | |||
| 048502ecd1 | |||
| 1b072448c4 | |||
| 86997488fb | |||
| db6efec417 | |||
| 2cc60c6ee2 | |||
| 80c98b6a35 | |||
| f51c01dc88 | |||
| 7b8a35a311 | |||
| d9db5263e8 | |||
| 912a661c04 | |||
| d3edc62fc7 | |||
| 6b5de5fec8 | |||
| fed4c51aa4 | |||
| 1721209f85 | |||
| 289820188e | |||
| 4fa0a9064b | |||
| f751d97c29 | |||
| d7028252c2 | |||
| a2ee70a964 | |||
| 57b166d13a | |||
| 5c76f2b9a2 | |||
| e59f06ff92 | |||
| d3b96dd048 | |||
| 6544e8135c | |||
| 54ed415d75 | |||
| f34937d4ae | |||
| a4fc40875b | |||
| 56c1441494 | |||
| 08ce8213fc | |||
| 0a3692e459 | |||
| 341546508f | |||
| 444d583a49 | |||
| 130ad4451b | |||
| d24cf8e7ec | |||
| 2d750a14b9 | |||
| b25b9d815b | |||
| daf965f119 | |||
| 296cd83a55 | |||
| b297888eb4 | |||
| e2569ed6dd | |||
| 4718bfac25 | |||
| 2f0798de32 | |||
| 11b0a00593 | |||
| 1d4eb59e94 | |||
| d6c8838fe2 | |||
| 0b888ac54d | |||
| 75099abbc1 | |||
| 3c4f835d8b | |||
| ce842f7561 | |||
| 508af7eb72 | |||
| a4386a617a | |||
| 767ba713a6 | |||
| 020e85906d | |||
| d1ffdd57a5 | |||
| 9c9323e040 | |||
| 376cf09565 | |||
| 047129223e | |||
| 4b64168faa | |||
| 2bc6d08410 | |||
| 484bb6b11f | |||
| a0884e2a03 | |||
| 1e352fdf80 | |||
| 83dfbb84c2 | |||
| 9043064543 | |||
| a3649148c1 | |||
| 28966739f6 | |||
| 13cb32903a | |||
| dcd75e0384 | |||
| dfdd9e02ca | |||
| 275796c241 | |||
| 6ca99e33ab | |||
| 1909caf769 | |||
| 2d07eeb518 | |||
| 1c0da7cd49 | |||
| fd316c81c0 | |||
| edbd437d7b | |||
| 9be1f9c18b | |||
| 96f06d8026 | |||
| 935d8e8aea | |||
| 21b99b3810 | |||
| c051a1249d | |||
| 564697e175 | |||
| d4bf358eae | |||
| f84d5ba3a0 | |||
| b550ff038c | |||
| ac9c629c0b | |||
| 755e24315e | |||
| c6ecd31ea1 | |||
| e600b91a85 | |||
| abff279707 | |||
| fd18508266 | |||
| d7bc309eeb | |||
| 33c40e5ef8 | |||
| 062d926900 | |||
| 0ad0046a76 | |||
| b36cee93f7 | |||
| 2731e70fb7 | |||
| 276cf858fc | |||
| b2acff40c4 | |||
| e3333e716f | |||
| bb7b8c2fab | |||
| 389c7be264 | |||
| c2481e460b | |||
| c93e4a951a | |||
| aa094bf930 | |||
| 8a741952df | |||
| 0dcc55ee16 | |||
| 6c3a3b9e29 | |||
| e4eff8c837 | |||
| 527b895e3c | |||
| 4eb0a7b7c0 | |||
| f11462bf41 | |||
| dea1c31701 | |||
| 68ab43b3ab | |||
| cf72e3bba5 | |||
| 62743edf45 | |||
| eec3752b5a | |||
| 006fd6c733 | |||
| 7078e20200 | |||
| 52c01abbe9 | |||
| 6e26f39751 | |||
| 08aab9d4d8 | |||
| 30cba7b935 | |||
| 9e27c0fe2c | |||
| 05c09eff19 | |||
| 258eb88684 | |||
| 9c73ef770f | |||
| de9a52366f | |||
| 57265b9e33 | |||
| b7ef2d97e2 | |||
| 4fb364195c | |||
| 578bede46b | |||
| 38a51324b2 | |||
| 0e7c9a4001 | |||
| 32fee6313b | |||
| c6a74c040e | |||
| a97384cec1 | |||
| f052adb93a | |||
| c2560e0cf8 | |||
| cd6d1fb2d2 | |||
| c354ee32e3 | |||
| 5c5df74860 | |||
| 887fb6a84c | |||
| 80ea9e8de4 | |||
| 3879b01ddd | |||
| 4490681fa3 | |||
| b98e24fabf | |||
| 2deafe214a | |||
| d0258756e5 | |||
| 4fecad16e4 | |||
| a82eeb44c0 | |||
| 7ec5ab0c20 | |||
| 872e10fc95 | |||
| e3b7d0f84d | |||
| e0c424eb92 | |||
| 536427310d | |||
| a7eb0bb04a | |||
| af6a5b85f5 | |||
| b96e8093a0 | |||
| 989dc0aabe | |||
| 735f892a08 | |||
| e959c275fd | |||
| 7dbcc90450 | |||
| 7f86eac8f1 | |||
| 40de717cc9 | |||
| df211c994d | |||
| ee314e9b03 | |||
| 9049a8b964 | |||
| 88c029202c | |||
| 9142fb753a | |||
| 4688c0bf10 | |||
| 7e55258bbf | |||
| 828e475921 | |||
| 9d5f919023 | |||
| b8932390f7 | |||
| 445881992e | |||
| 30d96dcb65 | |||
| 3a69fd50bb | |||
| 0f72729f0d | |||
| 0a8874180c | |||
| a6501474bb | |||
| 4cb93ddcde | |||
| 7f1918ae8c | |||
| 78adaaedef | |||
| 4a65113bff | |||
| 843d9d52c3 | |||
| 7a74189771 | |||
| 6de04f7781 | |||
| ba27f2ba28 | |||
| f09b89ed63 | |||
| bac8594c5f | |||
| 3582ae9163 | |||
| 7a95bafa54 | |||
| 1383af6208 | |||
| bea9a3c2dc | |||
| 78ccec5d7b | |||
| 534e3f7f81 | |||
| 6a427d6f89 | |||
| b4297580a7 | |||
| 26b0e2ae43 | |||
| e75ac7b3c0 | |||
| 74442b0d76 | |||
| 3431803980 | |||
| c5fb247de6 | |||
| 18640d59da | |||
| 48753b2539 | |||
| 606dcc8cc3 | |||
| 9e4c548c69 | |||
| 75a974643b | |||
| e2711ba911 | |||
| 7605198774 | |||
| fc9525e7d8 | |||
| e597b6473d | |||
| 16c8dcf788 | |||
| 37842d4fbd | |||
| 133a1d2e8f | |||
| 15c15a9c36 | |||
| d233d50de3 | |||
| a9d8e7ca82 | |||
| 4001cc9531 | |||
| 4319df0cb6 | |||
| 5be7898522 | |||
| 009760c31a | |||
| 77349526e4 | |||
| 3ba195c2d9 | |||
| 60f5032b12 | |||
| fdc201a799 | |||
| ec8616a515 | |||
| 349d11e76d | |||
| 62e3774ff7 | |||
| abaeb52b7c | |||
| 60e600c381 | |||
| 97f84ee87f | |||
| c5738aeed9 | |||
| 192adccb10 | |||
| defcacb59b | |||
| e4ea786956 | |||
| c9d98bf4d7 | |||
| 8dd08c360f | |||
| d6c4bb0e4e | |||
| c54916cdfd | |||
| e29cf6fcc1 | |||
| 5b3cd166b0 | |||
| a7246c6518 | |||
| b48fd78cf0 | |||
| 66c895ee3c | |||
| 37c450dd83 | |||
| 4c744da3e2 | |||
| 388e7b3c2f | |||
| 6ec5377381 | |||
| 24ec47e4ff | |||
| 4a122523d9 | |||
| 1241911ec0 | |||
| 0f54bb1938 | |||
| a3b55aa5cc | |||
| 4fa02a483b | |||
| a786c642f1 | |||
| 1bade7bee8 | |||
| d399df8798 | |||
| 749b11a5ea | |||
| 328af5f560 | |||
| 876f97e9a2 | |||
| 26d7a32f11 | |||
| 3057773009 | |||
| 6ca89cb907 | |||
| 88b5b9b21c | |||
| 6bcf8b91cc | |||
| 7cf7ebc555 | |||
| ee0aaca2b0 | |||
| b1b084452c | |||
| 213b1a2dae | |||
| b991cfc493 | |||
| 90cdea3656 | |||
| 1463f27a0f | |||
| afb8ab44cf | |||
| 282f36c47c | |||
| c3f27d1ad9 | |||
| ad230a8dc7 | |||
| c29c4c0a8b | |||
| 284f5615cc | |||
| c625096f59 | |||
| c3cd5d08c9 | |||
| c87c3c6672 | |||
| 8fc7688585 | |||
| 94bcfb11de | |||
| ebeaac3c5f | |||
| 942ffc038d | |||
| 9de344e48d | |||
| 5efea045d2 | |||
| 6bd3274a4b | |||
| a179313db2 | |||
| 289376840e | |||
| 0e3d84e5ca | |||
| cb643f8fd9 | |||
| 1faa81099c | |||
| 2dc079b950 | |||
| f6cf740507 | |||
| 338c866d83 | |||
| 038e6c312b | |||
| 6f6def34f7 | |||
| 88651149d3 | |||
| e301090704 | |||
| d0f8a612ad | |||
| a7728e3d81 | |||
| 4dadc18a3a | |||
| 43efe6921d | |||
| 2e31c8695a | |||
| 988bd8ccdc | |||
| 965794712e | |||
| 68ea086e2a | |||
| 7370f41fa0 | |||
| 5cbe30095b | |||
| f20c5a1bcb | |||
| 02979926fd | |||
| b08337af45 | |||
| 980c1e4812 | |||
| f43dd8db1b | |||
| badda165fe | |||
| f7d17875c0 | |||
| 0c124bcca3 | |||
| 3eb63f1730 | |||
| abb0b3b4c0 | |||
| 42662748d3 | |||
| b5b866a894 | |||
| 01cccc6462 | |||
| 9287b8ea42 | |||
| 5fabc5c7f9 | |||
| 61dc6f980d | |||
| 84328cfb90 | |||
| 37a35022f5 | |||
| 3afe540111 | |||
| 35687b1ee0 | |||
| 195e6b61f9 | |||
| dabc0331cb | |||
| 824abb0406 | |||
| f6789aaace | |||
| 10bce85561 | |||
| cd0f1b6c5c | |||
| 834bb7d777 | |||
| 5c1d7baf72 | |||
| 8bdbc9ec4c | |||
| 0c6a0720af | |||
| c6095fd92a | |||
| 7d0fb0de83 | |||
| 75281afe05 | |||
| 738e714f45 | |||
| 898bfaeecf | |||
| 7f44d9cdd2 | |||
| 8d9be21c8a | |||
| 82cb4c8c11 | |||
| 2778e4d697 | |||
| 30fe2dd502 | |||
| 1170ff58ee | |||
| 2483bf812f | |||
| 7a999de602 | |||
| 1929c27225 | |||
| 0cf5c9c7d6 | |||
| c20b691683 | |||
| 5c2101de1a | |||
| 2ec2e81511 | |||
| ac3d8b832c | |||
| ad174957e8 | |||
| 9b3fa83516 | |||
| 52b8d9d361 | |||
| d231ded7a2 | |||
| 5f57951e3d | |||
| b1a4788442 | |||
| 6377378a3d | |||
| 45b91cdea5 | |||
| d5647c6760 | |||
| f419559bde | |||
| 4cfaa39419 | |||
| d6b1762adf | |||
| 45c3293385 | |||
| 92d574605e | |||
| cec7b90631 | |||
| aa3cb35f09 | |||
| 7373138e3d | |||
| 2d8176f213 | |||
| 8589d72aee | |||
| 626b50087a | |||
| 1e62554dfb | |||
| bcf800482b | |||
| 08f94d2122 | |||
| 559cfd11db | |||
| ce820c2314 | |||
| 8583ed0e5c | |||
| f2c16a06a1 | |||
| 584d469a73 | |||
| 4298e582fb | |||
| a3fdfbbc16 | |||
| b8929a0d18 | |||
| 0fa5832b63 | |||
| bbb29ebd7c | |||
| 0fd9ac6da3 | |||
| e53098b179 | |||
| 78dcab2778 | |||
| e05f74cef1 | |||
| 3d8724c90d | |||
| ffeda19ce5 | |||
| 70fe8f14e3 | |||
| a639d9f49f | |||
| e5251422fe | |||
| d8f80c058d | |||
| 4cf3655c08 | |||
| 58fc786462 | |||
| 81b142704c | |||
| b0cbeda796 | |||
| 0b3a8a5f65 | |||
| d7d8d2a3e6 | |||
| d5c0309885 | |||
| 36fa9d81fb | |||
| 35f5a5d11a | |||
| d1fe50e271 | |||
| 2d08b31f9a | |||
| 27643f4a48 | |||
| 110e179e9e | |||
| fe6d99d4f1 | |||
| 06fbcbe24b | |||
| f40b57ee48 | |||
| 9438c69bed | |||
| 70d6f3000a | |||
| d47fd0cf18 | |||
| 40117fbe54 | |||
| 7466d8a1de | |||
| ce09443e48 | |||
| 1718dbe94e | |||
| 69894db206 | |||
| 6e4c873248 | |||
| 64693ffd26 | |||
| 66f8ca72c1 | |||
| c82f7bd8fe | |||
| 34176ab5f8 | |||
| 7f12ce9b45 | |||
| 9e1b9f7f5c | |||
| 23e949de1c | |||
| 00bd60a855 | |||
| dacd9afac0 | |||
| 3fc5bb31d0 | |||
| 78770888a5 | |||
| f5b092acf2 | |||
| ba9562f000 | |||
| aded178ab7 | |||
| 0b5d905120 | |||
| 08abec8fd5 | |||
| 63c23736ad | |||
| f7bd373ce6 | |||
| 8e717d2ea1 | |||
| 5f271ed127 | |||
| d686b65dbe | |||
| c7e882e001 | |||
| f07859996e | |||
| fbc73bc78a | |||
| bd8ad1ed1b | |||
| dc2ff83fa5 | |||
| 13545d147b | |||
| 1b85aa713b | |||
| 90a58858ca | |||
| 8149b85a55 | |||
| e79b6c514a | |||
| aaa894d74b | |||
| b55721f16f | |||
| b39d948d71 | |||
| 0fee279e3d | |||
| c7d8a9cb82 | |||
| b50075f102 | |||
| fb3ca9f960 | |||
| 058b971870 | |||
| 610ea414c1 | |||
| cc6997baec | |||
| d126c28b85 | |||
| c1649b9dc0 | |||
| b0a8d12112 | |||
| b5160d38cb | |||
| 360e9b5936 | |||
| 85a6cd828e | |||
| e698f19a64 | |||
| 83c1b580dd | |||
| 9273e1f8ed | |||
| 5287825279 | |||
| 5567959afd | |||
| eaab36b051 | |||
| 9a4f3af580 | |||
| b83664dfce | |||
| 349f7ae965 | |||
| 6f41716ab5 | |||
| abeb62dea8 | |||
| 7cfed80443 | |||
| 36aa7d9b25 | |||
| 2a449ad3c0 | |||
| a71c5957cc | |||
| eef6c7d55e | |||
| 3872322da1 | |||
| f1e850a62a | |||
| 8d9278419d | |||
| 0862827a51 | |||
| f37a220a05 | |||
| aa51c2b6dd | |||
| 92da70a7ae | |||
| a55862fc25 | |||
| 09b14a5702 | |||
| 260b831815 | |||
| 665cf6ac65 | |||
| 5e90021698 | |||
| 16aeb7e12c | |||
| b4cbec173b | |||
| 8c9e06c329 | |||
| e4a35fdd72 | |||
| 370d82e66c | |||
| 876e851517 | |||
| 190fe37a9f | |||
| 623661f8d2 | |||
| f31bc3f8c5 | |||
| 12fc48544c | |||
| 3b4e44a9fd | |||
| 7850fb727e | |||
| 9b3122837e | |||
| d4c59cc73b | |||
| fe36bf54b2 | |||
| 4d8308a763 | |||
| 11f26c1e2c | |||
| e10104d401 | |||
| 5e09c3c318 | |||
| 576b88221d | |||
| 7dc2a0091b | |||
| 89a4a85589 | |||
| a3baf07a5c | |||
| 8e6909478c | |||
| 2d2d0cabb3 | |||
| 6f04a1b8fa | |||
| a9ad3a56ad | |||
| 3069b14e9c | |||
| ae868c6372 | |||
| c287ff554c | |||
| 3ccbd35fad | |||
| 51cc69208f | |||
| 1fec2f7a46 | |||
| ffffdd2d7d | |||
| f8c3e3d5aa | |||
| eccce6fa84 | |||
| 44be6cee2c | |||
| 799afd511a | |||
| 37c4541468 | |||
| 119a66acd0 | |||
| 105193a452 | |||
| 02d793e9e0 | |||
| 90647a8ba8 | |||
| 06950a6bd8 | |||
| 0c2147d9c5 | |||
| 51e8174689 | |||
| 185318a70c | |||
| a88759ba94 | |||
| 96ac054392 | |||
| bd7f4dd73f | |||
| 2e22152089 | |||
| 98a89aea11 | |||
| 372e33c066 | |||
| 52641db590 | |||
| f9ad5f0d01 | |||
| 3db1c7a521 | |||
| b6d1f96930 | |||
| 61a9b01062 | |||
| f0faf5b42e | |||
| 11733d5e05 | |||
| c4a38d9008 | |||
| 0edd7e14b7 | |||
| 8b7bb7b6ae | |||
| 39d5955e58 | |||
| 536327f403 | |||
| 8679dc63be | |||
| 1e9979ae18 | |||
| d2dffe3186 | |||
| 9971867e2f | |||
| 8c93e578a0 | |||
| 1ba5d2e1ca | |||
| ed792fc0d4 | |||
| 4e575c44b3 | |||
| fc06181217 | |||
| 0d6fcf78d8 | |||
| 82225f0b81 | |||
| 2177e13b49 | |||
| b63f4eb503 | |||
| cd7bc09f9c | |||
| ccf775e976 | |||
| 811346e83d | |||
| d3240a75d0 | |||
| d93e396ce5 | |||
| d74cc234c4 | |||
| 0fda83d8de | |||
| a1ec8545fb | |||
| 087f574355 | |||
| 1de784b817 | |||
| db79c05d9a | |||
| 1fdb1f0da6 | |||
| e2bfaa6233 | |||
| 6089849d35 | |||
| 6e02bf59e9 | |||
| 12228c56d9 | |||
| 3b631cb6af | |||
| b27a58df7e | |||
| 308f139193 | |||
| 692fc4e11d | |||
| b52f65c4d7 | |||
| 299275e368 | |||
| bb71c519db | |||
| 9ec7109dc8 | |||
| 5b67fccb1b | |||
| 9807c4b7a1 | |||
| 9039600f6d | |||
| b5b0156e08 | |||
| 352e647d37 | |||
| e6b66f2525 | |||
| f5075906e2 | |||
| a6f57bced8 | |||
| d35bf9591c | |||
| d96f3f9dd4 | |||
| 1f4c2f8c5a | |||
| 95719654f3 | |||
| 4b921c47f5 | |||
| f069f53fae | |||
| 1494cc38fc | |||
| a035412956 | |||
| 08662ea831 | |||
| d97103268b | |||
| a959e2e34d | |||
| 602bfbdd79 | |||
| 0785bdd37a | |||
| 8b62b6e1b1 | |||
| d1c8ebd15b | |||
| 13088e4001 | |||
| 5c6fa1c82b | |||
| fd81867e7b | |||
| 78293e64dd | |||
| 930fc10aec | |||
| 0b2a4fda44 | |||
| efdce6d4cd | |||
| c0b0ea4493 | |||
| 0fcec0a0ca | |||
| c0701d8a35 | |||
| ae51092044 | |||
| b7fcccb8db | |||
| b21a52f5f3 | |||
| 2bdcfe13e6 | |||
| 12a97ec429 | |||
| 99298ce9f2 | |||
| a0ed25d373 | |||
| dc6c29c668 | |||
| 3a85f28b5c | |||
| 95c44b1ce2 | |||
| b88e0ecb78 | |||
| 74ad521fc9 |
@@ -17,4 +17,7 @@ vite.config.js
|
||||
**/*.ts
|
||||
**/*.json
|
||||
**/dev-dist
|
||||
**/dist
|
||||
**/dist
|
||||
/src-tauri/target/**/*
|
||||
reverbGen.mjs
|
||||
hydra.mjs
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Tauri Builder
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, ubuntu-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
rust_target: x86_64-unknown-linux-gnu
|
||||
- os: macos-latest
|
||||
rust_target: x86_64-apple-darwin
|
||||
- os: macos-latest
|
||||
rust_target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
rust_target: x86_64-pc-windows-msvc
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8.6.2
|
||||
|
||||
- name: Node.js setup
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: latest
|
||||
# node-version-file: '.nvmrc'
|
||||
|
||||
- name: Install Rust (Stable)
|
||||
run:
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf libasound2-dev
|
||||
|
||||
- name: Install app dependencies from lockfile and build web
|
||||
run: pnpm install
|
||||
|
||||
- name: Build the app
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
# tauri-action replaces \_\_VERSION\_\_ with the app version
|
||||
tagName: ${{ github.ref_name }}
|
||||
releaseName: "Strudel v__VERSION__"
|
||||
releaseBody: |
|
||||
See the assets to download this version and install.
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
@@ -39,4 +39,8 @@ server/samples/old
|
||||
repl/stats.html
|
||||
coverage
|
||||
public/icons/apple-splash-*
|
||||
dev-dist
|
||||
dev-dist
|
||||
Dirt-Samples
|
||||
tidal-drum-machines
|
||||
webaudiofontdata
|
||||
src-tauri/target
|
||||
@@ -13,7 +13,7 @@ To get in touch with the contributors, either
|
||||
## 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.
|
||||
[docs](https://strudel.cc/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.
|
||||
@@ -31,7 +31,7 @@ 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),
|
||||
If you find some weak spots in the [docs](https://strudel.cc/workshop/getting-started/),
|
||||
you can edit each file directly on github via the "Edit this page" link located in the right sidebar.
|
||||
|
||||
## Propose a Feature
|
||||
@@ -83,7 +83,7 @@ Please report any problems you've had with the setup instructions!
|
||||
|
||||
To make sure the code changes only where it should, we are using prettier to unify the code style.
|
||||
|
||||
- You can format all files at once by running `pnpm prettier` from the project root
|
||||
- You can format all files at once by running `pnpm codeformat` from the project root
|
||||
- Run `pnpm format-check` from the project root to check if all files are well formatted
|
||||
|
||||
If you use VSCode, you can
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
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/>
|
||||
- Try it here: <https://strudel.cc>
|
||||
- Docs: <https://strudel.cc/learn>
|
||||
- Technical Blog Post: <https://loophole-letters.vercel.app/strudel>
|
||||
- 1 Year of Strudel Blog Post: <https://loophole-letters.vercel.app/strudel1year>
|
||||
|
||||
@@ -24,8 +24,8 @@ There are multiple npm packages you can use to use strudel, or only parts of it,
|
||||
|
||||
- [`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
|
||||
- [`transpiler`](./packages/transpiler): user code transpiler
|
||||
- [`webaudio`](./packages/webaudio): webaudio output
|
||||
- [`osc`](./packages/osc): bindings to communicate via OSC
|
||||
- [`midi`](./packages/midi): webmidi bindings
|
||||
- [`serial`](./packages/serial): webserial bindings
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# Usage
|
||||
|
||||
```sh
|
||||
cd iclc2023/slides
|
||||
pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
- ctrl+click or ctrl+(left|right) to switch slides
|
||||
|
||||
## Samples
|
||||
|
||||
To make sure all the samples are available, clone:
|
||||
|
||||
```sh
|
||||
cd public/samples
|
||||
git clone https://github.com/ritchse/tidal-drum-machines.git
|
||||
git clone https://github.com/tidalcycles/Dirt-Samples.git
|
||||
git clone https://github.com/felixroos/webaudiofontdata.git
|
||||
```
|
||||
|
||||
or use symlinks..
|
||||
@@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Slides</title>
|
||||
</head>
|
||||
<body style="background-color: black">
|
||||
<div id="root">
|
||||
<span style="color: white">loading... </span>
|
||||
</div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"name": "slides",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdx-js/rollup": "^2.3.0",
|
||||
"@nanostores/persistent": "^0.7.0",
|
||||
"@nanostores/react": "^0.4.1",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/react": "workspace:*",
|
||||
"@strudel.cycles/soundfonts": "workspace:*",
|
||||
"@strudel.cycles/tonal": "workspace:*",
|
||||
"@strudel.cycles/csound": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"@uiw/codemirror-themes-all": "^4.19.8",
|
||||
"escodegen": "^2.0.0",
|
||||
"nanostores": "^0.7.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"vite-plugin-top-level-await": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"postcss": "^8.4.21",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"vite": "^4.2.0"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
Before Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 103 KiB |
@@ -1,46 +0,0 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { atomone } from '@uiw/codemirror-themes-all';
|
||||
import { CodeMirror, flash, useKeydown } from '@strudel.cycles/react';
|
||||
import { initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
import { TimeSpan } from '@strudel.cycles/core';
|
||||
|
||||
const initAudio = initAudioOnFirstClick();
|
||||
|
||||
function EventEditor({ code: initialCode }) {
|
||||
const [code, setCode] = useState(initialCode);
|
||||
const [view, setView] = useState();
|
||||
useKeydown(
|
||||
useCallback(
|
||||
async (e) => {
|
||||
if (view?.hasFocus) {
|
||||
if (e.ctrlKey || e.altKey) {
|
||||
if (e.code === 'Enter') {
|
||||
/* if (getAudioContext().state !== 'running') {
|
||||
alert('please click play to initialize the audio. you can use shortcuts after that!');
|
||||
return;
|
||||
} */
|
||||
e.preventDefault();
|
||||
flash(view);
|
||||
let json = JSON.parse(code);
|
||||
if (!Array.isArray(json)) {
|
||||
json = [json];
|
||||
}
|
||||
await initAudio;
|
||||
json.forEach((value) => {
|
||||
const hap = new Hap(new TimeSpan(0, 1), new TimeSpan(0, 1), value);
|
||||
webaudioOutput(hap, 0.01, 1, 1);
|
||||
});
|
||||
} else if (e.key === '.') {
|
||||
stop();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[code, view],
|
||||
),
|
||||
);
|
||||
return <CodeMirror value={code} onChange={setCode} theme={atomone} onViewChanged={(v) => setView(v)} fontSize={32} />;
|
||||
}
|
||||
|
||||
export default EventEditor;
|
||||
@@ -1,39 +0,0 @@
|
||||
import EventEditor from './EventEditor.jsx';
|
||||
import Stepper from './Stepper.jsx';
|
||||
|
||||
const snippets = [
|
||||
[`{ "s": "cp", "crush": 4 }`, 'Samples'],
|
||||
[`{ "s": "sawtooth", "note": "e3" }`, 'Oscillators'],
|
||||
[
|
||||
`[
|
||||
{ "s": "gm_epiano2", "note": "c4" },
|
||||
{ "s": "gm_epiano2", "note": "eb4" },
|
||||
{ "s": "gm_epiano2", "note": "g4" }
|
||||
]`,
|
||||
'Soundfonts',
|
||||
],
|
||||
[
|
||||
`{
|
||||
"s": "toys",
|
||||
"cutoff": 2000,
|
||||
"delay": 0.5,
|
||||
"crush": 4
|
||||
}`,
|
||||
'Effects',
|
||||
],
|
||||
];
|
||||
|
||||
function EventsDemo() {
|
||||
return (
|
||||
<Stepper
|
||||
steps={snippets.map(([code, label]) => (
|
||||
<div className="py-4 space-y-4">
|
||||
<h3>{label}</h3>
|
||||
<EventEditor code={code} />
|
||||
</div>
|
||||
))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default EventsDemo;
|
||||
@@ -1,21 +0,0 @@
|
||||
//import {Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
//import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
//import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import { atomOneDark } from 'react-syntax-highlighter/dist/esm/styles/hljs';
|
||||
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import js from 'react-syntax-highlighter/dist/esm/languages/hljs/javascript';
|
||||
import hs from 'react-syntax-highlighter/dist/esm/languages/hljs/haskell';
|
||||
SyntaxHighlighter.registerLanguage('javascript', js);
|
||||
SyntaxHighlighter.registerLanguage('haskell', hs);
|
||||
|
||||
function Highlight({ code, language = 'javascript' }) {
|
||||
return (
|
||||
<div className="text-[32px] leading-10 rounded-xl overflow-hidden">
|
||||
<SyntaxHighlighter language={language} style={atomOneDark} customStyle={{ padding: '8px 10px' }}>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Highlight;
|
||||
@@ -1,38 +0,0 @@
|
||||
import { SlideRepl } from './SlideRepl.jsx';
|
||||
import Stepper from './Stepper.jsx';
|
||||
|
||||
const snippets = [
|
||||
[`sound("bd [hh sd]")`, `sound(seq("bd", ["hh", "sd"]))`, 'Nested Sequences'], //
|
||||
[`sound("<bd sd>")`, `sound(cat("bd", "sd"))`, 'Cyclewise Sequences'], //
|
||||
[`sound("bd,hh")`, `sound(stack("bd", "hh"))`, 'Stack'], //
|
||||
[`sound("bd@3 sd@1")`, `sound(timeCat([3, "bd"], [1, "sd"]))`, 'Weighted Sequence'], //
|
||||
[`sound("{lt ht mt, bd sd}")`, `sound(polymeter(["lt","ht","mt"], ["bd", "sd"]))`, 'Polymeter'], //
|
||||
[`sound("{bd hh sd ht}%2")`, `sound(polymeterSteps(2, ["bd", "hh", "sd", "ht"]))`, 'Steps per Cycle'],
|
||||
];
|
||||
|
||||
function MiniComparison() {
|
||||
return (
|
||||
<>
|
||||
<h1>User Code: Mini Notation -> JavaScript</h1>
|
||||
<Stepper
|
||||
steps={snippets.map(([a, b, label]) => (
|
||||
<div className="py-4">
|
||||
<h3 className="pb-4">{label}</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex space-x-2">
|
||||
<img src="./img/tidalcycles.svg" className={`h-10 mt-2`} />
|
||||
<SlideRepl tune={a} hideHeader />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<img src="./img/js.jpg" className={`h-10 mt-2`} />
|
||||
<SlideRepl tune={b} hideHeader />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MiniComparison;
|
||||
@@ -1,50 +0,0 @@
|
||||
import { SlideRepl } from './SlideRepl.jsx';
|
||||
import Highlight from './Highlight.jsx';
|
||||
import Stepper from './Stepper.jsx';
|
||||
|
||||
const snippets = [
|
||||
[
|
||||
<>
|
||||
{' '}
|
||||
<SlideRepl tune={`s("bd [hh sd]").crush(4).log()`} hideHeader />
|
||||
</>,
|
||||
'Logging Events',
|
||||
],
|
||||
[
|
||||
<>
|
||||
<Highlight
|
||||
code={`let pattern = s("bd [hh sd]").crush(4);
|
||||
let events = pattern.queryArc(0, 1);
|
||||
console.log(events.map(e => e.show()))`}
|
||||
language="javascript"
|
||||
/>
|
||||
<Highlight
|
||||
language="json"
|
||||
code={`[
|
||||
"0/1 -> 1/2 | s:bd crush:4",
|
||||
"1/2 -> 3/4 | s:hh crush:4",
|
||||
"3/4 -> 1/1 | s:sd crush:4"
|
||||
]`}
|
||||
/>
|
||||
</>,
|
||||
'Querying Events Manually',
|
||||
],
|
||||
];
|
||||
|
||||
function QueryDemo() {
|
||||
return (
|
||||
<>
|
||||
<img src="./img/queryflow.png" className="w-[800px]" />
|
||||
<Stepper
|
||||
steps={snippets.map(([snippet, label]) => (
|
||||
<div className="py-4 space-y-4">
|
||||
<h3>{label}</h3>
|
||||
{snippet}
|
||||
</div>
|
||||
))}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default QueryDemo;
|
||||
@@ -1,49 +0,0 @@
|
||||
import { evalScope, controls } from '@strudel.cycles/core';
|
||||
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
||||
import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
|
||||
import { prebake } from './prebake';
|
||||
import { atomone } from '@uiw/codemirror-themes-all';
|
||||
import blackscreen from '@strudel.cycles/react/src/themes/blackscreen';
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
await evalScope(
|
||||
controls,
|
||||
import('@strudel.cycles/core'),
|
||||
import('@strudel.cycles/tonal'),
|
||||
import('@strudel.cycles/mini'),
|
||||
import('@strudel.cycles/webaudio'),
|
||||
import('@strudel.cycles/soundfonts'),
|
||||
import('@strudel.cycles/csound'),
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
initAudioOnFirstClick();
|
||||
prebake();
|
||||
}
|
||||
|
||||
export function SlideRepl({
|
||||
tune,
|
||||
drawTime,
|
||||
punchcard,
|
||||
canvasHeight = 100,
|
||||
hideHeader = false,
|
||||
fontSize = 32,
|
||||
disabled = false,
|
||||
}) {
|
||||
return (
|
||||
<div className="not-prose rounded-xl overflow-hidden">
|
||||
<_MiniRepl
|
||||
hideHeader={hideHeader}
|
||||
tune={tune}
|
||||
hideOutsideView={true}
|
||||
drawTime={drawTime}
|
||||
punchcard={punchcard}
|
||||
canvasHeight={canvasHeight}
|
||||
fontSize={fontSize}
|
||||
theme={disabled ? blackscreen : atomone}
|
||||
// theme={themes[theme]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import { persistentAtom } from '@nanostores/persistent';
|
||||
import { useStore } from '@nanostores/react'; // or '@nanostores/preact'
|
||||
import useEvent from './useEvent';
|
||||
|
||||
function cx(...classes) {
|
||||
return classes.filter(Boolean).join(' ');
|
||||
}
|
||||
// https://vitejs.dev/guide/features.html#glob-import
|
||||
const slideImports = import.meta.glob('./slides/*.mdx');
|
||||
//const slideImports = import.meta.glob('./slides/*.mdx', { eager: true });
|
||||
console.log('load slides...', slideImports);
|
||||
|
||||
const loadedMDXFiles = await Promise.all(
|
||||
Object.entries(slideImports).map(async ([path, load]) => {
|
||||
//Object.entries(slideImports).map(async ([path, m]) => {
|
||||
const segments = path.split('/');
|
||||
const filename = segments[segments.length - 1].slice(0, -4); // expects .mdx at the end
|
||||
return [filename, (await load()).default];
|
||||
//return [filename, m.default];
|
||||
}),
|
||||
);
|
||||
console.log('load done!');
|
||||
|
||||
const order = [
|
||||
'cover',
|
||||
'whatIsStrudel',
|
||||
'history',
|
||||
'hs2js',
|
||||
|
||||
'usage',
|
||||
'swatch',
|
||||
'repl',
|
||||
//'repl-features',
|
||||
'viz',
|
||||
|
||||
'replflow',
|
||||
|
||||
// User Code
|
||||
'transpilation',
|
||||
'mininotation',
|
||||
// 'mini-comparison', // remove?
|
||||
// Patterns
|
||||
'patterns-events', // remove?
|
||||
// scheduler?
|
||||
// Output
|
||||
'events-audio', // remove?
|
||||
'outputs',
|
||||
// 'csound', // remove?
|
||||
//
|
||||
// pattern alignment?
|
||||
'bad',
|
||||
'good',
|
||||
'future',
|
||||
'end',
|
||||
];
|
||||
// examples swatch
|
||||
// draw examples!!
|
||||
|
||||
const slideEntries = order.map((name) => loadedMDXFiles.find(([file]) => file === name));
|
||||
|
||||
// current slide index is persisted, so it's safe to refresh the browser
|
||||
export const slideIndex = persistentAtom('slideIndex', '0');
|
||||
|
||||
export const prev = () => slideIndex.set((parseInt(slideIndex.get()) - 1 + slideEntries.length) % slideEntries.length);
|
||||
export const next = () => slideIndex.set((parseInt(slideIndex.get()) + 1) % slideEntries.length);
|
||||
|
||||
function Slides() {
|
||||
const activeIndex = parseInt(useStore(slideIndex));
|
||||
|
||||
/*useEvent('click', (e) => {
|
||||
if (!e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
const leftHalf = e.clientX / document.body.clientWidth < 0.5;
|
||||
if (leftHalf) {
|
||||
prev();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});*/
|
||||
/*useEvent('keydown', (e) => {
|
||||
if (e.key === 'Home') {
|
||||
prev();
|
||||
} else if (e.key === 'End') {
|
||||
next();
|
||||
}
|
||||
});*/
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden relative">
|
||||
{slideEntries.map(([path, Slide], i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cx(
|
||||
`bg-slate-900 w-full h-full flex justify-center absolute top-0 transition-all ease-in-out duration-500 overflow-auto`,
|
||||
i < activeIndex && '-translate-x-full',
|
||||
i > activeIndex && 'translate-x-full',
|
||||
)}
|
||||
>
|
||||
<div className="prose prose-invert w-[1400px] p-12 max-w-full" style={{ fontSize: '2em' }}>
|
||||
{Math.abs(i - activeIndex) <= 1 && <Slide />}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="text-white text-2xl text-bold cursor-pointer select-none">
|
||||
<div className="fixed left-0 p-4 h-full items-start flex hover:bg-[#00000050]" onClick={() => prev()}>
|
||||
<
|
||||
</div>
|
||||
<div className="fixed right-0 p-4 h-full items-start flex hover:bg-[#00000050]" onClick={() => next()}>
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute top-0 w-full">
|
||||
<div className="h-[5px] bg-slate-500 w-full z-100">
|
||||
<div
|
||||
style={{ width: ((activeIndex / (slideEntries.length - 1)) * 100).toFixed(0) + '%' }}
|
||||
className="h-full bg-yellow-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Slides;
|
||||
@@ -1,20 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
function Stepper({ steps }) {
|
||||
const [step, setStep] = useState(1);
|
||||
return (
|
||||
<div className="not-prose">
|
||||
{steps.slice(0, step).map((snippet, i) => {
|
||||
const isActive = i === step - 1;
|
||||
return (
|
||||
<div key={i} className={`border-l-4 pl-8 border-gray-500 ${isActive ? 'border-yellow-500' : ''}`}>
|
||||
{snippet}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{step < steps.length && <button onClick={() => setStep((r) => r + 1)}>next</button>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Stepper;
|
||||
@@ -1,73 +0,0 @@
|
||||
import { SlideRepl } from './SlideRepl.jsx';
|
||||
import Highlight from './Highlight.jsx';
|
||||
import Stepper from './Stepper.jsx';
|
||||
|
||||
const snippets = [
|
||||
[`sound "bd ~ [sd cp]"`, [`sound("bd ~ [sd cp]")`], 'Mini Notation'], //
|
||||
[`sound "bd ~ [sd cp]" # speed "1 2"`, [`sound("bd ~ [sd cp]").speed("1 2")`], 'Composing Patterns'],
|
||||
[
|
||||
`fast 2 $ sound "bd ~ [sd cp]"`,
|
||||
[`fast(2, sound("bd ~ [sd cp]"))`, `sound("bd ~ [sd cp]").fast(2)`],
|
||||
'Patterns Transformations',
|
||||
],
|
||||
[
|
||||
`speed "1 2 3" + "4 5" # s "bd"`,
|
||||
['speed("1 2 3".add("4 5")).s("bd")', '"1 2 3".add("4 5").speed().s("bd")'],
|
||||
'Pattern Arithmetic',
|
||||
],
|
||||
[
|
||||
`jux rev
|
||||
$ every 3 (fast 2)
|
||||
$ sound "bd sd"`,
|
||||
[
|
||||
`sound("bd sd")
|
||||
.every(3, fast("2"))
|
||||
.jux(rev)`,
|
||||
],
|
||||
'Higher order transformations (with partial application)',
|
||||
],
|
||||
[
|
||||
`jux rev
|
||||
$ every "<3 5>" (fast "1 2")
|
||||
$ sound "bd sd cp mt"`,
|
||||
[
|
||||
`sound("bd sd cp mt")
|
||||
.every("<3 5>", fast("1 2"))
|
||||
.jux(rev)`,
|
||||
],
|
||||
'Patterns all the way down',
|
||||
],
|
||||
];
|
||||
|
||||
function SyntaxComparison() {
|
||||
return (
|
||||
<>
|
||||
<h1>Haskell -> JavaScript</h1>
|
||||
<Stepper
|
||||
steps={snippets
|
||||
.map(([hs, js, label]) => (
|
||||
<div className="py-4 space-y-4">
|
||||
<h3>{label}</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex space-x-4">
|
||||
<img src="./img/haskell.png" className="h-10 mt-2" />
|
||||
<Highlight language="haskell" code={hs} />
|
||||
</div>
|
||||
<div>
|
||||
{js.map((c, j) => (
|
||||
<div className="flex space-x-4" key={j}>
|
||||
<img src="./img/js.jpg" className={`h-10 mt-2`} />
|
||||
<SlideRepl tune={c} hideHeader />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
.concat([<>Fluent Interface: Just chain all the things!</>])}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SyntaxComparison;
|
||||
@@ -1,84 +0,0 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { atomone } from '@uiw/codemirror-themes-all';
|
||||
import { CodeMirror, flash, useKeydown } from '@strudel.cycles/react';
|
||||
import { transpiler } from '@strudel.cycles/transpiler';
|
||||
import Highlight from './Highlight';
|
||||
import Stepper from './Stepper';
|
||||
import escodegen from 'escodegen';
|
||||
|
||||
function getTranspiled(code) {
|
||||
return transpiler(code, {
|
||||
addReturn: false,
|
||||
codegenOptions: {
|
||||
format: escodegen.FORMAT_MINIFY,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function TranspilationEditor({ code: initialCode }) {
|
||||
const [code, setCode] = useState(initialCode);
|
||||
const [transpiled, setTranspiled] = useState(getTranspiled(initialCode));
|
||||
const [view, setView] = useState();
|
||||
useKeydown(
|
||||
useCallback(
|
||||
async (e) => {
|
||||
if (view?.hasFocus) {
|
||||
if (e.ctrlKey || e.altKey) {
|
||||
if (e.code === 'Enter') {
|
||||
/* if (getAudioContext().state !== 'running') {
|
||||
alert('please click play to initialize the audio. you can use shortcuts after that!');
|
||||
return;
|
||||
} */
|
||||
e.preventDefault();
|
||||
flash(view);
|
||||
const t = getTranspiled(code);
|
||||
setTranspiled(t);
|
||||
console.log('transpiled', t);
|
||||
} else if (e.key === '.') {
|
||||
stop();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[code, view],
|
||||
),
|
||||
);
|
||||
return (
|
||||
<div className="space-y-2 py-2">
|
||||
<CodeMirror
|
||||
value={code}
|
||||
onChange={(v) => {
|
||||
setCode(v);
|
||||
}}
|
||||
theme={atomone}
|
||||
onViewChanged={(v) => setView(v)}
|
||||
fontSize={32}
|
||||
/>
|
||||
<span>⬇️ transpiles to</span>
|
||||
<Highlight language="javascript" code={transpiled} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TranspilationDemo() {
|
||||
return (
|
||||
<div className="not-prose space-y-8">
|
||||
<Stepper
|
||||
steps={[
|
||||
<>
|
||||
<img src="./img/transpileflow.png" className="h-[500px]" />
|
||||
<ul className="list-decimal pl-8">
|
||||
<li>Parse JS Code with acorn to get AST</li>
|
||||
<li>Modify AST with estree-walker</li>
|
||||
<li>Convert modified AST back to JS with escodegen</li>
|
||||
</ul>
|
||||
</>,
|
||||
<TranspilationEditor code={`"bd [hh sd]"`} />,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TranspilationDemo;
|
||||
@@ -1,21 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.cm-activeLine {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.cm-theme {
|
||||
background-color: var(--background);
|
||||
border: 1px solid var(--lineHighlight);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.cm-gutters {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
img {
|
||||
@apply rounded-xl overflow-hidden;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import Slides from './Slides';
|
||||
import './index.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<Slides />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
@@ -1,22 +0,0 @@
|
||||
import { registerSynthSounds, samples } from '@strudel.cycles/webaudio';
|
||||
import { registerSoundfonts } from '@strudel.cycles/soundfonts';
|
||||
|
||||
export async function prebake() {
|
||||
// https://archive.org/details/SalamanderGrandPianoV3
|
||||
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
|
||||
await Promise.all([
|
||||
registerSynthSounds(),
|
||||
registerSoundfonts(`./samples/webaudiofontdata/sound/`),
|
||||
/* samples(`./piano.json`, `./piano/`, { prebake: true }),
|
||||
samples(`./vcsl.json`, 'github:sgossner/VCSL/master/', { prebake: true }),
|
||||
*/
|
||||
//samples(`./tidal-drum-machines.json`, 'github:ritchse/tidal-drum-machines/main/machines/', {
|
||||
samples(`./tidal-drum-machines.json`, './samples/tidal-drum-machines/machines/', {
|
||||
prebake: true,
|
||||
tag: 'drum-machines',
|
||||
}) /*
|
||||
samples(`./EmuSP12.json`, `./EmuSP12/`, { prebake: true, tag: 'drum-machines' }), */,
|
||||
// samples('github:tidalcycles/Dirt-Samples/master'),
|
||||
samples('./Dirt-Samples.json', './samples/Dirt-Samples/'),
|
||||
]);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
# What's bad
|
||||
|
||||
- Dynamic Typing in JS
|
||||
- Fractions and Hot Paths
|
||||
- Verbose function composition
|
||||
- Method-chaining tradeoffs
|
||||
@@ -1,7 +0,0 @@
|
||||
<div className="flex flex-col justify-between h-full overflow-hidden max-h-screen space-y-8">
|
||||
<h2 className="m-0">Strudel: live coding patterns on the Web</h2>
|
||||
<div className="justify-center flex overflow-hidden not-prose">
|
||||
<img src="./img/strudel_icon.png" className="h-full" />
|
||||
</div>
|
||||
<span>Felix Roos, Alex McLean</span>
|
||||
</div>
|
||||
@@ -1,65 +0,0 @@
|
||||
import { SlideRepl } from '../SlideRepl.jsx';
|
||||
|
||||
# CSound Example
|
||||
|
||||
## Loading ORC instruments
|
||||
|
||||
<SlideRepl
|
||||
tune={`// livecode.orc by Steven Yi
|
||||
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
|
||||
|
||||
stack(
|
||||
note("<C^7 A7 Dm7 Fm7>/2".voicings('lefthand'))
|
||||
.cutoff(sine.range(500,2000).round().slow(16))
|
||||
.euclidLegato(3,8).csound('FM1'),
|
||||
|
||||
note("<C2 A1 D2 F2>/2").ply(8).csound('Bass')
|
||||
.gain("1 4 1 4"),
|
||||
|
||||
note("0 7 [4 3] 2".fast(2/3)
|
||||
.off(".25 .125", add("<2 4 -3 -1>"))
|
||||
.slow(2).scale('A4 minor'))
|
||||
.legato(.25).csound('SynHarp'),
|
||||
|
||||
s("bd*2,[~ hh]*2,~ cp").bank('RolandTR909')
|
||||
)`}
|
||||
hideHeader
|
||||
/>
|
||||
|
||||
## Synthesis
|
||||
|
||||
<SlideRepl
|
||||
tune={`await loadCsound\`
|
||||
instr CoolSynth
|
||||
iduration = p3
|
||||
ifreq = p4
|
||||
igain = p5
|
||||
ioct = octcps(ifreq)
|
||||
|
||||
kpwm = oscili(.05, 8)
|
||||
asig = vco2(igain, ifreq, 4, .5 + kpwm)
|
||||
asig += vco2(igain, ifreq * 2)
|
||||
|
||||
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 + 2), 0.5)
|
||||
|
||||
iattack = .01
|
||||
isustain = .5
|
||||
idecay = .1
|
||||
irelease = .1
|
||||
asig *= linsegr:a(0, iattack, 1, idecay, isustain, iduration, isustain, irelease, 0)
|
||||
|
||||
out(asig, asig)
|
||||
|
||||
endin\`
|
||||
|
||||
"<0 2 [4 6](3,4,2) 3\*2>"
|
||||
.off(1/4, add(2))
|
||||
.off(1/2, add(6))
|
||||
.scale('D minor')
|
||||
.note()
|
||||
//.pianoroll()
|
||||
.csound('CoolSynth')`}
|
||||
hideHeader
|
||||
/>
|
||||
@@ -1 +0,0 @@
|
||||
# Strudel Docs
|
||||
@@ -1,4 +0,0 @@
|
||||
# Thanks
|
||||
|
||||
- ICLC organizers
|
||||
- Tidal Community
|
||||
@@ -1,7 +0,0 @@
|
||||
import { SlideRepl } from '../SlideRepl.jsx';
|
||||
import Highlight from '../Highlight.jsx';
|
||||
import EventsDemo from '../EventsDemo.jsx';
|
||||
|
||||
# Webaudio Output: Values -> Sound
|
||||
|
||||
<EventsDemo />
|
||||
@@ -1,15 +0,0 @@
|
||||
import { SlideRepl } from '../SlideRepl.jsx';
|
||||
|
||||
# Pattern Example
|
||||
|
||||
<SlideRepl
|
||||
tune={`"<0 2 [4 6](3,4,1) 3>"
|
||||
.off(1/4, add(2))
|
||||
.off(1/2, add(6))
|
||||
.scale('D minor')
|
||||
.legato(.25)
|
||||
.note().s("sawtooth square")
|
||||
.delay(.8).delaytime(.125)`}
|
||||
hideHeader
|
||||
punchcard
|
||||
/>
|
||||
@@ -1,10 +0,0 @@
|
||||
# Future Outlook
|
||||
|
||||
- (slow) community growth
|
||||
- block based evaluation
|
||||
- live collaboration
|
||||
- more audio backends
|
||||
- interface experiments, e.g.:
|
||||
- alternative syntax, visual notations
|
||||
- time scrubbing
|
||||
- ui controls
|
||||
@@ -1,9 +0,0 @@
|
||||
# What's good
|
||||
|
||||
- Zero-install, multi-platform
|
||||
- works (surprisingly) well on mobile devices
|
||||
- visual feedback good for teaching + learning
|
||||
- loads of potential contributors
|
||||
- instant pattern sharing
|
||||
- two-way flow of features between strudel and tidal
|
||||
- full mini-notation in hydra and gibber
|
||||
@@ -1,3 +0,0 @@
|
||||
# The journey
|
||||
|
||||

|
||||
@@ -1,3 +0,0 @@
|
||||
import SyntaxComparison from '../SyntaxComparison.jsx';
|
||||
|
||||
<SyntaxComparison />
|
||||
@@ -1,5 +0,0 @@
|
||||
import { SlideRepl } from '../SlideRepl.jsx';
|
||||
import Highlight from '../Highlight.jsx';
|
||||
import MiniComparison from '../MiniComparison.jsx';
|
||||
|
||||
<MiniComparison />
|
||||
@@ -1,29 +0,0 @@
|
||||
import Highlight from '../Highlight';
|
||||
import Stepper from '../Stepper';
|
||||
|
||||
# User Code: Mini Notation
|
||||
|
||||
<img src="./img/pegflow.png" />
|
||||
|
||||
<Stepper
|
||||
steps={[
|
||||
<ul className="list-decimal pl-8">
|
||||
<li>Parse Mini Notation to get AST</li>
|
||||
<li>walk AST and construct function calls</li>
|
||||
</ul>,
|
||||
<div className="space-y-2 py-2">
|
||||
<Highlight code={`mini('bd [hh sd]')`} />
|
||||
<span>⬇️ resulting calls</span>
|
||||
<Highlight
|
||||
code={`seq(
|
||||
reify('bd').withLocation([1,1,1], [1,4,4]),
|
||||
seq(
|
||||
reify('hh').withLocation([1,5,5], [1,8,8]),
|
||||
reify('sd').withLocation([1,8,8], [1,10,10]),
|
||||
)
|
||||
)`}
|
||||
/>
|
||||
<span>➡️ location can be used for highlighting</span>
|
||||
</div>,
|
||||
]}
|
||||
/>
|
||||
@@ -1,7 +0,0 @@
|
||||
# What can I pattern?
|
||||
|
||||
- Default: Web Audio API
|
||||
- OSC via osc-js, e.g. SuperDirt
|
||||
- Csound via the Csound WebAssembly build
|
||||
- MIDI via WebMIDI
|
||||
- Serial via WebSerial, e.g. for robot dancing
|
||||
@@ -1,5 +0,0 @@
|
||||
import QueryDemo from '../QueryDemo.jsx';
|
||||
|
||||
# Querying: From Patterns to Events
|
||||
|
||||
<QueryDemo />
|
||||
@@ -1,12 +0,0 @@
|
||||
# REPL: Features
|
||||
|
||||
- Visual Feedback
|
||||
- Example shuffler (Hydra-style)
|
||||
- Tutorial + reference
|
||||
- Shareable links
|
||||
- Sound Browser
|
||||
- Offline Support (+ installable PWA)
|
||||
- Outputs: Web Audio, MIDI, OSC, CSound, Serial, ..
|
||||
- Themes
|
||||
|
||||

|
||||
@@ -1,7 +0,0 @@
|
||||
# REPL - live coding editor
|
||||
|
||||
[strudel.tidalcycles.org](https://strudel.tidalcycles.org/)
|
||||
|
||||
{/* <iframe className="w-full h-[600px]" src="https://strudel.tidalcycles.org/#c2FtcGxlcyh7CiAgYmQ6IFsnYmQvQlQwQUFEQS53YXYnLCdiZC9CVDBBQUQwLndhdicsJ2JkL0JUMEEwREEud2F2JywnYmQvQlQwQTBEMy53YXYnLCdiZC9CVDBBMEQwLndhdicsJ2JkL0JUMEEwQTcud2F2J10sCiAgc2Q6IFsnc2Qvcnl0bS0wMS1jbGFzc2ljLndhdicsJ3NkL3J5dG0tMDAtaGFyZC53YXYnXSwKICBoaDogWydoaDI3LzAwMF9oaDI3Y2xvc2VkaGgud2F2JywnaGgvMDAwX2hoM2Nsb3NlZGhoLndhdiddLAp9LCAnZ2l0aHViOnRpZGFsY3ljbGVzL0RpcnQtU2FtcGxlcy9tYXN0ZXIvJyk7CnN0YWNrKAogIHMoImJkLFt%2BIDxzZCEzIHNkKDMsNCwyKT5dLGhoKjQiKSAvLyBkcnVtcwogIC5zcGVlZChwZXJsaW4ucmFuZ2UoLjcsMSkpIC8vIHJhbmRvbSBzYW1wbGUgc3BlZWQgdmFyaWF0aW9uCiAgLy8uaHVzaCgpCiAgLCI8YTEgYjEqMiBhMSgzLDgpIGUyPiIgLy8gYmFzc2xpbmUKICAub2ZmKDEvOCx4PT54LmFkZCgxMikuZGVncmFkZUJ5KC41KSkgLy8gcmFuZG9tIG9jdGF2ZSBqdW1wcwogIC5hZGQocGVybGluLnJhbmdlKDAsLjUpKSAvLyByYW5kb20gcGl0Y2ggdmFyaWF0aW9uCiAgLnN1cGVyaW1wb3NlKGFkZCguMDUpKSAvLyBhZGQgc2Vjb25kLCBzbGlnaHRseSBkZXR1bmVkIHZvaWNlCiAgLm4oKSAvLyB3cmFwIGluICJuIgogIC5kZWNheSguMTUpLnN1c3RhaW4oMCkgLy8gbWFrZSBlYWNoIG5vdGUgb2YgZXF1YWwgbGVuZ3RoCiAgLnMoJ3Nhd3Rvb3RoJykgLy8gd2F2ZWZvcm0KICAuZ2FpbiguNCkgLy8gdHVybiBkb3duCiAgLmN1dG9mZihzaW5lLnNsb3coNykucmFuZ2UoMzAwLDUwMDApKSAvLyBhdXRvbWF0ZSBjdXRvZmYKICAvLy5odXNoKCkKICAsIjxBbTchMyA8RW03IEU3YjEzIEVtNyBFYm03YjU%2BPiIudm9pY2luZ3MoJ2xlZnRoYW5kJykgLy8gY2hvcmRzCiAgLnN1cGVyaW1wb3NlKHg9PnguYWRkKC4wNCkpIC8vIGFkZCBzZWNvbmQsIHNsaWdodGx5IGRldHVuZWQgdm9pY2UKICAuYWRkKHBlcmxpbi5yYW5nZSgwLC41KSkgLy8gcmFuZG9tIHBpdGNoIHZhcmlhdGlvbgogIC5uKCkgLy8gd3JhcCBpbiAibiIKICAucygnc2F3dG9vdGgnKSAvLyB3YXZlZm9ybQogIC5nYWluKC4xNikgLy8gdHVybiBkb3duCiAgLmN1dG9mZig1MDApIC8vIGZpeGVkIGN1dG9mZgogIC5hdHRhY2soMSkgLy8gc2xvd2x5IGZhZGUgaW4KICAvLy5odXNoKCkKICAsImE0IGM1IDxlNiBhNj4iLnN0cnVjdCgieCg1LDgpIikKICAuc3VwZXJpbXBvc2UoeD0%2BeC5hZGQoLjA0KSkgLy8gYWRkIHNlY29uZCwgc2xpZ2h0bHkgZGV0dW5lZCB2b2ljZQogIC5hZGQocGVybGluLnJhbmdlKDAsLjUpKSAvLyByYW5kb20gcGl0Y2ggdmFyaWF0aW9uCiAgLm4oKSAvLyB3cmFwIGluICJuIgogIC5kZWNheSguMSkuc3VzdGFpbigwKSAvLyBtYWtlIG5vdGVzIHNob3J0CiAgLnMoJ3RyaWFuZ2xlJykgLy8gd2F2ZWZvcm0KICAuZGVncmFkZUJ5KHBlcmxpbi5yYW5nZSgwLC41KSkgLy8gcmFuZG9tbHkgY29udHJvbGxlZCByYW5kb20gcmVtb3ZhbCA6KQogIC5lY2hvV2l0aCg0LC4xMjUsKHgsbik9PnguZ2FpbiguMTUqMS8obisxKSkpIC8vIGVjaG8gbm90ZXMKICAvLy5odXNoKCkKKQogIC5zbG93KDEuNCk%3D" /> */}
|
||||
|
||||

|
||||
@@ -1,5 +0,0 @@
|
||||
# REPL: System Overview
|
||||
|
||||
<center>
|
||||
<img src="./img/strudelflow.png" className="w-[900px]" />
|
||||
</center>
|
||||
@@ -1,3 +0,0 @@
|
||||
# Strudel Docs
|
||||
|
||||

|
||||
@@ -1,5 +0,0 @@
|
||||
# Pattern Swatches
|
||||
|
||||
fork strudel - publish your own patterns with a fixed version
|
||||
|
||||

|
||||
@@ -1,5 +0,0 @@
|
||||
import TranspilationDemo from '../TranspilationDemo';
|
||||
|
||||
# User Code: Transpilation
|
||||
|
||||
<TranspilationDemo />
|
||||
@@ -1,14 +0,0 @@
|
||||
import Highlight from '../Highlight.jsx';
|
||||
|
||||
# Ways to use Strudel
|
||||
|
||||
- "Strudel REPL" at [strudel.tidalcycles.org](https://strudel.tidalcycles.org)
|
||||
- strudel on [flok.cc](https://flok.cc/) for collaboration
|
||||
- `@strudel.cycles/*` npm packages for your own projects
|
||||
- Embeddable [Web Component](https://github.com/tidalcycles/strudel/tree/main/packages/embed#strudelcyclesembed)
|
||||
- Browser Plugin for [Mastodon](https://github.com/mortuosplango/topfenstrudel) + Discourse Plugin:
|
||||
|
||||
<div className="flex">
|
||||
<img src="./img/topfenstrudel.png" />
|
||||
<img src="./img/discourse.png" />
|
||||
</div>
|
||||
@@ -1,20 +0,0 @@
|
||||
import { SlideRepl } from '../SlideRepl.jsx';
|
||||
|
||||
# REPL: Visual Feedback
|
||||
|
||||
Highlighting + Piano Roll
|
||||
|
||||
<SlideRepl
|
||||
hideHeader
|
||||
punchcard
|
||||
canvasHeight={300}
|
||||
fontSize={24}
|
||||
tune={`// Steve Reich - Piano Phase (1967)
|
||||
note("E4 F#4 B4 C#5 D5 F#4 E4 C#5 B4 F#4 D5 C#5")
|
||||
.layer(
|
||||
x=>x.color('#00ff0050').pan(0).fast(1), // left speaker
|
||||
x=>x.color('#ff00ff50').pan(1).fast(1.01) // right speaker
|
||||
)
|
||||
.slow(2).room(.5)
|
||||
`}
|
||||
/>
|
||||
@@ -1,28 +0,0 @@
|
||||
import { SlideRepl } from '../SlideRepl.jsx';
|
||||
|
||||
# What is Strudel
|
||||
|
||||
- TidalCycles faithfully ported to JavaScript
|
||||
- zero-install live coding environment for the browser
|
||||
- free & open source, modular, hackable, sharable, embeddable:
|
||||
|
||||
<SlideRepl
|
||||
hideHeader
|
||||
fontSize={24}
|
||||
tune={`// froos - freakuency - 2023/03/29
|
||||
stack(
|
||||
stack(
|
||||
s("bd, ~ <rim cp>,~ hh*2").off(1/8, x=>x.speed(2).gain(.5)).fast(2),
|
||||
s("oh*6").end(.01).speed(.5).room(.5).fast("1 2")
|
||||
).bank('RolandTR909'),
|
||||
//
|
||||
note("[c a f e](<3 5>,8,<0 1>)".add("0,.1").sub(24)).s('sawtooth').lpf(500),
|
||||
//
|
||||
"[~ <Cm7 Fm7 Bb7 Db7>]*2".voicings('lefthand').note().legato(.2)
|
||||
.echoWith(6, .1, (p,n)=>p.add(note(n*.6)))
|
||||
.lpf(sine.range(300,1000).slow(8)).lpq(6)
|
||||
.add(note(perlin.range(0,.8))).s('gm_epiano2')
|
||||
.mask("<0 1>")
|
||||
).restart("<1@3 1*[4 [8 16]]>")
|
||||
.slow(2)`}
|
||||
/>
|
||||
@@ -1,12 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
function useEvent(name, onTrigger, useCapture = false) {
|
||||
useEffect(() => {
|
||||
document.addEventListener(name, onTrigger, useCapture);
|
||||
return () => {
|
||||
document.removeEventListener(name, onTrigger, useCapture);
|
||||
};
|
||||
}, [onTrigger]);
|
||||
}
|
||||
|
||||
export default useEvent;
|
||||
@@ -1,26 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./index.html',
|
||||
'./src/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'../../packages/react/src/**/*.{html,js,jsx,md,mdx,ts,tsx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// codemirror-theme settings
|
||||
background: 'var(--background)',
|
||||
lineBackground: 'var(--lineBackground)',
|
||||
foreground: 'var(--foreground)',
|
||||
caret: 'var(--caret)',
|
||||
selection: 'var(--selection)',
|
||||
selectionMatch: 'var(--selectionMatch)',
|
||||
gutterBackground: 'var(--gutterBackground)',
|
||||
gutterForeground: 'var(--gutterForeground)',
|
||||
gutterBorder: 'var(--gutterBorder)',
|
||||
lineHighlight: 'var(--lineHighlight)',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import mdx from '@mdx-js/rollup';
|
||||
import gfm from 'remark-gfm';
|
||||
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||
|
||||
const options = {
|
||||
// See https://mdxjs.com/advanced/plugins
|
||||
remarkPlugins: [gfm],
|
||||
rehypePlugins: [],
|
||||
};
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
mdx(options),
|
||||
topLevelAwait({
|
||||
// The export name of top-level await promise for each chunk module
|
||||
promiseExportName: '__tla',
|
||||
// The function to generate import names of top-level await promise in each chunk module
|
||||
promiseImportName: (i) => `__tla_${i}`,
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
target: 'esnext', //browsers can handle the latest ES features
|
||||
},
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
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/desktopbridge/index.mjs';
|
||||
export * from './packages/midi/index.mjs';
|
||||
export * from './packages/mini/index.mjs';
|
||||
export * from './packages/osc/index.mjs';
|
||||
@@ -10,8 +10,6 @@ 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';
|
||||
|
||||
@@ -47,7 +47,7 @@ If you want to automatically deploy your site on push, go to `deploy.yml` and ch
|
||||
## running locally
|
||||
|
||||
- install dependencies with `npm run setup`
|
||||
- run dev server with `npm run repl` and open `http://localhost:3000/strudel/swatch/`
|
||||
- run dev server with `npm run repl` and open `http://localhost:4321/strudel/swatch/`
|
||||
|
||||
## tests fail?
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"build": "npm run prebuild && cd website && npm run build",
|
||||
"preview": "cd website && npm run preview",
|
||||
"osc": "cd packages/osc && npm run server",
|
||||
"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",
|
||||
@@ -44,7 +43,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://strudel.tidalcycles.org",
|
||||
"homepage": "https://strudel.cc",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/mini": "workspace:*",
|
||||
@@ -53,22 +52,21 @@
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"@strudel.cycles/xen": "workspace:*",
|
||||
"acorn": "^8.8.1",
|
||||
"dependency-tree": "^9.0.0",
|
||||
"vitest": "^0.25.7"
|
||||
"dependency-tree": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/ui": "^0.25.7",
|
||||
"c8": "^7.12.0",
|
||||
"canvas": "^2.11.0",
|
||||
"eslint": "^8.28.0",
|
||||
"@tauri-apps/cli": "^1.4.0",
|
||||
"@vitest/ui": "^0.28.0",
|
||||
"canvas": "^2.11.2",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"events": "^3.3.0",
|
||||
"gh-pages": "^4.0.0",
|
||||
"jsdoc": "^3.6.10",
|
||||
"jsdoc": "^4.0.2",
|
||||
"jsdoc-json": "^2.0.2",
|
||||
"jsdoc-to-markdown": "^7.1.1",
|
||||
"lerna": "^6.5.1",
|
||||
"prettier": "^2.8.1",
|
||||
"rollup-plugin-visualizer": "^5.8.1"
|
||||
"jsdoc-to-markdown": "^8.0.0",
|
||||
"lerna": "^6.6.1",
|
||||
"prettier": "^2.8.8",
|
||||
"rollup-plugin-visualizer": "^5.8.1",
|
||||
"vitest": "^0.33.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import jsdoc from '../../doc.json';
|
||||
// import { javascriptLanguage } from '@codemirror/lang-javascript';
|
||||
import { autocompletion } from '@codemirror/autocomplete';
|
||||
|
||||
const getDocLabel = (doc) => doc.name || doc.longname;
|
||||
const getInnerText = (html) => {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = html;
|
||||
return div.textContent || div.innerText || '';
|
||||
};
|
||||
|
||||
export function Autocomplete({ doc }) {
|
||||
return (
|
||||
<div className="prose dark:prose-invert max-h-[400px] overflow-auto">
|
||||
<h3 className="pt-0 mt-0">{getDocLabel(doc)}</h3>
|
||||
<div dangerouslySetInnerHTML={{ __html: doc.description }} />
|
||||
<ul>
|
||||
{doc.params?.map(({ name, type, description }, i) => (
|
||||
<li key={i}>
|
||||
{name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)}</> : ''}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div>
|
||||
{doc.examples?.map((example, i) => (
|
||||
<div key={i}>
|
||||
<pre
|
||||
className="cursor-pointer"
|
||||
onMouseDown={(e) => {
|
||||
console.log('ola!');
|
||||
navigator.clipboard.writeText(example);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{example}
|
||||
</pre>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const jsdocCompletions = jsdoc.docs
|
||||
.filter(
|
||||
(doc) =>
|
||||
getDocLabel(doc) &&
|
||||
!getDocLabel(doc).startsWith('_') &&
|
||||
!['package'].includes(doc.kind) &&
|
||||
!['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)),
|
||||
)
|
||||
// https://codemirror.net/docs/ref/#autocomplete.Completion
|
||||
.map((doc) /*: Completion */ => ({
|
||||
label: getDocLabel(doc),
|
||||
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
|
||||
info: () => {
|
||||
const node = document.createElement('div');
|
||||
// if Autocomplete is non-interactive, it could also be rendered at build time..
|
||||
// .. using renderToStaticMarkup
|
||||
createRoot(node).render(<Autocomplete doc={doc} />);
|
||||
return node;
|
||||
},
|
||||
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
||||
}));
|
||||
|
||||
export const strudelAutocomplete = (context /* : CompletionContext */) => {
|
||||
let word = context.matchBefore(/\w*/);
|
||||
if (word.from == word.to && !context.explicit) return null;
|
||||
return {
|
||||
from: word.from,
|
||||
options: jsdocCompletions,
|
||||
/* options: [
|
||||
{ label: 'match', type: 'keyword' },
|
||||
{ label: 'hello', type: 'variable', info: '(World)' },
|
||||
{ label: 'magic', type: 'text', apply: '⠁⭒*.✩.*⭒⠁', detail: 'macro' },
|
||||
], */
|
||||
};
|
||||
};
|
||||
|
||||
export function isAutoCompletionEnabled(on) {
|
||||
return on
|
||||
? [
|
||||
autocompletion({ override: [strudelAutocomplete] }),
|
||||
//javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }),
|
||||
]
|
||||
: []; // autocompletion({ override: [] })
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# @strudel/codemirror
|
||||
|
||||
This package contains helpers and extensions to use codemirror6. See [vite-vanilla-repl-cm6](../core/examples/vite-vanilla-repl-cm6/main.js) as an example of using it.
|
||||
@@ -0,0 +1,242 @@
|
||||
import { closeBrackets } from '@codemirror/autocomplete';
|
||||
// import { search, highlightSelectionMatches } from '@codemirror/search';
|
||||
import { history } from '@codemirror/commands';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||
import { Compartment, EditorState } from '@codemirror/state';
|
||||
import { EditorView, highlightActiveLineGutter, highlightActiveLine, keymap, lineNumbers } from '@codemirror/view';
|
||||
import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core';
|
||||
import { isAutoCompletionEnabled } from './Autocomplete';
|
||||
import { flash, isFlashEnabled } from './flash.mjs';
|
||||
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
||||
import { keybindings } from './keybindings.mjs';
|
||||
import { theme } from './themes.mjs';
|
||||
import { updateWidgets, sliderPlugin } from './slider.mjs';
|
||||
|
||||
const extensions = {
|
||||
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
|
||||
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
|
||||
theme,
|
||||
isAutoCompletionEnabled,
|
||||
isPatternHighlightingEnabled,
|
||||
isFlashEnabled,
|
||||
keybindings,
|
||||
};
|
||||
const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
|
||||
|
||||
// https://codemirror.net/docs/guide/
|
||||
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, settings, root }) {
|
||||
const initialSettings = Object.keys(compartments).map((key) =>
|
||||
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
|
||||
);
|
||||
let state = EditorState.create({
|
||||
doc: initialCode,
|
||||
extensions: [
|
||||
/* search(),
|
||||
highlightSelectionMatches(), */
|
||||
...initialSettings,
|
||||
javascript(),
|
||||
sliderPlugin,
|
||||
// indentOnInput(), // works without. already brought with javascript extension?
|
||||
// bracketMatching(), // does not do anything
|
||||
closeBrackets(),
|
||||
highlightActiveLineGutter(),
|
||||
highlightActiveLine(),
|
||||
syntaxHighlighting(defaultHighlightStyle),
|
||||
history(),
|
||||
EditorView.updateListener.of((v) => onChange(v)),
|
||||
keymap.of([
|
||||
{
|
||||
key: 'Ctrl-Enter',
|
||||
run: () => onEvaluate?.(),
|
||||
},
|
||||
{
|
||||
key: 'Alt-Enter',
|
||||
run: () => onEvaluate?.(),
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-.',
|
||||
run: () => onStop?.(),
|
||||
},
|
||||
{
|
||||
key: 'Alt-.',
|
||||
run: (_, e) => {
|
||||
e.preventDefault();
|
||||
onStop?.();
|
||||
},
|
||||
},
|
||||
/* {
|
||||
key: 'Ctrl-Shift-.',
|
||||
run: () => (onPanic ? onPanic() : onStop?.()),
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-Shift-Enter',
|
||||
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
|
||||
}, */
|
||||
]),
|
||||
],
|
||||
});
|
||||
|
||||
return new EditorView({
|
||||
state,
|
||||
parent: root,
|
||||
});
|
||||
}
|
||||
|
||||
export class StrudelMirror {
|
||||
constructor(options) {
|
||||
const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, settings, ...replOptions } = options;
|
||||
this.code = initialCode;
|
||||
this.root = root;
|
||||
this.miniLocations = [];
|
||||
this.widgets = [];
|
||||
this.painters = [];
|
||||
this.onDraw = onDraw;
|
||||
const self = this;
|
||||
|
||||
this.drawer = new Drawer((haps, time) => {
|
||||
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
|
||||
this.highlight(currentFrame, time);
|
||||
this.onDraw?.(haps, time, currentFrame, this.painters);
|
||||
}, drawTime);
|
||||
|
||||
// this approach might not work with multiple repls on screen..
|
||||
Pattern.prototype.onPaint = function (onPaint) {
|
||||
self.painters.push(onPaint);
|
||||
return this;
|
||||
};
|
||||
|
||||
this.prebaked = prebake();
|
||||
// this.drawFirstFrame();
|
||||
|
||||
this.repl = repl({
|
||||
...replOptions,
|
||||
onToggle: (started) => {
|
||||
replOptions?.onToggle?.(started);
|
||||
if (started) {
|
||||
this.drawer.start(this.repl.scheduler);
|
||||
} else {
|
||||
this.drawer.stop();
|
||||
updateMiniLocations(this.editor, []);
|
||||
cleanupDraw(false);
|
||||
}
|
||||
},
|
||||
beforeEval: async () => {
|
||||
cleanupDraw();
|
||||
this.painters = [];
|
||||
await this.prebaked;
|
||||
await replOptions?.beforeEval?.();
|
||||
},
|
||||
afterEval: (options) => {
|
||||
// remember for when highlighting is toggled on
|
||||
this.miniLocations = options.meta?.miniLocations;
|
||||
this.widgets = options.meta?.widgets;
|
||||
updateWidgets(this.editor, this.widgets);
|
||||
updateMiniLocations(this.editor, this.miniLocations);
|
||||
replOptions?.afterEval?.(options);
|
||||
this.drawer.invalidate();
|
||||
},
|
||||
});
|
||||
this.editor = initEditor({
|
||||
root,
|
||||
settings,
|
||||
initialCode,
|
||||
onChange: (v) => {
|
||||
if (v.docChanged) {
|
||||
this.code = v.state.doc.toString();
|
||||
this.repl.setCode(this.code);
|
||||
}
|
||||
},
|
||||
onEvaluate: () => this.evaluate(),
|
||||
onStop: () => this.stop(),
|
||||
});
|
||||
}
|
||||
async drawFirstFrame() {
|
||||
if (!this.onDraw) {
|
||||
return;
|
||||
}
|
||||
// draw first frame instantly
|
||||
await this.prebaked;
|
||||
try {
|
||||
await this.repl.evaluate(this.code, false);
|
||||
this.drawer.invalidate(this.repl.scheduler);
|
||||
this.onDraw?.(this.drawer.visibleHaps, 0, []);
|
||||
} catch (err) {
|
||||
console.warn('first frame could not be painted');
|
||||
}
|
||||
}
|
||||
async evaluate() {
|
||||
this.flash();
|
||||
await this.repl.evaluate(this.code);
|
||||
}
|
||||
async stop() {
|
||||
this.repl.scheduler.stop();
|
||||
}
|
||||
async toggle() {
|
||||
if (this.repl.scheduler.started) {
|
||||
this.repl.scheduler.stop();
|
||||
} else {
|
||||
this.evaluate();
|
||||
}
|
||||
}
|
||||
flash(ms) {
|
||||
flash(this.editor, ms);
|
||||
}
|
||||
highlight(haps, time) {
|
||||
highlightMiniLocations(this.editor, time, haps);
|
||||
}
|
||||
setFontSize(size) {
|
||||
this.root.style.fontSize = size + 'px';
|
||||
}
|
||||
setFontFamily(family) {
|
||||
this.root.style.fontFamily = family;
|
||||
}
|
||||
reconfigureExtension(key, value) {
|
||||
if (!extensions[key]) {
|
||||
console.warn(`extension ${key} is not known`);
|
||||
return;
|
||||
}
|
||||
value = parseBooleans(value);
|
||||
const newValue = extensions[key](value, this);
|
||||
this.editor.dispatch({
|
||||
effects: compartments[key].reconfigure(newValue),
|
||||
});
|
||||
}
|
||||
setLineWrappingEnabled(enabled) {
|
||||
this.reconfigureExtension('isLineWrappingEnabled', enabled);
|
||||
}
|
||||
setLineNumbersDisplayed(enabled) {
|
||||
this.reconfigureExtension('isLineNumbersDisplayed', enabled);
|
||||
}
|
||||
setTheme(theme) {
|
||||
this.reconfigureExtension('theme', theme);
|
||||
}
|
||||
setAutocompletionEnabled(enabled) {
|
||||
this.reconfigureExtension('isAutoCompletionEnabled', enabled);
|
||||
}
|
||||
updateSettings(settings) {
|
||||
this.setFontSize(settings.fontSize);
|
||||
this.setFontFamily(settings.fontFamily);
|
||||
for (let key in extensions) {
|
||||
this.reconfigureExtension(key, settings[key]);
|
||||
}
|
||||
}
|
||||
changeSetting(key, value) {
|
||||
if (extensions[key]) {
|
||||
this.reconfigureExtension(key, value);
|
||||
return;
|
||||
} else if (key === 'fontFamily') {
|
||||
this.setFontFamily(value);
|
||||
} else if (key === 'fontSize') {
|
||||
this.setFontSize(value);
|
||||
}
|
||||
}
|
||||
setCode(code) {
|
||||
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
|
||||
this.editor.dispatch({ changes });
|
||||
}
|
||||
}
|
||||
|
||||
function parseBooleans(value) {
|
||||
return { true: true, false: false }[value] ?? value;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { StateEffect, StateField } from '@codemirror/state';
|
||||
import { Decoration, EditorView } from '@codemirror/view';
|
||||
|
||||
export const setFlash = StateEffect.define();
|
||||
export const flashField = StateField.define({
|
||||
create() {
|
||||
return Decoration.none;
|
||||
},
|
||||
update(flash, tr) {
|
||||
try {
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(setFlash)) {
|
||||
if (e.value && tr.newDoc.length > 0) {
|
||||
const mark = Decoration.mark({ attributes: { style: `background-color: #FFCA2880` } });
|
||||
flash = Decoration.set([mark.range(0, tr.newDoc.length)]);
|
||||
} else {
|
||||
flash = Decoration.set([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return flash;
|
||||
} catch (err) {
|
||||
console.warn('flash error', err);
|
||||
return flash;
|
||||
}
|
||||
},
|
||||
provide: (f) => EditorView.decorations.from(f),
|
||||
});
|
||||
|
||||
export const flash = (view, ms = 200) => {
|
||||
view.dispatch({ effects: setFlash.of(true) });
|
||||
setTimeout(() => {
|
||||
view.dispatch({ effects: setFlash.of(false) });
|
||||
}, ms);
|
||||
};
|
||||
|
||||
export const isFlashEnabled = (on) => (on ? flashField : []);
|
||||
@@ -0,0 +1,135 @@
|
||||
import { RangeSetBuilder, StateEffect, StateField } from '@codemirror/state';
|
||||
import { Decoration, EditorView } from '@codemirror/view';
|
||||
|
||||
export const setMiniLocations = StateEffect.define();
|
||||
export const showMiniLocations = StateEffect.define();
|
||||
export const updateMiniLocations = (view, locations) => {
|
||||
view.dispatch({ effects: setMiniLocations.of(locations) });
|
||||
};
|
||||
export const highlightMiniLocations = (view, atTime, haps) => {
|
||||
view.dispatch({ effects: showMiniLocations.of({ atTime, haps }) });
|
||||
};
|
||||
|
||||
const miniLocations = StateField.define({
|
||||
create() {
|
||||
return Decoration.none;
|
||||
},
|
||||
update(locations, tr) {
|
||||
if (tr.docChanged) {
|
||||
locations = locations.map(tr.changes);
|
||||
}
|
||||
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(setMiniLocations)) {
|
||||
// this is called on eval, with the mini locations obtained from the transpiler
|
||||
// codemirror will automatically remap the marks when the document is edited
|
||||
// create a mark for each mini location, adding the range to the spec to find it later
|
||||
const marks = e.value
|
||||
.filter(([from]) => from < tr.newDoc.length)
|
||||
.map(([from, to]) => [from, Math.min(to, tr.newDoc.length)])
|
||||
.map(
|
||||
(range) =>
|
||||
Decoration.mark({
|
||||
id: range.join(':'),
|
||||
// this green is only to verify that the decoration moves when the document is edited
|
||||
// it will be removed later, so the mark is not visible by default
|
||||
attributes: { style: `background-color: #00CA2880` },
|
||||
}).range(...range), // -> Decoration
|
||||
);
|
||||
|
||||
locations = Decoration.set(marks, true); // -> DecorationSet === RangeSet<Decoration>
|
||||
}
|
||||
}
|
||||
|
||||
return locations;
|
||||
},
|
||||
});
|
||||
|
||||
const visibleMiniLocations = StateField.define({
|
||||
create() {
|
||||
return { atTime: 0, haps: new Map() };
|
||||
},
|
||||
update(visible, tr) {
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(showMiniLocations)) {
|
||||
// this is called every frame to show the locations that are currently active
|
||||
// we can NOT create new marks because the context.locations haven't changed since eval time
|
||||
// this is why we need to find a way to update the existing decorations, showing the ones that have an active range
|
||||
const haps = new Map();
|
||||
for (let hap of e.value.haps) {
|
||||
for (let { start, end } of hap.context.locations) {
|
||||
let id = `${start}:${end}`;
|
||||
if (!haps.has(id) || haps.get(id).whole.begin.lt(hap.whole.begin)) {
|
||||
haps.set(id, hap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visible = { atTime: e.value.atTime, haps };
|
||||
}
|
||||
}
|
||||
|
||||
return visible;
|
||||
},
|
||||
});
|
||||
|
||||
// // Derive the set of decorations from the miniLocations and visibleLocations
|
||||
const miniLocationHighlights = EditorView.decorations.compute([miniLocations, visibleMiniLocations], (state) => {
|
||||
const iterator = state.field(miniLocations).iter();
|
||||
const { haps } = state.field(visibleMiniLocations);
|
||||
const builder = new RangeSetBuilder();
|
||||
|
||||
while (iterator.value) {
|
||||
const {
|
||||
from,
|
||||
to,
|
||||
value: {
|
||||
spec: { id },
|
||||
},
|
||||
} = iterator;
|
||||
|
||||
if (haps.has(id)) {
|
||||
const hap = haps.get(id);
|
||||
const color = hap.context.color ?? 'var(--foreground)';
|
||||
// Get explicit channels for color values
|
||||
/*
|
||||
const swatch = document.createElement('div');
|
||||
swatch.style.color = color;
|
||||
document.body.appendChild(swatch);
|
||||
let channels = getComputedStyle(swatch)
|
||||
.color.match(/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*(\d*(?:\.\d+)?))?\)$/)
|
||||
.slice(1)
|
||||
.map((c) => parseFloat(c || 1));
|
||||
document.body.removeChild(swatch);
|
||||
|
||||
// Get percentage of event
|
||||
const percent = 1 - (atTime - hap.whole.begin) / hap.whole.duration;
|
||||
channels[3] *= percent;
|
||||
*/
|
||||
|
||||
builder.add(
|
||||
from,
|
||||
to,
|
||||
Decoration.mark({
|
||||
// attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` },
|
||||
attributes: { style: `outline: solid 2px ${color}` },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
iterator.next();
|
||||
}
|
||||
|
||||
return builder.finish();
|
||||
});
|
||||
|
||||
export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights];
|
||||
|
||||
export const isPatternHighlightingEnabled = (on, config) => {
|
||||
on &&
|
||||
config &&
|
||||
setTimeout(() => {
|
||||
updateMiniLocations(config.editor, config.miniLocations);
|
||||
}, 100);
|
||||
return on ? highlightExtension : [];
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './codemirror.mjs';
|
||||
export * from './highlight.mjs';
|
||||
export * from './flash.mjs';
|
||||
export * from './slider.mjs';
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Prec } from '@codemirror/state';
|
||||
import { keymap, ViewPlugin } from '@codemirror/view';
|
||||
// import { searchKeymap } from '@codemirror/search';
|
||||
import { emacs } from '@replit/codemirror-emacs';
|
||||
import { vim } from '@replit/codemirror-vim';
|
||||
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
||||
import { defaultKeymap, historyKeymap } from '@codemirror/commands';
|
||||
|
||||
const vscodePlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
constructor() {}
|
||||
},
|
||||
{
|
||||
provide: () => {
|
||||
return Prec.highest(keymap.of([...vscodeKeymap]));
|
||||
},
|
||||
},
|
||||
);
|
||||
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
|
||||
|
||||
const keymaps = {
|
||||
vim,
|
||||
emacs,
|
||||
vscode: vscodeExtension,
|
||||
};
|
||||
|
||||
export function keybindings(name) {
|
||||
const active = keymaps[name];
|
||||
return [keymap.of(defaultKeymap), keymap.of(historyKeymap), active ? active() : []];
|
||||
// keymap.of(searchKeymap),
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "@strudel/codemirror",
|
||||
"version": "0.9.0",
|
||||
"description": "Codemirror Extensions for Strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"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>",
|
||||
"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": {
|
||||
"@codemirror/autocomplete": "^6.6.0",
|
||||
"@codemirror/commands": "^6.2.4",
|
||||
"@codemirror/lang-javascript": "^6.1.7",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/search": "^6.0.0",
|
||||
"@codemirror/state": "^6.2.0",
|
||||
"@codemirror/view": "^6.10.0",
|
||||
"@lezer/highlight": "^1.1.4",
|
||||
"@replit/codemirror-emacs": "^6.0.1",
|
||||
"@replit/codemirror-vim": "^6.0.14",
|
||||
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@uiw/codemirror-themes": "^4.19.16",
|
||||
"@uiw/codemirror-themes-all": "^4.19.16",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^4.3.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import { ref, pure } from '@strudel.cycles/core';
|
||||
import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view';
|
||||
import { StateEffect, StateField } from '@codemirror/state';
|
||||
|
||||
export let sliderValues = {};
|
||||
const getSliderID = (from) => `slider_${from}`;
|
||||
|
||||
export class SliderWidget extends WidgetType {
|
||||
constructor(value, min, max, from, to, step, view) {
|
||||
super();
|
||||
this.value = value;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.from = from;
|
||||
this.originalFrom = from;
|
||||
this.to = to;
|
||||
this.step = step;
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
eq() {
|
||||
return false;
|
||||
}
|
||||
|
||||
toDOM() {
|
||||
let wrap = document.createElement('span');
|
||||
wrap.setAttribute('aria-hidden', 'true');
|
||||
wrap.className = 'cm-slider'; // inline-flex items-center
|
||||
let slider = wrap.appendChild(document.createElement('input'));
|
||||
slider.type = 'range';
|
||||
slider.min = this.min;
|
||||
slider.max = this.max;
|
||||
slider.step = this.step ?? (this.max - this.min) / 1000;
|
||||
slider.originalValue = this.value;
|
||||
// to make sure the code stays in sync, let's save the original value
|
||||
// becuase .value automatically clamps values so it'll desync with the code
|
||||
slider.value = slider.originalValue;
|
||||
slider.from = this.from;
|
||||
slider.originalFrom = this.originalFrom;
|
||||
slider.to = this.to;
|
||||
slider.style = 'width:64px;margin-right:4px;transform:translateY(4px)';
|
||||
this.slider = slider;
|
||||
slider.addEventListener('input', (e) => {
|
||||
const next = e.target.value;
|
||||
let insert = next;
|
||||
//let insert = next.toFixed(2);
|
||||
const to = slider.from + slider.originalValue.length;
|
||||
let change = { from: slider.from, to, insert };
|
||||
slider.originalValue = insert;
|
||||
slider.value = insert;
|
||||
this.view.dispatch({ changes: change });
|
||||
const id = getSliderID(slider.originalFrom); // matches id generated in transpiler
|
||||
window.postMessage({ type: 'cm-slider', value: Number(next), id });
|
||||
});
|
||||
return wrap;
|
||||
}
|
||||
|
||||
ignoreEvent(e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export const setWidgets = StateEffect.define();
|
||||
|
||||
export const updateWidgets = (view, widgets) => {
|
||||
view.dispatch({ effects: setWidgets.of(widgets) });
|
||||
};
|
||||
|
||||
function getWidgets(widgetConfigs, view) {
|
||||
return widgetConfigs.map(({ from, to, value, min, max, step }) => {
|
||||
return Decoration.widget({
|
||||
widget: new SliderWidget(value, min, max, from, to, step, view),
|
||||
side: 0,
|
||||
}).range(from /* , to */);
|
||||
});
|
||||
}
|
||||
|
||||
export const sliderPlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations; //: DecorationSet
|
||||
|
||||
constructor(view /* : EditorView */) {
|
||||
this.decorations = Decoration.set([]);
|
||||
}
|
||||
|
||||
update(update /* : ViewUpdate */) {
|
||||
update.transactions.forEach((tr) => {
|
||||
if (tr.docChanged) {
|
||||
this.decorations = this.decorations.map(tr.changes);
|
||||
const iterator = this.decorations.iter();
|
||||
while (iterator.value) {
|
||||
// when the widgets are moved, we need to tell the dom node the current position
|
||||
// this is important because the updateSliderValue function has to work with the dom node
|
||||
if (iterator.value?.widget?.slider) {
|
||||
iterator.value.widget.slider.from = iterator.from;
|
||||
iterator.value.widget.slider.to = iterator.to;
|
||||
}
|
||||
iterator.next();
|
||||
}
|
||||
}
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(setWidgets)) {
|
||||
this.decorations = Decoration.set(getWidgets(e.value, update.view));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: (v) => v.decorations,
|
||||
},
|
||||
);
|
||||
|
||||
export let slider = (value) => {
|
||||
console.warn('slider will only work when the transpiler is used... passing value as is');
|
||||
return pure(value);
|
||||
};
|
||||
// function transpiled from slider = (value, min, max)
|
||||
export let sliderWithID = (id, value, min, max) => {
|
||||
sliderValues[id] = value; // sync state at eval time (code -> state)
|
||||
return ref(() => sliderValues[id]); // use state at query time
|
||||
};
|
||||
// update state when sliders are moved
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('message', (e) => {
|
||||
if (e.data.type === 'cm-slider') {
|
||||
if (sliderValues[e.data.id] !== undefined) {
|
||||
// update state when slider is moved
|
||||
sliderValues[e.data.id] = e.data.value;
|
||||
} else {
|
||||
console.warn(`slider with id "${e.data.id}" is not registered. Only ${Object.keys(sliderValues)}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
import {
|
||||
abcdef,
|
||||
androidstudio,
|
||||
atomone,
|
||||
aura,
|
||||
bespin,
|
||||
darcula,
|
||||
dracula,
|
||||
duotoneDark,
|
||||
eclipse,
|
||||
githubDark,
|
||||
gruvboxDark,
|
||||
materialDark,
|
||||
nord,
|
||||
okaidia,
|
||||
solarizedDark,
|
||||
sublime,
|
||||
tokyoNight,
|
||||
tokyoNightStorm,
|
||||
vscodeDark,
|
||||
xcodeDark,
|
||||
bbedit,
|
||||
duotoneLight,
|
||||
githubLight,
|
||||
gruvboxLight,
|
||||
materialLight,
|
||||
noctisLilac,
|
||||
solarizedLight,
|
||||
tokyoNightDay,
|
||||
xcodeLight,
|
||||
} from '@uiw/codemirror-themes-all';
|
||||
|
||||
import strudelTheme from './themes/strudel-theme';
|
||||
import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen';
|
||||
import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen';
|
||||
import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen';
|
||||
import teletext, { settings as teletextSettings } from './themes/teletext';
|
||||
import algoboy, { settings as algoboySettings } from './themes/algoboy';
|
||||
import terminal, { settings as terminalSettings } from './themes/terminal';
|
||||
|
||||
export const themes = {
|
||||
strudelTheme,
|
||||
bluescreen,
|
||||
blackscreen,
|
||||
whitescreen,
|
||||
teletext,
|
||||
algoboy,
|
||||
terminal,
|
||||
abcdef,
|
||||
androidstudio,
|
||||
atomone,
|
||||
aura,
|
||||
bespin,
|
||||
darcula,
|
||||
dracula,
|
||||
duotoneDark,
|
||||
eclipse,
|
||||
githubDark,
|
||||
gruvboxDark,
|
||||
materialDark,
|
||||
nord,
|
||||
okaidia,
|
||||
solarizedDark,
|
||||
sublime,
|
||||
tokyoNight,
|
||||
tokyoNightStorm,
|
||||
vscodeDark,
|
||||
xcodeDark,
|
||||
bbedit,
|
||||
duotoneLight,
|
||||
githubLight,
|
||||
gruvboxLight,
|
||||
materialLight,
|
||||
noctisLilac,
|
||||
solarizedLight,
|
||||
tokyoNightDay,
|
||||
xcodeLight,
|
||||
};
|
||||
|
||||
// lineBackground is background with 50% opacity, to make sure the selection below is visible
|
||||
|
||||
export const settings = {
|
||||
strudelTheme: {
|
||||
background: '#222',
|
||||
lineBackground: '#22222299',
|
||||
foreground: '#fff',
|
||||
// foreground: '#75baff',
|
||||
caret: '#ffcc00',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#036dd626',
|
||||
// lineHighlight: '#8a91991a', // original
|
||||
lineHighlight: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
// gutterForeground: '#8a919966',
|
||||
gutterForeground: '#8a919966',
|
||||
},
|
||||
bluescreen: bluescreenSettings,
|
||||
blackscreen: blackscreenSettings,
|
||||
whitescreen: whitescreenSettings,
|
||||
teletext: teletextSettings,
|
||||
algoboy: algoboySettings,
|
||||
terminal: terminalSettings,
|
||||
abcdef: {
|
||||
background: '#0f0f0f',
|
||||
lineBackground: '#0f0f0f99',
|
||||
foreground: '#defdef',
|
||||
caret: '#00FF00',
|
||||
selection: '#515151',
|
||||
selectionMatch: '#515151',
|
||||
gutterBackground: '#555',
|
||||
gutterForeground: '#FFFFFF',
|
||||
lineHighlight: '#314151',
|
||||
},
|
||||
androidstudio: {
|
||||
background: '#282b2e',
|
||||
lineBackground: '#282b2e99',
|
||||
foreground: '#a9b7c6',
|
||||
caret: '#00FF00',
|
||||
selection: '#343739',
|
||||
selectionMatch: '#343739',
|
||||
lineHighlight: '#343739',
|
||||
},
|
||||
atomone: {
|
||||
background: '#272C35',
|
||||
lineBackground: '#272C3599',
|
||||
foreground: '#9d9b97',
|
||||
caret: '#797977',
|
||||
selection: '#ffffff30',
|
||||
selectionMatch: '#2B323D',
|
||||
gutterBackground: '#272C35',
|
||||
gutterForeground: '#465063',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#2B323D',
|
||||
},
|
||||
aura: {
|
||||
background: '#21202e',
|
||||
lineBackground: '#21202e99',
|
||||
foreground: '#edecee',
|
||||
caret: '#a277ff',
|
||||
selection: '#3d375e7f',
|
||||
selectionMatch: '#3d375e7f',
|
||||
gutterBackground: '#21202e',
|
||||
gutterForeground: '#edecee',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#a394f033',
|
||||
},
|
||||
bbedit: {
|
||||
light: true,
|
||||
background: '#FFFFFF',
|
||||
lineBackground: '#FFFFFF99',
|
||||
foreground: '#000000',
|
||||
caret: '#FBAC52',
|
||||
selection: '#FFD420',
|
||||
selectionMatch: '#FFD420',
|
||||
gutterBackground: '#f5f5f5',
|
||||
gutterForeground: '#4D4D4C',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#00000012',
|
||||
},
|
||||
bespin: {
|
||||
background: '#28211c',
|
||||
lineBackground: '#28211c99',
|
||||
foreground: '#9d9b97',
|
||||
caret: '#797977',
|
||||
selection: '#36312e',
|
||||
selectionMatch: '#4f382b',
|
||||
gutterBackground: '#28211c',
|
||||
gutterForeground: '#666666',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
darcula: {
|
||||
background: '#2B2B2B',
|
||||
lineBackground: '#2B2B2B99',
|
||||
foreground: '#f8f8f2',
|
||||
caret: '#FFFFFF',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||
gutterBackground: 'rgba(255, 255, 255, 0.1)',
|
||||
gutterForeground: '#999',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
dracula: {
|
||||
background: '#282a36',
|
||||
lineBackground: '#282a3699',
|
||||
foreground: '#f8f8f2',
|
||||
caret: '#f8f8f0',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||
gutterBackground: '#282a36',
|
||||
gutterForeground: '#6D8A88',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
duotoneLight: {
|
||||
light: true,
|
||||
background: '#faf8f5',
|
||||
lineBackground: '#faf8f599',
|
||||
foreground: '#b29762',
|
||||
caret: '#93abdc',
|
||||
selection: '#e3dcce',
|
||||
selectionMatch: '#e3dcce',
|
||||
gutterBackground: '#faf8f5',
|
||||
gutterForeground: '#cdc4b1',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#EFEFEF',
|
||||
},
|
||||
duotoneDark: {
|
||||
background: '#2a2734',
|
||||
lineBackground: '#2a273499',
|
||||
foreground: '#6c6783',
|
||||
caret: '#ffad5c',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
gutterBackground: '#2a2734',
|
||||
gutterForeground: '#545167',
|
||||
lineHighlight: '#36334280',
|
||||
},
|
||||
eclipse: {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#000',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#d7d4f0',
|
||||
selectionMatch: '#d7d4f0',
|
||||
gutterBackground: '#f7f7f7',
|
||||
gutterForeground: '#999',
|
||||
lineHighlight: '#e8f2ff',
|
||||
gutterBorder: 'transparent',
|
||||
},
|
||||
githubLight: {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#24292e',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#6e7781',
|
||||
},
|
||||
githubDark: {
|
||||
background: '#0d1117',
|
||||
lineBackground: '#0d111799',
|
||||
foreground: '#c9d1d9',
|
||||
caret: '#c9d1d9',
|
||||
selection: '#003d73',
|
||||
selectionMatch: '#003d73',
|
||||
lineHighlight: '#36334280',
|
||||
},
|
||||
gruvboxDark: {
|
||||
background: '#282828',
|
||||
lineBackground: '#28282899',
|
||||
foreground: '#ebdbb2',
|
||||
caret: '#ebdbb2',
|
||||
selection: '#bdae93',
|
||||
selectionMatch: '#bdae93',
|
||||
lineHighlight: '#3c3836',
|
||||
gutterBackground: '#282828',
|
||||
gutterForeground: '#7c6f64',
|
||||
},
|
||||
gruvboxLight: {
|
||||
light: true,
|
||||
background: '#fbf1c7',
|
||||
lineBackground: '#fbf1c799',
|
||||
foreground: '#3c3836',
|
||||
caret: '#af3a03',
|
||||
selection: '#ebdbb2',
|
||||
selectionMatch: '#bdae93',
|
||||
lineHighlight: '#ebdbb2',
|
||||
gutterBackground: '#ebdbb2',
|
||||
gutterForeground: '#665c54',
|
||||
gutterBorder: 'transparent',
|
||||
},
|
||||
materialDark: {
|
||||
background: '#2e3235',
|
||||
lineBackground: '#2e323599',
|
||||
foreground: '#bdbdbd',
|
||||
caret: '#a0a4ae',
|
||||
selection: '#d7d4f0',
|
||||
selectionMatch: '#d7d4f0',
|
||||
gutterBackground: '#2e3235',
|
||||
gutterForeground: '#999',
|
||||
gutterActiveForeground: '#4f5b66',
|
||||
lineHighlight: '#545b61',
|
||||
},
|
||||
materialLight: {
|
||||
light: true,
|
||||
background: '#FAFAFA',
|
||||
lineBackground: '#FAFAFA99',
|
||||
foreground: '#90A4AE',
|
||||
caret: '#272727',
|
||||
selection: '#80CBC440',
|
||||
selectionMatch: '#FAFAFA',
|
||||
gutterBackground: '#FAFAFA',
|
||||
gutterForeground: '#90A4AE',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#CCD7DA50',
|
||||
},
|
||||
noctisLilac: {
|
||||
light: true,
|
||||
background: '#f2f1f8',
|
||||
lineBackground: '#f2f1f899',
|
||||
foreground: '#0c006b',
|
||||
caret: '#5c49e9',
|
||||
selection: '#d5d1f2',
|
||||
selectionMatch: '#d5d1f2',
|
||||
gutterBackground: '#f2f1f8',
|
||||
gutterForeground: '#0c006b70',
|
||||
lineHighlight: '#e1def3',
|
||||
},
|
||||
nord: {
|
||||
background: '#2e3440',
|
||||
lineBackground: '#2e344099',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#3b4252',
|
||||
selectionMatch: '#e5e9f0',
|
||||
gutterBackground: '#2e3440',
|
||||
gutterForeground: '#4c566a',
|
||||
gutterActiveForeground: '#d8dee9',
|
||||
lineHighlight: '#4c566a',
|
||||
},
|
||||
okaidia: {
|
||||
background: '#272822',
|
||||
lineBackground: '#27282299',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#49483E',
|
||||
selectionMatch: '#49483E',
|
||||
gutterBackground: '#272822',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#00000059',
|
||||
},
|
||||
solarizedLight: {
|
||||
light: true,
|
||||
background: '#fdf6e3',
|
||||
lineBackground: '#fdf6e399',
|
||||
foreground: '#657b83',
|
||||
caret: '#586e75',
|
||||
selection: '#dfd9c8',
|
||||
selectionMatch: '#dfd9c8',
|
||||
gutterBackground: '#00000010',
|
||||
gutterForeground: '#657b83',
|
||||
lineHighlight: '#dfd9c8',
|
||||
},
|
||||
solarizedDark: {
|
||||
background: '#002b36',
|
||||
lineBackground: '#002b3699',
|
||||
foreground: '#93a1a1',
|
||||
caret: '#839496',
|
||||
selection: '#173541',
|
||||
selectionMatch: '#aafe661a',
|
||||
gutterBackground: '#00252f',
|
||||
gutterForeground: '#839496',
|
||||
lineHighlight: '#173541',
|
||||
},
|
||||
sublime: {
|
||||
background: '#303841',
|
||||
lineBackground: '#30384199',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FBAC52',
|
||||
selection: '#4C5964',
|
||||
selectionMatch: '#3A546E',
|
||||
gutterBackground: '#303841',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#00000059',
|
||||
},
|
||||
tokyoNightDay: {
|
||||
light: true,
|
||||
background: '#e1e2e7',
|
||||
lineBackground: '#e1e2e799',
|
||||
foreground: '#3760bf',
|
||||
caret: '#3760bf',
|
||||
selection: '#99a7df',
|
||||
selectionMatch: '#99a7df',
|
||||
gutterBackground: '#e1e2e7',
|
||||
gutterForeground: '#3760bf',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#5f5faf11',
|
||||
},
|
||||
tokyoNightStorm: {
|
||||
background: '#24283b',
|
||||
lineBackground: '#24283b99',
|
||||
foreground: '#7982a9',
|
||||
caret: '#c0caf5',
|
||||
selection: '#6f7bb630',
|
||||
selectionMatch: '#1f2335',
|
||||
gutterBackground: '#24283b',
|
||||
gutterForeground: '#7982a9',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#292e42',
|
||||
},
|
||||
tokyoNight: {
|
||||
background: '#1a1b26',
|
||||
lineBackground: '#1a1b2699',
|
||||
foreground: '#787c99',
|
||||
caret: '#c0caf5',
|
||||
selection: '#515c7e40',
|
||||
selectionMatch: '#16161e',
|
||||
gutterBackground: '#1a1b26',
|
||||
gutterForeground: '#787c99',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#1e202e',
|
||||
},
|
||||
vscodeDark: {
|
||||
background: '#1e1e1e',
|
||||
lineBackground: '#1e1e1e99',
|
||||
foreground: '#9cdcfe',
|
||||
caret: '#c6c6c6',
|
||||
selection: '#6199ff2f',
|
||||
selectionMatch: '#72a1ff59',
|
||||
lineHighlight: '#ffffff0f',
|
||||
gutterBackground: '#1e1e1e',
|
||||
gutterForeground: '#838383',
|
||||
gutterActiveForeground: '#fff',
|
||||
},
|
||||
xcodeLight: {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#3D3D3D',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#AFAFAF',
|
||||
lineHighlight: '#EDF4FF',
|
||||
},
|
||||
xcodeDark: {
|
||||
background: '#292A30',
|
||||
lineBackground: '#292A3099',
|
||||
foreground: '#CECFD0',
|
||||
caret: '#fff',
|
||||
selection: '#727377',
|
||||
selectionMatch: '#727377',
|
||||
lineHighlight: '#2F3239',
|
||||
},
|
||||
};
|
||||
|
||||
function getColors(str) {
|
||||
const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})/g;
|
||||
const colors = [];
|
||||
|
||||
let match;
|
||||
while ((match = colorRegex.exec(str)) !== null) {
|
||||
const color = match[0];
|
||||
if (!colors.includes(color)) {
|
||||
colors.push(color);
|
||||
}
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
export function themeColors(theme) {
|
||||
return getColors(stringifySafe(theme));
|
||||
}
|
||||
|
||||
function getCircularReplacer() {
|
||||
const seen = new WeakSet();
|
||||
return (key, value) => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
function stringifySafe(json) {
|
||||
return JSON.stringify(json, getCircularReplacer());
|
||||
}
|
||||
|
||||
export function injectStyle(rule) {
|
||||
const newStyle = document.createElement('style');
|
||||
document.head.appendChild(newStyle);
|
||||
const styleSheet = newStyle.sheet;
|
||||
const ruleIndex = styleSheet.insertRule(rule, 0);
|
||||
return () => styleSheet.deleteRule(ruleIndex);
|
||||
}
|
||||
|
||||
export const theme = (theme) => themes[theme] || themes.strudelTheme;
|
||||
@@ -0,0 +1,41 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: '#9bbc0f',
|
||||
foreground: '#0f380f', // whats that?
|
||||
caret: '#0f380f',
|
||||
selection: '#306230',
|
||||
selectionMatch: '#ffffff26',
|
||||
lineHighlight: '#8bac0f',
|
||||
lineBackground: '#9bbc0f50',
|
||||
//lineBackground: 'transparent',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#0f380f',
|
||||
light: true,
|
||||
customStyle: '.cm-line { line-height: 1 }',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#0f380f' },
|
||||
{ tag: t.operator, color: '#0f380f' },
|
||||
{ tag: t.special(t.variableName), color: '#0f380f' },
|
||||
{ tag: t.typeName, color: '#0f380f' },
|
||||
{ tag: t.atom, color: '#0f380f' },
|
||||
{ tag: t.number, color: '#0f380f' },
|
||||
{ tag: t.definition(t.variableName), color: '#0f380f' },
|
||||
{ tag: t.string, color: '#0f380f' },
|
||||
{ tag: t.special(t.string), color: '#0f380f' },
|
||||
{ tag: t.comment, color: '#0f380f' },
|
||||
{ tag: t.variableName, color: '#0f380f' },
|
||||
{ tag: t.tagName, color: '#0f380f' },
|
||||
{ tag: t.bracket, color: '#0f380f' },
|
||||
{ tag: t.meta, color: '#0f380f' },
|
||||
{ tag: t.attributeName, color: '#0f380f' },
|
||||
{ tag: t.propertyName, color: '#0f380f' },
|
||||
{ tag: t.className, color: '#0f380f' },
|
||||
{ tag: t.invalid, color: '#0f380f' },
|
||||
{ tag: [t.unit, t.punctuation], color: '#0f380f' },
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: 'black',
|
||||
foreground: 'white', // whats that?
|
||||
caret: 'white',
|
||||
selection: '#ffffff20',
|
||||
selectionMatch: '#036dd626',
|
||||
lineHighlight: '#ffffff10',
|
||||
lineBackground: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: 'white' },
|
||||
{ tag: t.operator, color: 'white' },
|
||||
{ tag: t.special(t.variableName), color: 'white' },
|
||||
{ tag: t.typeName, color: 'white' },
|
||||
{ tag: t.atom, color: 'white' },
|
||||
{ tag: t.number, color: 'white' },
|
||||
{ tag: t.definition(t.variableName), color: 'white' },
|
||||
{ tag: t.string, color: 'white' },
|
||||
{ tag: t.special(t.string), color: 'white' },
|
||||
{ tag: t.comment, color: 'white' },
|
||||
{ tag: t.variableName, color: 'white' },
|
||||
{ tag: t.tagName, color: 'white' },
|
||||
{ tag: t.bracket, color: 'white' },
|
||||
{ tag: t.meta, color: 'white' },
|
||||
{ tag: t.attributeName, color: 'white' },
|
||||
{ tag: t.propertyName, color: 'white' },
|
||||
{ tag: t.className, color: 'white' },
|
||||
{ tag: t.invalid, color: 'white' },
|
||||
{ tag: [t.unit, t.punctuation], color: 'white' },
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: '#051DB5',
|
||||
lineBackground: '#051DB550',
|
||||
foreground: 'white', // whats that?
|
||||
caret: 'white',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#036dd626',
|
||||
// lineHighlight: '#8a91991a', // original
|
||||
lineHighlight: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
// gutterForeground: '#8a919966',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: 'white' },
|
||||
{ tag: t.operator, color: 'white' },
|
||||
{ tag: t.special(t.variableName), color: 'white' },
|
||||
{ tag: t.typeName, color: 'white' },
|
||||
{ tag: t.atom, color: 'white' },
|
||||
{ tag: t.number, color: 'white' },
|
||||
{ tag: t.definition(t.variableName), color: 'white' },
|
||||
{ tag: t.string, color: 'white' },
|
||||
{ tag: t.special(t.string), color: 'white' },
|
||||
{ tag: t.comment, color: 'white' },
|
||||
{ tag: t.variableName, color: 'white' },
|
||||
{ tag: t.tagName, color: 'white' },
|
||||
{ tag: t.bracket, color: 'white' },
|
||||
{ tag: t.meta, color: 'white' },
|
||||
{ tag: t.attributeName, color: 'white' },
|
||||
{ tag: t.propertyName, color: 'white' },
|
||||
{ tag: t.className, color: 'white' },
|
||||
{ tag: t.invalid, color: 'white' },
|
||||
{ tag: [t.unit, t.punctuation], color: 'white' },
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#222',
|
||||
foreground: '#75baff', // whats that?
|
||||
caret: '#ffcc00',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#036dd626',
|
||||
// lineHighlight: '#8a91991a', // original
|
||||
lineHighlight: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
// gutterForeground: '#8a919966',
|
||||
gutterForeground: '#8a919966',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#c792ea' },
|
||||
{ tag: t.operator, color: '#89ddff' },
|
||||
{ tag: t.special(t.variableName), color: '#eeffff' },
|
||||
// { tag: t.typeName, color: '#f07178' }, // original
|
||||
{ tag: t.typeName, color: '#c3e88d' },
|
||||
{ tag: t.atom, color: '#f78c6c' },
|
||||
// { tag: t.number, color: '#ff5370' }, // original
|
||||
{ tag: t.number, color: '#c3e88d' },
|
||||
{ tag: t.definition(t.variableName), color: '#82aaff' },
|
||||
{ tag: t.string, color: '#c3e88d' },
|
||||
// { tag: t.special(t.string), color: '#f07178' }, // original
|
||||
{ tag: t.special(t.string), color: '#c3e88d' },
|
||||
{ tag: t.comment, color: '#7d8799' },
|
||||
// { tag: t.variableName, color: '#f07178' }, // original
|
||||
{ tag: t.variableName, color: '#c792ea' },
|
||||
// { tag: t.tagName, color: '#ff5370' }, // original
|
||||
{ tag: t.tagName, color: '#c3e88d' },
|
||||
{ tag: t.bracket, color: '#525154' },
|
||||
// { tag: t.bracket, color: '#a2a1a4' }, // original
|
||||
{ tag: t.meta, color: '#ffcb6b' },
|
||||
{ tag: t.attributeName, color: '#c792ea' },
|
||||
{ tag: t.propertyName, color: '#c792ea' },
|
||||
|
||||
{ tag: t.className, color: '#decb6b' },
|
||||
{ tag: t.invalid, color: '#ffffff' },
|
||||
{ tag: [t.unit, t.punctuation], color: '#82aaff' },
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
|
||||
let colorA = '#6edee4';
|
||||
//let colorB = 'magenta';
|
||||
let colorB = 'white';
|
||||
let colorC = 'red';
|
||||
let colorD = '#f8fc55';
|
||||
|
||||
export const settings = {
|
||||
background: '#000000',
|
||||
foreground: colorA, // whats that?
|
||||
caret: colorC,
|
||||
selection: colorD,
|
||||
selectionMatch: colorA,
|
||||
lineHighlight: '#6edee440', // panel bg
|
||||
lineBackground: '#00000040',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
customStyle: '.cm-line { line-height: 1 }',
|
||||
};
|
||||
|
||||
let punctuation = colorD;
|
||||
let mini = colorB;
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: colorA },
|
||||
{ tag: t.operator, color: mini },
|
||||
{ tag: t.special(t.variableName), color: colorA },
|
||||
{ tag: t.typeName, color: colorA },
|
||||
{ tag: t.atom, color: colorA },
|
||||
{ tag: t.number, color: mini },
|
||||
{ tag: t.definition(t.variableName), color: colorA },
|
||||
{ tag: t.string, color: mini },
|
||||
{ tag: t.special(t.string), color: mini },
|
||||
{ tag: t.comment, color: punctuation },
|
||||
{ tag: t.variableName, color: colorA },
|
||||
{ tag: t.tagName, color: colorA },
|
||||
{ tag: t.bracket, color: punctuation },
|
||||
{ tag: t.meta, color: colorA },
|
||||
{ tag: t.attributeName, color: colorA },
|
||||
{ tag: t.propertyName, color: colorA }, // methods
|
||||
{ tag: t.className, color: colorA },
|
||||
{ tag: t.invalid, color: colorC },
|
||||
{ tag: [t.unit, t.punctuation], color: punctuation },
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: 'black',
|
||||
foreground: '#41FF00', // whats that?
|
||||
caret: '#41FF00',
|
||||
selection: '#ffffff20',
|
||||
selectionMatch: '#036dd626',
|
||||
lineHighlight: '#ffffff10',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#41FF00' },
|
||||
{ tag: t.operator, color: '#41FF00' },
|
||||
{ tag: t.special(t.variableName), color: '#41FF00' },
|
||||
{ tag: t.typeName, color: '#41FF00' },
|
||||
{ tag: t.atom, color: '#41FF00' },
|
||||
{ tag: t.number, color: '#41FF00' },
|
||||
{ tag: t.definition(t.variableName), color: '#41FF00' },
|
||||
{ tag: t.string, color: '#41FF00' },
|
||||
{ tag: t.special(t.string), color: '#41FF00' },
|
||||
{ tag: t.comment, color: '#41FF00' },
|
||||
{ tag: t.variableName, color: '#41FF00' },
|
||||
{ tag: t.tagName, color: '#41FF00' },
|
||||
{ tag: t.bracket, color: '#41FF00' },
|
||||
{ tag: t.meta, color: '#41FF00' },
|
||||
{ tag: t.attributeName, color: '#41FF00' },
|
||||
{ tag: t.propertyName, color: '#41FF00' },
|
||||
{ tag: t.className, color: '#41FF00' },
|
||||
{ tag: t.invalid, color: '#41FF00' },
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: 'white',
|
||||
foreground: 'black', // whats that?
|
||||
caret: 'black',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#ffffff26',
|
||||
lineHighlight: '#cccccc50',
|
||||
lineBackground: '#ffffff50',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: 'black',
|
||||
light: true,
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: 'black' },
|
||||
{ tag: t.operator, color: 'black' },
|
||||
{ tag: t.special(t.variableName), color: 'black' },
|
||||
{ tag: t.typeName, color: 'black' },
|
||||
{ tag: t.atom, color: 'black' },
|
||||
{ tag: t.number, color: 'black' },
|
||||
{ tag: t.definition(t.variableName), color: 'black' },
|
||||
{ tag: t.string, color: 'black' },
|
||||
{ tag: t.special(t.string), color: 'black' },
|
||||
{ tag: t.comment, color: 'black' },
|
||||
{ tag: t.variableName, color: 'black' },
|
||||
{ tag: t.tagName, color: 'black' },
|
||||
{ tag: t.bracket, color: 'black' },
|
||||
{ tag: t.meta, color: 'black' },
|
||||
{ tag: t.attributeName, color: 'black' },
|
||||
{ tag: t.propertyName, color: 'black' },
|
||||
{ tag: t.className, color: 'black' },
|
||||
{ tag: t.invalid, color: 'black' },
|
||||
],
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { controls, Pattern, getDrawContext, silence, register, pure } from './index.mjs';
|
||||
import { Pattern, getDrawContext, silence, register, pure } from './index.mjs';
|
||||
import controls from './controls.mjs'; // do not import from index.mjs as it breaks for some reason..
|
||||
const { createParams } = controls;
|
||||
|
||||
let clearColor = '#22222210';
|
||||
|
||||
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
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';
|
||||
import { Pattern, register, sequence } from './pattern.mjs';
|
||||
import { zipWith } from './util.mjs';
|
||||
|
||||
const controls = {};
|
||||
@@ -86,6 +86,16 @@ const generic_params = [
|
||||
*
|
||||
*/
|
||||
['gain'],
|
||||
/**
|
||||
* Gain applied after all effects have been processed.
|
||||
*
|
||||
* @name postgain
|
||||
* @example
|
||||
* s("bd sd,hh*4")
|
||||
* .compressor("-20:20:10:.002:.02").postgain(1.5)
|
||||
*
|
||||
*/
|
||||
['postgain'],
|
||||
/**
|
||||
* Like {@link gain}, but linear.
|
||||
*
|
||||
@@ -109,6 +119,97 @@ const generic_params = [
|
||||
*/
|
||||
['attack', 'att'],
|
||||
|
||||
/**
|
||||
* Sets the Frequency Modulation Harmonicity Ratio.
|
||||
* Controls the timbre of the sound.
|
||||
* Whole numbers and simple ratios sound more natural,
|
||||
* while decimal numbers and complex ratios sound metallic.
|
||||
*
|
||||
* @name fmh
|
||||
* @param {number | Pattern} harmonicity
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* .fm(4)
|
||||
* .fmh("<1 2 1.5 1.61>")
|
||||
* .scope()
|
||||
*
|
||||
*/
|
||||
[['fmh', 'fmi'], 'fmh'],
|
||||
/**
|
||||
* Sets the Frequency Modulation of the synth.
|
||||
* Controls the modulation index, which defines the brightness of the sound.
|
||||
*
|
||||
* @name fm
|
||||
* @param {number | Pattern} brightness modulation index
|
||||
* @synonyms fmi
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* .fm("<0 1 2 8 32>")
|
||||
* .scope()
|
||||
*
|
||||
*/
|
||||
[['fmi', 'fmh'], 'fm'],
|
||||
// fm envelope
|
||||
/**
|
||||
* Ramp type of fm envelope. Exp might be a bit broken..
|
||||
*
|
||||
* @name fmenv
|
||||
* @param {number | Pattern} type lin | exp
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* .fm(4)
|
||||
* .fmdecay(.2)
|
||||
* .fmsustain(0)
|
||||
* .fmenv("<exp lin>")
|
||||
* .scope()
|
||||
*
|
||||
*/
|
||||
['fmenv'],
|
||||
/**
|
||||
* Attack time for the FM envelope: time it takes to reach maximum modulation
|
||||
*
|
||||
* @name fmattack
|
||||
* @param {number | Pattern} time attack time
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* .fm(4)
|
||||
* .fmattack("<0 .05 .1 .2>")
|
||||
* .scope()
|
||||
*
|
||||
*/
|
||||
['fmattack'],
|
||||
/**
|
||||
* Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase.
|
||||
*
|
||||
* @name fmdecay
|
||||
* @param {number | Pattern} time decay time
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* .fm(4)
|
||||
* .fmdecay("<.01 .05 .1 .2>")
|
||||
* .fmsustain(.4)
|
||||
* .scope()
|
||||
*
|
||||
*/
|
||||
['fmdecay'],
|
||||
/**
|
||||
* Sustain level for the FM envelope: how much modulation is applied after the decay phase
|
||||
*
|
||||
* @name fmsustain
|
||||
* @param {number | Pattern} level sustain level
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* .fm(4)
|
||||
* .fmdecay(.1)
|
||||
* .fmsustain("<1 .75 .5 0>")
|
||||
* .scope()
|
||||
*
|
||||
*/
|
||||
['fmsustain'],
|
||||
// these are not really useful... skipping for now
|
||||
['fmrelease'],
|
||||
['fmvelocity'],
|
||||
|
||||
/**
|
||||
* Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`.
|
||||
*
|
||||
@@ -120,6 +221,9 @@ const generic_params = [
|
||||
*/
|
||||
['bank'],
|
||||
|
||||
['analyze'], // analyser node send amount 0 - 1 (used by scope)
|
||||
['fft'], // fftSize of analyser
|
||||
|
||||
/**
|
||||
* Amplitude envelope decay time: the time it takes after the attack time to reach the sustain level.
|
||||
* Note that the decay is only audible if the sustain value is lower than 1.
|
||||
@@ -205,29 +309,43 @@ const generic_params = [
|
||||
*/
|
||||
['end'],
|
||||
/**
|
||||
* Loops the sample (from `begin` to `end`) the specified number of times.
|
||||
* Loops the sample.
|
||||
* Note that the tempo of the loop is not synced with the cycle tempo.
|
||||
* To change the loop region, use loopBegin / loopEnd.
|
||||
*
|
||||
* @name loop
|
||||
* @param {number | Pattern} times How often the sample is looped
|
||||
* @param {number | Pattern} on If 1, the sample is looped
|
||||
* @example
|
||||
* s("bd").loop("<1 2 3 4>").osc()
|
||||
* s("casio").loop(1)
|
||||
*
|
||||
*/
|
||||
['loop'],
|
||||
// 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.
|
||||
* Begin to loop at a specific point in the sample (inbetween `begin` and `end`).
|
||||
* Note that the loop point must be inbetween `begin` and `end`, and before `loopEnd`!
|
||||
* Note: Samples starting with wt_ will automatically loop! (wt = wavetable)
|
||||
*
|
||||
* @name legato
|
||||
* @param {number | Pattern} duration between 0 and 1, where 1 is the length of the whole hap time
|
||||
* @name loopBegin
|
||||
* @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample
|
||||
* @synonyms loopb
|
||||
* @example
|
||||
* "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>")
|
||||
*
|
||||
* s("space").loop(1)
|
||||
* .loopBegin("<0 .125 .25>").scope()
|
||||
*/
|
||||
// ['legato'],
|
||||
// ['clhatdecay'],
|
||||
['loopBegin', 'loopb'],
|
||||
/**
|
||||
*
|
||||
* End the looping section at a specific point in the sample (inbetween `begin` and `end`).
|
||||
* Note that the loop point must be inbetween `begin` and `end`, and after `loopBegin`!
|
||||
*
|
||||
* @name loopEnd
|
||||
* @param {number | Pattern} time between 0 and 1, where 1 is the length of the sample
|
||||
* @synonyms loope
|
||||
* @example
|
||||
* s("space").loop(1)
|
||||
* .loopEnd("<1 .75 .5 .25>").scope()
|
||||
*/
|
||||
['loopEnd', 'loope'],
|
||||
/**
|
||||
* bit crusher effect.
|
||||
*
|
||||
@@ -237,6 +355,20 @@ const generic_params = [
|
||||
* s("<bd sd>,hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>")
|
||||
*
|
||||
*/
|
||||
// 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
|
||||
* @noAutocomplete
|
||||
* @example
|
||||
* "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>")
|
||||
*
|
||||
*/
|
||||
// ['legato'],
|
||||
// ['clhatdecay'],
|
||||
['crush'],
|
||||
/**
|
||||
* fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers
|
||||
@@ -249,6 +381,61 @@ const generic_params = [
|
||||
*/
|
||||
['coarse'],
|
||||
|
||||
['phaserrate', 'phasr'], // superdirt only
|
||||
|
||||
/**
|
||||
* Phaser audio effect that approximates popular guitar pedals.
|
||||
*
|
||||
* @name phaser
|
||||
* @synonyms ph
|
||||
* @param {number | Pattern} speed speed of modulation
|
||||
* @example
|
||||
* n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
|
||||
* .phaser("<1 2 4 8>")
|
||||
*
|
||||
*/
|
||||
[['phaser', 'phaserdepth', 'phasercenter', 'phasersweep'], 'ph'],
|
||||
|
||||
/**
|
||||
* The frequency sweep range of the lfo for the phaser effect. Defaults to 2000
|
||||
*
|
||||
* @name phasersweep
|
||||
* @synonyms phs
|
||||
* @param {number | Pattern} phasersweep most useful values are between 0 and 4000
|
||||
* @example
|
||||
* n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
|
||||
* .phaser(2).phasersweep("<800 2000 4000>")
|
||||
*
|
||||
*/
|
||||
['phasersweep', 'phs'],
|
||||
|
||||
/**
|
||||
* The center frequency of the phaser in HZ. Defaults to 1000
|
||||
*
|
||||
* @name phasercenter
|
||||
* @synonyms phc
|
||||
* @param {number | Pattern} centerfrequency in HZ
|
||||
* @example
|
||||
* n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
|
||||
* .phaser(2).phasercenter("<800 2000 4000>")
|
||||
*
|
||||
*/
|
||||
|
||||
['phasercenter', 'phc'],
|
||||
|
||||
/**
|
||||
* The amount the signal is affected by the phaser effect. Defaults to 0.75
|
||||
*
|
||||
* @name phaserdepth
|
||||
* @synonyms phd
|
||||
* @param {number | Pattern} depth number between 0 and 1
|
||||
* @example
|
||||
* n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
|
||||
* .phaser(2).phaserdepth("<0 .5 .75 1>")
|
||||
*
|
||||
*/
|
||||
['phaserdepth', 'phd', 'phasdp'], // also a superdirt control
|
||||
|
||||
/**
|
||||
* choose the channel the pattern is sent to in superdirt
|
||||
*
|
||||
@@ -282,6 +469,227 @@ const generic_params = [
|
||||
*
|
||||
*/
|
||||
[['cutoff', 'resonance'], 'ctf', 'lpf', 'lp'],
|
||||
|
||||
/**
|
||||
* Sets the lowpass filter envelope modulation depth.
|
||||
* @name lpenv
|
||||
* @param {number | Pattern} modulation depth of the lowpass filter envelope between 0 and _n_
|
||||
* @synonyms lpe
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .lpf(500)
|
||||
* .lpa(.5)
|
||||
* .lpenv("<4 2 1 0 -1 -2 -4>/4")
|
||||
*/
|
||||
['lpenv', 'lpe'],
|
||||
/**
|
||||
* Sets the highpass filter envelope modulation depth.
|
||||
* @name hpenv
|
||||
* @param {number | Pattern} modulation depth of the highpass filter envelope between 0 and _n_
|
||||
* @synonyms hpe
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .hpf(500)
|
||||
* .hpa(.5)
|
||||
* .hpenv("<4 2 1 0 -1 -2 -4>/4")
|
||||
*/
|
||||
['hpenv', 'hpe'],
|
||||
/**
|
||||
* Sets the bandpass filter envelope modulation depth.
|
||||
* @name bpenv
|
||||
* @param {number | Pattern} modulation depth of the bandpass filter envelope between 0 and _n_
|
||||
* @synonyms bpe
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .bpf(500)
|
||||
* .bpa(.5)
|
||||
* .bpenv("<4 2 1 0 -1 -2 -4>/4")
|
||||
*/
|
||||
['bpenv', 'bpe'],
|
||||
/**
|
||||
* Sets the attack duration for the lowpass filter envelope.
|
||||
* @name lpattack
|
||||
* @param {number | Pattern} attack time of the filter envelope
|
||||
* @synonyms lpa
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .lpf(500)
|
||||
* .lpa("<.5 .25 .1 .01>/4")
|
||||
* .lpenv(4)
|
||||
*/
|
||||
['lpattack', 'lpa'],
|
||||
/**
|
||||
* Sets the attack duration for the highpass filter envelope.
|
||||
* @name hpattack
|
||||
* @param {number | Pattern} attack time of the highpass filter envelope
|
||||
* @synonyms hpa
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .hpf(500)
|
||||
* .hpa("<.5 .25 .1 .01>/4")
|
||||
* .hpenv(4)
|
||||
*/
|
||||
['hpattack', 'hpa'],
|
||||
/**
|
||||
* Sets the attack duration for the bandpass filter envelope.
|
||||
* @name bpattack
|
||||
* @param {number | Pattern} attack time of the bandpass filter envelope
|
||||
* @synonyms bpa
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .bpf(500)
|
||||
* .bpa("<.5 .25 .1 .01>/4")
|
||||
* .bpenv(4)
|
||||
*/
|
||||
['bpattack', 'bpa'],
|
||||
/**
|
||||
* Sets the decay duration for the lowpass filter envelope.
|
||||
* @name lpdecay
|
||||
* @param {number | Pattern} decay time of the filter envelope
|
||||
* @synonyms lpd
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .lpf(500)
|
||||
* .lpd("<.5 .25 .1 0>/4")
|
||||
* .lps(0.2)
|
||||
* .lpenv(4)
|
||||
*/
|
||||
['lpdecay', 'lpd'],
|
||||
/**
|
||||
* Sets the decay duration for the highpass filter envelope.
|
||||
* @name hpdecay
|
||||
* @param {number | Pattern} decay time of the highpass filter envelope
|
||||
* @synonyms hpd
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .hpf(500)
|
||||
* .hpd("<.5 .25 .1 0>/4")
|
||||
* .hps(0.2)
|
||||
* .hpenv(4)
|
||||
*/
|
||||
['hpdecay', 'hpd'],
|
||||
/**
|
||||
* Sets the decay duration for the bandpass filter envelope.
|
||||
* @name bpdecay
|
||||
* @param {number | Pattern} decay time of the bandpass filter envelope
|
||||
* @synonyms bpd
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .bpf(500)
|
||||
* .bpd("<.5 .25 .1 0>/4")
|
||||
* .bps(0.2)
|
||||
* .bpenv(4)
|
||||
*/
|
||||
['bpdecay', 'bpd'],
|
||||
/**
|
||||
* Sets the sustain amplitude for the lowpass filter envelope.
|
||||
* @name lpsustain
|
||||
* @param {number | Pattern} sustain amplitude of the lowpass filter envelope
|
||||
* @synonyms lps
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .lpf(500)
|
||||
* .lpd(.5)
|
||||
* .lps("<0 .25 .5 1>/4")
|
||||
* .lpenv(4)
|
||||
*/
|
||||
['lpsustain', 'lps'],
|
||||
/**
|
||||
* Sets the sustain amplitude for the highpass filter envelope.
|
||||
* @name hpsustain
|
||||
* @param {number | Pattern} sustain amplitude of the highpass filter envelope
|
||||
* @synonyms hps
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .hpf(500)
|
||||
* .hpd(.5)
|
||||
* .hps("<0 .25 .5 1>/4")
|
||||
* .hpenv(4)
|
||||
*/
|
||||
['hpsustain', 'hps'],
|
||||
/**
|
||||
* Sets the sustain amplitude for the bandpass filter envelope.
|
||||
* @name bpsustain
|
||||
* @param {number | Pattern} sustain amplitude of the bandpass filter envelope
|
||||
* @synonyms bps
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .bpf(500)
|
||||
* .bpd(.5)
|
||||
* .bps("<0 .25 .5 1>/4")
|
||||
* .bpenv(4)
|
||||
*/
|
||||
['bpsustain', 'bps'],
|
||||
/**
|
||||
* Sets the release time for the lowpass filter envelope.
|
||||
* @name lprelease
|
||||
* @param {number | Pattern} release time of the filter envelope
|
||||
* @synonyms lpr
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .clip(.5)
|
||||
* .lpf(500)
|
||||
* .lpenv(4)
|
||||
* .lpr("<.5 .25 .1 0>/4")
|
||||
* .release(.5)
|
||||
*/
|
||||
['lprelease', 'lpr'],
|
||||
/**
|
||||
* Sets the release time for the highpass filter envelope.
|
||||
* @name hprelease
|
||||
* @param {number | Pattern} release time of the highpass filter envelope
|
||||
* @synonyms hpr
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .clip(.5)
|
||||
* .hpf(500)
|
||||
* .hpenv(4)
|
||||
* .hpr("<.5 .25 .1 0>/4")
|
||||
* .release(.5)
|
||||
*/
|
||||
['hprelease', 'hpr'],
|
||||
/**
|
||||
* Sets the release time for the bandpass filter envelope.
|
||||
* @name bprelease
|
||||
* @param {number | Pattern} release time of the bandpass filter envelope
|
||||
* @synonyms bpr
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .clip(.5)
|
||||
* .bpf(500)
|
||||
* .bpenv(4)
|
||||
* .bpr("<.5 .25 .1 0>/4")
|
||||
* .release(.5)
|
||||
*/
|
||||
['bprelease', 'bpr'],
|
||||
/**
|
||||
* Sets the filter type. The 24db filter is more aggressive. More types might be added in the future.
|
||||
* @name ftype
|
||||
* @param {number | Pattern} type 12db (default) or 24db
|
||||
* @example
|
||||
* note("<c2 e2 f2 g2>")
|
||||
* .sound('sawtooth')
|
||||
* .lpf(500)
|
||||
* .bpenv(4)
|
||||
* .ftype("<12db 24db>")
|
||||
*/
|
||||
['ftype'],
|
||||
['fanchor'],
|
||||
/**
|
||||
* Applies the cutoff frequency of the **h**igh-**p**ass **f**ilter.
|
||||
*
|
||||
@@ -298,6 +706,45 @@ const generic_params = [
|
||||
*/
|
||||
// currently an alias of 'hcutoff' https://github.com/tidalcycles/strudel/issues/496
|
||||
// ['hpf'],
|
||||
/**
|
||||
* Applies a vibrato to the frequency of the oscillator.
|
||||
*
|
||||
* @name vib
|
||||
* @synonyms vibrato, v
|
||||
* @param {number | Pattern} frequency of the vibrato in hertz
|
||||
* @example
|
||||
* note("a")
|
||||
* .vib("<.5 1 2 4 8 16>")
|
||||
* @example
|
||||
* // change the modulation depth with ":"
|
||||
* note("a")
|
||||
* .vib("<.5 1 2 4 8 16>:12")
|
||||
*/
|
||||
[['vib', 'vibmod'], 'vibrato', 'v'],
|
||||
/**
|
||||
* Adds pink noise to the mix
|
||||
*
|
||||
* @name noise
|
||||
* @param {number | Pattern} wet wet amount
|
||||
* @example
|
||||
* sound("<white pink brown>/2")
|
||||
*/
|
||||
['noise'],
|
||||
/**
|
||||
* Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set
|
||||
*
|
||||
* @name vibmod
|
||||
* @synonyms vmod
|
||||
* @param {number | Pattern} depth of vibrato (in semitones)
|
||||
* @example
|
||||
* note("a").vib(4)
|
||||
* .vibmod("<.25 .5 1 2 12>")
|
||||
* @example
|
||||
* // change the vibrato frequency with ":"
|
||||
* note("a")
|
||||
* .vibmod("<.25 .5 1 2 12>:8")
|
||||
*/
|
||||
[['vibmod', 'vib'], 'vmod'],
|
||||
[['hcutoff', 'hresonance'], 'hpf', 'hp'],
|
||||
/**
|
||||
* Controls the **h**igh-**p**ass **q**-value.
|
||||
@@ -476,6 +923,14 @@ const generic_params = [
|
||||
*
|
||||
*/
|
||||
['lsize'],
|
||||
/**
|
||||
* Sets the displayed text for an event on the pianoroll
|
||||
*
|
||||
* @name label
|
||||
* @param {string} label text to display
|
||||
*/
|
||||
['activeLabel'],
|
||||
[['label', 'activeLabel']],
|
||||
// ['lfo'],
|
||||
// ['lfocutoffint'],
|
||||
// ['lfodelay'],
|
||||
@@ -508,7 +963,7 @@ const generic_params = [
|
||||
* @superDirtOnly
|
||||
*/
|
||||
['octave'],
|
||||
['offset'], // TODO: what is this? not found in tidal doc
|
||||
|
||||
// ['ophatdecay'],
|
||||
// TODO: example
|
||||
/**
|
||||
@@ -572,6 +1027,15 @@ const generic_params = [
|
||||
// TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano
|
||||
// ['velocity'],
|
||||
['voice'], // TODO: synth param
|
||||
|
||||
// voicings // https://github.com/tidalcycles/strudel/issues/506
|
||||
['chord'], // chord to voice, like C Eb Fm7 G7. the symbols can be defined via addVoicings
|
||||
['dictionary', 'dict'], // which dictionary to use for the voicings
|
||||
['anchor'], // the top note to align the voicing to, defaults to c5
|
||||
['offset'], // how the voicing is offset from the anchored position
|
||||
['octaves'], // how many octaves are voicing steps spread apart, defaults to 1
|
||||
[['mode', 'anchor']], // below = anchor note will be removed from the voicing, useful for melody harmonization
|
||||
|
||||
/**
|
||||
* Sets the level of reverb.
|
||||
*
|
||||
@@ -586,20 +1050,74 @@ const generic_params = [
|
||||
*
|
||||
*/
|
||||
[['room', 'size']],
|
||||
/**
|
||||
* Reverb lowpass starting frequency (in hertz).
|
||||
* When this property is changed, the reverb will be recaculated, so only change this sparsely..
|
||||
*
|
||||
* @name roomlp
|
||||
* @synonyms rlp
|
||||
* @param {number} frequency between 0 and 20000hz
|
||||
* @example
|
||||
* s("bd sd").room(0.5).rlp(10000)
|
||||
* @example
|
||||
* s("bd sd").room(0.5).rlp(5000)
|
||||
*/
|
||||
['roomlp', 'rlp'],
|
||||
/**
|
||||
* Reverb lowpass frequency at -60dB (in hertz).
|
||||
* When this property is changed, the reverb will be recaculated, so only change this sparsely..
|
||||
*
|
||||
* @name roomdim
|
||||
* @synonyms rdim
|
||||
* @param {number} frequency between 0 and 20000hz
|
||||
* @example
|
||||
* s("bd sd").room(0.5).rlp(10000).rdim(8000)
|
||||
* @example
|
||||
* s("bd sd").room(0.5).rlp(5000).rdim(400)
|
||||
*
|
||||
*/
|
||||
['roomdim', 'rdim'],
|
||||
/**
|
||||
* Reverb fade time (in seconds).
|
||||
* When this property is changed, the reverb will be recaculated, so only change this sparsely..
|
||||
*
|
||||
* @name roomfade
|
||||
* @synonyms rfade
|
||||
* @param {number} seconds for the reverb to fade
|
||||
* @example
|
||||
* s("bd sd").room(0.5).rlp(10000).rfade(0.5)
|
||||
* @example
|
||||
* s("bd sd").room(0.5).rlp(5000).rfade(4)
|
||||
*
|
||||
*/
|
||||
['roomfade', 'rfade'],
|
||||
/**
|
||||
* Sets the sample to use as an impulse response for the reverb.
|
||||
* @name iresponse
|
||||
* @param {string | Pattern} sample to use as an impulse response
|
||||
* @synonyms ir
|
||||
* @example
|
||||
* s("bd sd").room(.8).ir("<shaker_large:0 shaker_large:2>")
|
||||
*
|
||||
*/
|
||||
[['ir', 'i'], 'iresponse'],
|
||||
/**
|
||||
* Sets the room size of the reverb, see {@link room}.
|
||||
* When this property is changed, the reverb will be recaculated, so only change this sparsely..
|
||||
*
|
||||
* @name roomsize
|
||||
* @param {number | Pattern} size between 0 and 10
|
||||
* @synonyms size, sz
|
||||
* @synonyms rsize, sz, size
|
||||
* @example
|
||||
* s("bd sd").room(.8).roomsize("<0 1 2 4 8>")
|
||||
* s("bd sd").room(.8).rsize(1)
|
||||
* @example
|
||||
* s("bd sd").room(.8).rsize(4)
|
||||
*
|
||||
*/
|
||||
// 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?
|
||||
['size', 'sz', 'roomsize'],
|
||||
['roomsize', 'size', 'sz', 'rsize'],
|
||||
// ['sagogo'],
|
||||
// ['sclap'],
|
||||
// ['sclaves'],
|
||||
@@ -614,6 +1132,21 @@ const generic_params = [
|
||||
*
|
||||
*/
|
||||
['shape'],
|
||||
/**
|
||||
* Dynamics Compressor. The params are `compressor("threshold:ratio:knee:attack:release")`
|
||||
* More info [here](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties)
|
||||
*
|
||||
* @name compressor
|
||||
* @example
|
||||
* s("bd sd,hh*4")
|
||||
* .compressor("-20:20:10:.002:.02")
|
||||
*
|
||||
*/
|
||||
[['compressor', 'compressorRatio', 'compressorKnee', 'compressorAttack', 'compressorRelease']],
|
||||
['compressorKnee'],
|
||||
['compressorRatio'],
|
||||
['compressorAttack'],
|
||||
['compressorRelease'],
|
||||
/**
|
||||
* Changes the speed of sample playback, i.e. a cheap way of changing pitch.
|
||||
*
|
||||
@@ -698,9 +1231,6 @@ const generic_params = [
|
||||
*/
|
||||
['tremolodepth', 'tremdp'],
|
||||
['tremolorate', 'tremr'],
|
||||
// TODO: doesn't seem to do anything
|
||||
['phaserdepth', 'phasdp'],
|
||||
['phaserrate', 'phasr'],
|
||||
|
||||
['fshift'],
|
||||
['fshiftnote'],
|
||||
@@ -749,18 +1279,33 @@ const generic_params = [
|
||||
['val'],
|
||||
['cps'],
|
||||
/**
|
||||
* If set to 1, samples will be cut to the duration of their event.
|
||||
* In tidal, this would be done with legato, which [is about to land in strudel too](https://github.com/tidalcycles/strudel/issues/111)
|
||||
* Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration.
|
||||
* In tidal, this would be done with legato, [which has a complicated history in strudel](https://github.com/tidalcycles/strudel/issues/111).
|
||||
* For now, if you're coming from tidal, just think clip = legato.
|
||||
*
|
||||
* @name clip
|
||||
* @param {number | Pattern} active 1 or 0
|
||||
* @param {number | Pattern} factor >= 0
|
||||
* @example
|
||||
* note("c a f e ~").s("piano").clip(1)
|
||||
* note("c a f e").s("piano").clip("<.5 1 2>")
|
||||
*
|
||||
*/
|
||||
['clip'],
|
||||
];
|
||||
|
||||
// ZZFX
|
||||
['zrand'],
|
||||
['curve'],
|
||||
['slide'], // superdirt duplicate
|
||||
['deltaSlide'],
|
||||
['pitchJump'],
|
||||
['pitchJumpTime'],
|
||||
['lfo', 'repeatTime'],
|
||||
['znoise'], // noise on the frequency or as bubo calls it "frequency fog" :)
|
||||
['zmod'],
|
||||
['zcrush'], // like crush but scaled differently
|
||||
['zdelay'],
|
||||
['tremolo'],
|
||||
['zzfx'],
|
||||
];
|
||||
// TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13
|
||||
|
||||
controls.createParam = function (names) {
|
||||
@@ -810,4 +1355,26 @@ generic_params.forEach(([names, ...aliases]) => {
|
||||
controls.createParams = (...names) =>
|
||||
names.reduce((acc, name) => Object.assign(acc, { [name]: controls.createParam(name) }), {});
|
||||
|
||||
/**
|
||||
* ADSR envelope: Combination of Attack, Decay, Sustain, and Release.
|
||||
*
|
||||
* @name adsr
|
||||
* @param {number | Pattern} time attack time in seconds
|
||||
* @param {number | Pattern} time decay time in seconds
|
||||
* @param {number | Pattern} gain sustain level (0 to 1)
|
||||
* @param {number | Pattern} time release time in seconds
|
||||
* @example
|
||||
* note("<c3 bb2 f3 eb3>").sound("sawtooth").lpf(600).adsr(".1:.1:.5:.2")
|
||||
*/
|
||||
controls.adsr = register('adsr', (adsr, pat) => {
|
||||
adsr = !Array.isArray(adsr) ? [adsr] : adsr;
|
||||
const [attack, decay, sustain, release] = adsr;
|
||||
return pat.set({ attack, decay, sustain, release });
|
||||
});
|
||||
controls.ds = register('ds', (ds, pat) => {
|
||||
ds = !Array.isArray(ds) ? [ds] : ds;
|
||||
const [decay, sustain] = ds;
|
||||
return pat.set({ decay, sustain });
|
||||
});
|
||||
|
||||
export default controls;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
draw.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/tone/draw.mjs>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/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/>.
|
||||
*/
|
||||
|
||||
@@ -9,17 +9,29 @@ import { Pattern, getTime, State, TimeSpan } from './index.mjs';
|
||||
export const getDrawContext = (id = 'test-canvas') => {
|
||||
let canvas = document.querySelector('#' + id);
|
||||
if (!canvas) {
|
||||
const scale = 2; // 2 = crisp on retina screens
|
||||
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';
|
||||
canvas.width = window.innerWidth * scale;
|
||||
canvas.height = window.innerHeight * scale;
|
||||
canvas.style = 'pointer-events:none;width:100%;height:100%;position:fixed;top:0;left:0';
|
||||
document.body.prepend(canvas);
|
||||
let timeout;
|
||||
window.addEventListener('resize', () => {
|
||||
timeout && clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
canvas.width = window.innerWidth * scale;
|
||||
canvas.height = window.innerHeight * scale;
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
return canvas.getContext('2d');
|
||||
};
|
||||
|
||||
Pattern.prototype.draw = function (callback, { from, to, onQuery }) {
|
||||
Pattern.prototype.draw = function (callback, { from, to, onQuery } = {}) {
|
||||
if (typeof window === 'undefined') {
|
||||
return this;
|
||||
}
|
||||
if (window.strudelAnimation) {
|
||||
cancelAnimationFrame(window.strudelAnimation);
|
||||
}
|
||||
@@ -51,7 +63,7 @@ Pattern.prototype.draw = function (callback, { from, to, onQuery }) {
|
||||
|
||||
export const cleanupDraw = (clearScreen = true) => {
|
||||
const ctx = getDrawContext();
|
||||
clearScreen && ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
|
||||
clearScreen && ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.width);
|
||||
if (window.strudelAnimation) {
|
||||
cancelAnimationFrame(window.strudelAnimation);
|
||||
}
|
||||
@@ -65,3 +77,97 @@ Pattern.prototype.onPaint = function (onPaint) {
|
||||
this.context = { onPaint };
|
||||
return this;
|
||||
};
|
||||
|
||||
// const round = (x) => Math.round(x * 1000) / 1000;
|
||||
|
||||
// encapsulates starting and stopping animation frames
|
||||
export class Framer {
|
||||
constructor(onFrame, onError) {
|
||||
this.onFrame = onFrame;
|
||||
this.onError = onError;
|
||||
}
|
||||
start() {
|
||||
const self = this;
|
||||
let frame = requestAnimationFrame(function updateHighlights(time) {
|
||||
try {
|
||||
self.onFrame(time);
|
||||
} catch (err) {
|
||||
self.onError(err);
|
||||
}
|
||||
frame = requestAnimationFrame(updateHighlights);
|
||||
});
|
||||
self.cancel = () => {
|
||||
cancelAnimationFrame(frame);
|
||||
};
|
||||
}
|
||||
stop() {
|
||||
if (this.cancel) {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// syncs animation frames to a cyclist scheduler
|
||||
// see vite-vanilla-repl-cm6 for an example
|
||||
export class Drawer {
|
||||
constructor(onDraw, drawTime) {
|
||||
let [lookbehind, lookahead] = drawTime; // e.g. [-2, 2]
|
||||
lookbehind = Math.abs(lookbehind);
|
||||
this.visibleHaps = [];
|
||||
this.lastFrame = null;
|
||||
this.drawTime = drawTime;
|
||||
this.framer = new Framer(
|
||||
() => {
|
||||
if (!this.scheduler) {
|
||||
console.warn('Drawer: no scheduler');
|
||||
return;
|
||||
}
|
||||
// calculate current frame time (think right side of screen for pianoroll)
|
||||
const phase = this.scheduler.now() + lookahead;
|
||||
// first frame just captures the phase
|
||||
if (this.lastFrame === null) {
|
||||
this.lastFrame = phase;
|
||||
return;
|
||||
}
|
||||
// query haps from last frame till now. take last 100ms max
|
||||
const haps = this.scheduler.pattern.queryArc(Math.max(this.lastFrame, phase - 1 / 10), phase);
|
||||
this.lastFrame = phase;
|
||||
this.visibleHaps = (this.visibleHaps || [])
|
||||
// filter out haps that are too far in the past (think left edge of screen for pianoroll)
|
||||
.filter((h) => h.whole.end >= phase - lookbehind - lookahead)
|
||||
// add new haps with onset (think right edge bars scrolling in)
|
||||
.concat(haps.filter((h) => h.hasOnset()));
|
||||
const time = phase - lookahead;
|
||||
onDraw(this.visibleHaps, time, this);
|
||||
},
|
||||
(err) => {
|
||||
console.warn('draw error', err);
|
||||
},
|
||||
);
|
||||
}
|
||||
invalidate(scheduler = this.scheduler) {
|
||||
if (!scheduler || !scheduler.pattern) {
|
||||
return;
|
||||
}
|
||||
this.scheduler = scheduler;
|
||||
const t = scheduler.now();
|
||||
let [_, lookahead] = this.drawTime;
|
||||
const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1];
|
||||
// remove all future haps
|
||||
this.visibleHaps = this.visibleHaps.filter((h) => h.whole.begin < t);
|
||||
// query future haps
|
||||
const futureHaps = scheduler.pattern.queryArc(begin, end); // +0.1 = workaround for weird holes in query..
|
||||
// append future haps
|
||||
this.visibleHaps = this.visibleHaps.concat(futureHaps);
|
||||
}
|
||||
start(scheduler) {
|
||||
this.scheduler = scheduler;
|
||||
this.invalidate();
|
||||
this.framer.start();
|
||||
}
|
||||
stop() {
|
||||
if (this.framer) {
|
||||
this.framer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
evaluate.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/eval/evaluate.mjs>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/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/>.
|
||||
*/
|
||||
|
||||
@@ -37,8 +37,12 @@ function safeEval(str, options = {}) {
|
||||
}
|
||||
|
||||
export const evaluate = async (code, transpiler) => {
|
||||
let meta = {};
|
||||
if (transpiler) {
|
||||
code = transpiler(code); // transform syntactically correct js code to semantically usable code
|
||||
// transform syntactically correct js code to semantically usable code
|
||||
const transpiled = transpiler(code);
|
||||
code = transpiled.output;
|
||||
meta = transpiled;
|
||||
}
|
||||
// if no transpiler is given, we expect a single instruction (!wrapExpression)
|
||||
const options = { wrapExpression: !!transpiler };
|
||||
@@ -48,5 +52,5 @@ export const evaluate = async (code, transpiler) => {
|
||||
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 };
|
||||
return { mode: 'javascript', pattern: evaluated, meta };
|
||||
};
|
||||
|
||||
@@ -22,5 +22,3 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
public/samples
|
||||
@@ -0,0 +1,8 @@
|
||||
# vite-vanilla-repl-cm6
|
||||
|
||||
This folder demonstrates how to set up a strudel repl using vite and vanilla JS + codemirror. Run it using:
|
||||
|
||||
```sh
|
||||
pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
@@ -0,0 +1,22 @@
|
||||
<!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>
|
||||
<main>
|
||||
<nav>
|
||||
<button id="play">eval</button>
|
||||
<button id="stop">stop</button>
|
||||
</nav>
|
||||
<div class="container">
|
||||
<div id="editor"></div>
|
||||
<div id="output"></div>
|
||||
</div>
|
||||
<canvas id="roll"></canvas>
|
||||
</main>
|
||||
<script type="module" src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,39 @@
|
||||
import { StrudelMirror } from '@strudel/codemirror';
|
||||
import { funk42 } from './tunes';
|
||||
import { drawPianoroll, evalScope, controls } from '@strudel.cycles/core';
|
||||
import './style.css';
|
||||
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
||||
import { transpiler } from '@strudel.cycles/transpiler';
|
||||
import { getAudioContext, webaudioOutput, registerSynthSounds } from '@strudel.cycles/webaudio';
|
||||
import { registerSoundfonts } from '@strudel.cycles/soundfonts';
|
||||
|
||||
// init canvas
|
||||
const canvas = document.getElementById('roll');
|
||||
canvas.width = canvas.width * 2;
|
||||
canvas.height = canvas.height * 2;
|
||||
const drawContext = canvas.getContext('2d');
|
||||
const drawTime = [-2, 2]; // time window of drawn haps
|
||||
|
||||
const editor = new StrudelMirror({
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime: () => getAudioContext().currentTime,
|
||||
transpiler,
|
||||
root: document.getElementById('editor'),
|
||||
initialCode: funk42,
|
||||
drawTime,
|
||||
onDraw: (haps, time) => drawPianoroll({ haps, time, ctx: drawContext, drawTime, fold: 0 }),
|
||||
prebake: async () => {
|
||||
initAudioOnFirstClick(); // needed to make the browser happy (don't await this here..)
|
||||
const loadModules = evalScope(
|
||||
controls,
|
||||
import('@strudel.cycles/core'),
|
||||
import('@strudel.cycles/mini'),
|
||||
import('@strudel.cycles/tonal'),
|
||||
import('@strudel.cycles/webaudio'),
|
||||
);
|
||||
await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]);
|
||||
},
|
||||
});
|
||||
|
||||
document.getElementById('play').addEventListener('click', () => editor.evaluate());
|
||||
document.getElementById('stop').addEventListener('click', () => editor.stop());
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "vite-vanilla-repl-cm6",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^4.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strudel/codemirror": "workspace:*",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/mini": "workspace:*",
|
||||
"@strudel.cycles/soundfonts": "workspace:*",
|
||||
"@strudel.cycles/tonal": "workspace:*",
|
||||
"@strudel.cycles/transpiler": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
background: #282c34;
|
||||
}
|
||||
|
||||
main {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.container {
|
||||
flex-grow: 1;
|
||||
max-height: 100%;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#editor {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#roll {
|
||||
height: 300px;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
export const bumpStreet = `// froos - "22 bump street", licensed with CC BY-NC-SA 4.0
|
||||
await samples('github:felixroos/samples/main')
|
||||
await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
|
||||
|
||||
"<[0,<6 7 9>,13,<17 20 22 26>]!2>/2"
|
||||
// make it 22 edo
|
||||
.fmap(v => Math.pow(2,v/22))
|
||||
// mess with the base frequency
|
||||
.mul("<300 [300@3 200]>/8").freq()
|
||||
.layer(
|
||||
// chords
|
||||
x=>x.div(freq(2)).s("flute").euclidLegato("<3 2>",8)
|
||||
.shape(.4).lpf(sine.range(800,4000).slow(8)),
|
||||
// adlibs
|
||||
x=>x.arp("{0 3 2 [1 3]}%1.5")
|
||||
.s('xylo').mul(freq(2))
|
||||
.delay(.5).delayfeedback(.4).juxBy(.5, rev)
|
||||
.hpf(sine.range(200,3000).slow(8)),
|
||||
// bass
|
||||
x=>x.arp("[0 [2 1?]](5,8)").s('sawtooth').div(freq(4))
|
||||
.lpf(sine.range(400,2000).slow(8)).lpq(8).shape(.4)
|
||||
.off(1/8, x=>x.mul(freq(2)).degradeBy(.5)).gain(.3)
|
||||
).clip(1).release(.2)
|
||||
.stack(
|
||||
// drums
|
||||
s("bd sd:<2 1>, [~ hh]*2, [~ rim]").bank('RolandTR909')
|
||||
.off(1/8, x=>x.speed(2).gain(.4)).sometimes(ply(2)).gain(.8)
|
||||
.mask("<0@4 1@12>/4")
|
||||
.reset("<x@15 [x(3,8) x*[4 8]]>")
|
||||
// wait for it...
|
||||
).fast(2/3)
|
||||
//.crush(6) // remove "//" if you dare`;
|
||||
|
||||
export const trafficFlam = `// froos - "traffic flam", licensed with CC BY-NC-SA 4.0
|
||||
|
||||
await samples('github:felixroos/samples/main')
|
||||
await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
|
||||
|
||||
addVoicings('hip', {
|
||||
m11: ['2M 3m 4P 7m'],
|
||||
'^7#11': ['3M 4A 5P 7M'],
|
||||
}, ['C4', 'C6'])
|
||||
|
||||
stack(
|
||||
stack(
|
||||
"<Bbm11 A^7#11>/2".voicings('hip').note()
|
||||
.s("gm_epiano1:2")
|
||||
.arp("[<[0 1 2 3] [3 2 1 0]> ~@5]/2")
|
||||
.release(2).late(.25).lpf(2000),
|
||||
"<Bb1 A1>/2".note().s('gm_acoustic_bass'),
|
||||
n("<0 2 3>(3,8)".off(1/8, add(4)))
|
||||
.scale("<Bb4:minor A4:lydian>/2")
|
||||
.s('gm_electric_guitar_jazz')
|
||||
.decay(sine.range(.05, .2).slow(32)).sustain(0)
|
||||
.delay(.5).lpf(sine.range(100,5000).slow(64))
|
||||
.gain(.7).room(.5).pan(sine.range(0,1).slow(11))
|
||||
).add(perlin.range(0,.25).note()),
|
||||
stack(
|
||||
s("bd:1(3,8) rim").bank('RolandTR707').slow(2).room("<0 <.1 .6>>")
|
||||
.when("<0@7 1>",x=>x.echoWith(3, .0625, (x,i) => x.speed(1+i*.24))),
|
||||
s("rim*4").end(.05).bank('RolandTR808').speed(.8).room(.2)
|
||||
)
|
||||
)
|
||||
.late("[0 .05]*2").late(12)
|
||||
|
||||
`;
|
||||
|
||||
export const funk42 = `// froos - how to funk in 42 lines of code
|
||||
// adapted from "how to funk in two minutes" by marc rebillet https://www.youtube.com/watch?v=3vBwRfQbXkg
|
||||
// thanks to peach for the transcription: https://www.youtube.com/watch?v=8eiPXvIgda4
|
||||
|
||||
await samples('github:felixroos/samples/main')
|
||||
await samples('https://strudel.cc/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
|
||||
|
||||
setcps(.5)
|
||||
|
||||
let drums = stack(
|
||||
s("bd*2, ~ sd").bank('RolandTR707').room("0 .1"),
|
||||
s("hh*4").begin(.2).release(.02).end(.25).release(.02)
|
||||
.gain(.3).bank('RolandTR707').late(.02).room(.5),
|
||||
//s("shaker_small").struct("[x x*2]*2").speed(".8,.9").release(.02)
|
||||
).fast(2)
|
||||
|
||||
let wurli = note(\`<
|
||||
[[a2,g3,[b3 c4],e4] ~ [g3,c4,e4](3,8)@4 ~@2]!3
|
||||
[[e2,e3,a3,b3,e4]@3 [e2,e3,ab3,b3,e4]@5]>\`)
|
||||
.s("gm_epiano1:5").decay(.2).sustain("<[1 0@7]!3 1>")
|
||||
.gain("<[.8@2 .4@14]!3 .7>").room(.3)
|
||||
|
||||
let organ = note("<[~@3 [a3,d4,f#4]@2 [[a3,c4,e4]@2 ~] ~@2]!3 ~>".add(12))
|
||||
.s("gm_percussive_organ:2").gain(.6).lpf(1800).pan(.2).room(.3);
|
||||
|
||||
let clav = note(\`<
|
||||
[~@3 a2 [g3,[b3 c4],e4]@2 ~ a2 [g3,b3,e4] ~@2 [g3,c4,e4] ~@4]!3
|
||||
[~@3 e3 [[a3 b3],c3,e3]@2 ~ e2 [e3,a3]@3 [b3,e3] ~@2 [b3,e3]@2]>\`)
|
||||
.s("gm_clavinet:1").decay("<.25!3 [.25 .4]>").sustain(0)
|
||||
.gain(.7).pan(.8).room(.2);
|
||||
|
||||
let bass = note(\`<
|
||||
[a1 [~ [g2 a2]] [g1 g#1] [a1 [g2 a2]]]
|
||||
[a1 [~ [g2 a2]] [e3 d3] [c3 [g3 a3]]]
|
||||
[a1 [~ [g2 a2]] [g1 g#1] [a1 [g2 a2]]]
|
||||
[e2@6 e1@5 e1 [[d2 e3] g1]@4]
|
||||
>\`).s("gm_electric_bass_pick:1").release(.1)
|
||||
|
||||
stack(
|
||||
drums
|
||||
,wurli
|
||||
,organ
|
||||
,clav
|
||||
,bass
|
||||
)`;
|
||||
@@ -6,5 +6,3 @@ This folder demonstrates how to set up a strudel repl using vite and vanilla JS.
|
||||
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)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^3.2.0"
|
||||
"vite": "^4.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
|
||||
@@ -32,7 +32,11 @@ export class Hap {
|
||||
}
|
||||
|
||||
get duration() {
|
||||
return this.whole.end.sub(this.whole.begin);
|
||||
return this.whole.end.sub(this.whole.begin).mul(typeof this.value?.clip === 'number' ? this.value?.clip : 1);
|
||||
}
|
||||
|
||||
get endClipped() {
|
||||
return this.whole.begin.add(this.duration);
|
||||
}
|
||||
|
||||
wholeOrPart() {
|
||||
|
||||
@@ -18,11 +18,13 @@ export * from './util.mjs';
|
||||
export * from './speak.mjs';
|
||||
export * from './evaluate.mjs';
|
||||
export * from './repl.mjs';
|
||||
export * from './cyclist.mjs';
|
||||
export * from './logger.mjs';
|
||||
export * from './time.mjs';
|
||||
export * from './draw.mjs';
|
||||
export * from './animate.mjs';
|
||||
export * from './pianoroll.mjs';
|
||||
export * from './spiral.mjs';
|
||||
export * from './ui.mjs';
|
||||
export { default as drawLine } from './drawLine.mjs';
|
||||
export { default as gist } from './gist.js';
|
||||
|
||||