├── .stylua.toml ├── LICENSE ├── Makefile ├── README.md ├── modal ├── modal.lua ├── rockspecs └── modal-dev-1.rockspec ├── scripts ├── gendef.lua └── pack.lua ├── spec ├── maxi_spec.lua ├── pattern_spec.lua ├── span_spec.lua ├── stream_spec.lua ├── tdef_spec.lua ├── time_spec.lua ├── util_spec.lua └── valuemap_spec.lua └── src ├── a2s.lua ├── clock.lua ├── control.lua ├── factory.lua ├── ideas ├── iter.lua ├── norns.lua └── sample.lua ├── init.lua ├── losc.lua ├── luacats.lua ├── lulpeg.lua ├── mml.lua ├── notation.lua ├── pattern.lua ├── repl.lua ├── server.lua ├── theory.lua ├── types.lua └── ut.lua /.stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 120 2 | line_endings = "Unix" 3 | indent_type = "Spaces" 4 | indent_width = 3 5 | quote_style = "AutoPreferDouble" 6 | call_parentheses = "None" 7 | collapse_simple_statement = "Never" 8 | 9 | [sort_requires] 10 | enabled = false 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # LUA= $(shell echo `which lua`) 2 | # LUA_BINDIR= $(shell echo `dirname $(LUA)`) 3 | # LUA_PREFIX= $(shell echo `dirname $(LUA_BINDIR)`) 4 | # LUA_SHAREDIR=$(LUA_PREFIX)/share/lua/5.1 5 | # 6 | # _REPODIR != cd "$(shell dirname $(firstword $(MAKEFILE_LIST)))/" && pwd 7 | 8 | modal: 9 | 10 | test: 11 | sudo luarocks-5.1 build && busted --lua=/usr/bin/lua5.1 12 | # sudo luarocks-5.2 build && busted --lua=/usr/bin/lua5.2 13 | # sudo luarocks-5.3 build && busted --lua=/usr/bin/lua5.3 14 | # sudo luarocks build --lua-version 5.4 && busted --lua=/usr/bin/lua5.4 15 | 16 | build: 17 | luajit ./scripts/pack.lua > modal.lua 18 | echo "#!/usr/bin/luajit\nrequire'modal'.repl()" > modal 19 | echo "#!/usr/bin/luajit\nrequire'modal'.server()" > mods 20 | chmod +x modal 21 | # doc: 22 | # ldoc . 23 | 24 | remove: 25 | luarocks remove modal 26 | 27 | install: 28 | luarocks build 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # modal 2 | 3 | Lua port of tidal, this project is at early alpha stage, all kinds of things can go wrong, but trying out and feedbacks are appreciated! 4 | 5 | See [Wiki](https://github.com/noearc/modal/wiki) for more desgin highlights and future plans. 6 | 7 | ## Dependencies 8 | 9 | May need to manually install `libasio-dev` for abletonlink to work 10 | 11 | ## Install 12 | 13 | ### Copy 14 | 15 | If you are familiar with lua and just want to experiment right away, copy [modal.lua](https://github.com/noearc/modal/blob/main/modal.lua) and use seqerate lua instance to run repl and server. 16 | Install or build missing dependencies ... 17 | 18 | ### Luarocks 19 | 20 | Install luarocks **this one is not ready yet** 21 | 22 | ``` 23 | sudo luarocks install modal 24 | ``` 25 | 26 | ### Luvit 27 | 28 | 1. Install [lit](https://github.com/luvit/lit) and 29 | 30 | ``` 31 | lit install noearc/modal 32 | ``` 33 | 34 | this installs modal.lua into a deps/ directory. later should be able to build a binary with lit as well. 35 | 36 | ## Build and Develop 37 | 38 | 1. Clone or download the project zip from github 39 | 2. Open a terminal in the project directory 40 | 3. Run `sudo make install` 41 | 4. Install [busted](https://luarocks.org/modules/lunarmodules/busted) to run tests with `busted` 42 | 43 | ## Play 44 | 45 | 1. Start supercollider and run `SuperDirt.start`. 46 | 2. Use [modal.nvim](https://github.com/noearc/modal.nvim) in neovim. 47 | 3. Or in terminal, launch `mods` (server backend) and `modal` (repl) side by side. 48 | 49 | ## History 50 | 51 | This porject works on top of the working prototype port of [tranquility](https://github.com/XiNNiW/tranquility), the og lua port. I originally intended this as a moonscript port, because it has a more tarse syntax. But as this project grew larger, lua tooling is obviously better. Plus I wrote the custom parser to replace it as the user code parser. So this project is the current lua port of Tidal that went the furtherest for now :) 52 | 53 | ## Collaboration 54 | 55 | Collaboration is welcome! 56 | -------------------------------------------------------------------------------- /modal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/luajit 2 | require'modal'.repl() 3 | -------------------------------------------------------------------------------- /rockspecs/modal-dev-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "modal" 2 | version = "dev-1" 3 | source = { 4 | url = "https://github.com/noearc/modal.git", 5 | } 6 | 7 | description = { 8 | summary = "lua port of the tidalcycles pattern language", 9 | detailed = [[ 10 | modal is an experimental port of the livecoding music language Tidalcycles(http://tidalcycles.org/) to the lua. 11 | This project follows the footsteps of vortex, strudel and tranquility.]], 12 | homepage = "https://github.com/noearc/modal", 13 | license = "GPL3", 14 | } 15 | 16 | dependencies = { 17 | "lua >= 5.1", 18 | "abletonlink >= 1.0.0-1", 19 | -- "lpeg >= 1.1.0-1", 20 | "readline >= 3.3-0", 21 | } 22 | 23 | build = { 24 | type = "command", 25 | build_command = "make build", 26 | install = { 27 | lua = { 28 | ["modal"] = "modal.lua", 29 | }, 30 | bin = { 31 | ["modal"] = "modal", 32 | ["mods"] = "mods", 33 | }, 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /scripts/gendef.lua: -------------------------------------------------------------------------------- 1 | local M = require "modal" 2 | 3 | -- TODO: cache locals 4 | -- TODO: generate the def file at build tiem, figure out how to properly load them in coding and performing (learn luaLS!) 5 | -- TODO: use this in repl 6 | 7 | local type_var = 96 8 | 9 | local function gen_arg() 10 | type_var = type_var + 1 11 | return string.char(type_var) 12 | end 13 | 14 | local param_format = "---@param %s %s" 15 | local return_format = "---@return %s" 16 | 17 | local function get_type(ttab) 18 | local tstr = ttab[1] 19 | if ttab.type then 20 | return "table" 21 | end 22 | if tstr == "a" or tstr == "b" then -- not in {Pattern Time ....} 23 | return "any" 24 | end 25 | return tstr 26 | end 27 | 28 | local function gen(name) 29 | local argtypes = M.t[name] 30 | local arity = #argtypes 31 | local argstr = "" 32 | local typestr = "" 33 | for i = 1, arity do 34 | local arg_name = argtypes[i].name 35 | typestr = typestr .. string.format(param_format, arg_name, get_type(argtypes[i])) .. "\n" 36 | if i == arity then 37 | argstr = argstr .. arg_name 38 | else 39 | argstr = argstr .. arg_name .. ", " 40 | end 41 | end 42 | -- typestr = typestr .. string.format(return_format, argtypes.ret[1]) .. "\n" 43 | typestr = typestr .. string.format(return_format, "Pattern") .. "\n" 44 | local fstr = string.format("%s = function(%s) end", name, argstr) 45 | type_var = 96 46 | return typestr .. fstr .. "\n" 47 | end 48 | 49 | -- for i, _ in pairs(M.t) do 50 | -- -- print(gen(i)) 51 | -- local ok, res = pcall(gen, i) 52 | -- if ok then 53 | -- print(res) 54 | -- end 55 | -- end 56 | 57 | return gen 58 | -------------------------------------------------------------------------------- /scripts/pack.lua: -------------------------------------------------------------------------------- 1 | -- path = "../src/core/" 2 | 3 | files = {} 4 | 5 | local fs = { 6 | dir = function(path) 7 | local listing = io.popen("ls " .. path):read "*all" 8 | local files = {} 9 | for file in listing:gmatch "[^\n]+" do 10 | files[file] = true 11 | end 12 | return next, files 13 | end, 14 | attributes = function() 15 | return {} 16 | end, 17 | } 18 | 19 | local function scandir(root) 20 | -- adapted from http://keplerproject.github.com/luafilesystem/examples.html 21 | local hndl 22 | for f in fs.dir(root) do 23 | if f:find "%.lua$" then 24 | hndl = f:gsub("%.lua$", ""):gsub("^[/\\]", ""):gsub("/", "."):gsub("\\", ".") 25 | files[hndl] = io.open(root .. f) 26 | end 27 | end 28 | end 29 | 30 | scandir "src/" 31 | 32 | local modules = { "ut", "types", "a2s", "notation", "theory", "clock", "factory", "control", "pattern", "losc" } 33 | 34 | local function not_modname(line) 35 | for _, name in ipairs(modules) do 36 | if line:match(('local %s = require "%s"'):format(name, name)) then 37 | return false 38 | end 39 | end 40 | return true 41 | end 42 | 43 | local function get_content(name, file, no_req) 44 | no_req = no_req or true 45 | local contents = {} 46 | for line in file:lines() do 47 | if no_req then 48 | if not_modname(line) and not line:match(("local %s = {}"):format(name)) then 49 | contents[#contents + 1] = " " .. line 50 | end 51 | else 52 | contents[#contents + 1] = " " .. line 53 | end 54 | end 55 | contents[#contents] = nil 56 | local str = table.concat(contents, "\n") 57 | return str 58 | end 59 | 60 | local function wrap(name, file, no_req) 61 | local format = [[do 62 | %s 63 | end 64 | ]] 65 | return format:format(get_content(name, file, no_req)) 66 | end 67 | 68 | local requires = [==[ 69 | --[[lit-meta 70 | name = "noearc/modal" 71 | version = "0.0.1.1" 72 | homepage = "https://github.com/noearc/modal" 73 | description = "tidal cycles in lua!" 74 | license = "GPL3" 75 | ]] 76 | local ut = {} 77 | local pattern = {} 78 | local control = {} 79 | local types = {} 80 | local theory = {} 81 | local notation = {} 82 | local a2s = {} 83 | local factory = {} 84 | local losc = {} 85 | ]==] 86 | 87 | -- load("lulpeg", false) 88 | local header = files["lulpeg"]:read "*a" .. "\n" .. requires 89 | -- .. files["losc"]:read "*a" .. requires 90 | 91 | function load(name, no_req) 92 | header = header .. "\n" .. wrap(name, files[name], no_req) 93 | end 94 | load "losc" 95 | load "ut" 96 | load "types" 97 | load "a2s" 98 | load "notation" 99 | load "theory" 100 | load "clock" 101 | load "factory" 102 | load "control" 103 | load "pattern" 104 | header = header .. "\n" .. get_content("init", files["init"]) 105 | load "repl" 106 | load "server" 107 | header = header .. "\n" .. "modal.ut = ut" 108 | header = header .. "\n" .. "return modal" 109 | 110 | print(header) 111 | 112 | -- TODO: lfs 113 | -- TODO: stylua the result if avaliable 114 | -------------------------------------------------------------------------------- /spec/maxi_spec.lua: -------------------------------------------------------------------------------- 1 | local M = require "modal" 2 | M() 3 | local maxi = require("modal").maxi(M) 4 | local mini = require("modal").mini(M) 5 | local describe = require("busted").describe 6 | local it = require("busted").it 7 | local assert = require("busted").assert 8 | 9 | assert.pat = function(a, b) 10 | assert.same(a(0, 1), b(0, 1)) 11 | end 12 | 13 | describe("symb", function() 14 | it("should parse steps to lua String or Id", function() 15 | local hello 16 | assert.same("hello", maxi "hello") 17 | assert.same(hello, maxi "'hello") 18 | end) 19 | it("should parse steps to lua String or Id", function() 20 | assert.same(42, maxi "42") 21 | end) 22 | end) 23 | 24 | describe("set", function() 25 | it("should parse set to lua var set in only top level????", function() 26 | assert.same(1, maxi "a = 1; 'a") 27 | end) 28 | -- it("should parse set a func call to var", function() 29 | -- assert.same("a = fast(pure2(2), pure2(1)); ", to_str " a = $ fast 2 1 ") 30 | -- end) 31 | -- it("should parse set a sexp to var", function() 32 | -- assert.same("a = fast(2, 1); ", to_str " a = (fast 2 1) ") 33 | -- end) 34 | -- it("should parse set a mini-notation to var", function() 35 | -- assert.same([[a = fastcat({pure("bd"),pure("sd")}); ]], to_str "a = [bd sd]") 36 | -- end) 37 | end) 38 | 39 | describe("step", function() 40 | it("numbers", function() 41 | assert.same(-1, maxi "-1") 42 | end) 43 | end) 44 | 45 | describe("slice", function() 46 | it("should parse mini slice as a first class", function() 47 | assert.same("bd", maxi "bd") 48 | end) 49 | end) 50 | 51 | describe("subcycle", function() 52 | it("should parse mini subcycle as a first class", function() 53 | assert.pat(fastcat { "bd" }, mini "[bd]") 54 | assert.pat(fastcat { "bd", "sd" }, mini "[bd sd]") 55 | end) 56 | end) 57 | 58 | describe("stack", function() 59 | it("should parse mini stack as a first class", function() 60 | assert.pat(stack { pure "bd", pure "sd" }, mini "[bd, sd]") 61 | assert.pat(stack { fastcat { "bd", "bd" }, fastcat { "sd", "sd" } }, mini "[bd bd, sd sd]") 62 | end) 63 | end) 64 | 65 | describe("slow_seq", function() 66 | it("should parse mini slow_seq as a first class", function() 67 | assert.pat(slowcat { "bd" }, mini "") 68 | -- assert.pat(slowcat { "bd", "sd" }, mini "") 69 | -- assert.pat(slowcat { 0, 1, 2, 3, 4 }, mini "<0 .. 4>") 70 | end) 71 | end) 72 | 73 | describe("polymeter", function() 74 | it("should parse mini polymeter as a first class", function() 75 | assert.pat(fastcat { "bd", "sd", "hh" }, mini "{bd sd hh}") 76 | assert.pat(fastcat { "bd", "sd", "hh" }, mini "{bd sd hh}%3") 77 | assert.pat(fastcat { "bd", "sd" }, mini "{bd sd hh}%2") 78 | assert.pat(stack { fastcat { "bd", "sd" }, fastcat { 1, 2 } }, mini "{bd sd hh, 1 2 3 4}%2") 79 | end) 80 | end) 81 | 82 | describe("choose", function() 83 | it("should parse mini choose as first class", function() 84 | assert.pat(randcat { pure "bd", pure "sd", pure "cp" }, mini "[bd | sd | cp]") 85 | assert.pat(randcat { fastcat { "bd", "sd" }, pure "sd", pure "cp" }, mini "[bd sd | sd | cp]") 86 | assert.pat(randcat { fastcat { "bd", "sd" }, pure "sd", pure "cp" }, mini "< bd sd | sd | cp >") 87 | end) 88 | end) 89 | 90 | describe("dotStack", function() 91 | it("should parse mini dotStack as first class", function() 92 | assert.pat(fastcat { fastcat { 1, 2 }, fastcat { 3, 4 } }, mini "[1 2 . 3 4]") 93 | end) 94 | it("should parse mini dotStack as first class", function() 95 | assert.pat(slowcat { fastcat { 1, 2 }, fastcat { 3, 4 } }, mini "<1 2 . 3 4>") 96 | end) 97 | end) 98 | 99 | describe("ops", function() 100 | it("should parse mini ops as a first class", function() 101 | assert.pat(fast(2, "bd"), mini "bd*2") 102 | assert.pat(euclidRot(3, 8, 0, "bd"), mini "bd(3,8)") 103 | assert.pat(degradeBy(0.5, "bd"), mini "bd?") 104 | assert.pat(fastcat { "bd", "bd", "sd" }, mini "[bd! sd]") 105 | assert.pat(pure { 0.3, 0.5, 2 }, mini "0.3:0.5:2") 106 | end) 107 | it("weight", function() 108 | assert.pat(timecat { 2, "bd", 1, "sd" }, mini "[bd@2 sd]") 109 | assert.pat(timecat { 3, "bd", 2, "sd" }, mini "[bd __ sd _]") 110 | assert.pat(arrange { 2, "bd", 1, "sd" }, mini "") 111 | end) 112 | end) 113 | 114 | describe("tidal ops", function() 115 | it("should parse tidal ops as a first class", function() 116 | assert.pat(note(3), maxi [[note 2 +| note 1]]) 117 | assert.pat(pure { s = "bd", room = 0.2 }, maxi [[s bd |> room 0.2]]) 118 | assert.pat(pure { s = "bd", room = 0.2 }, maxi [[s bd >|| room 0.2]]) 119 | end) 120 | end) 121 | 122 | describe("list(p)", function() 123 | it("should parse sexp as a first class", function() 124 | assert.same(3, maxi "(+ 1 2)") 125 | assert.same(-1, maxi "(- 1 2)") 126 | end) 127 | it("should parse sexp func call as a first class", function() 128 | assert.pat(fast(1, 2), maxi "(fast 1 2)") 129 | assert.pat(fast(1, pure(2)), maxi "(fast 1 (pure 2))") 130 | end) 131 | it("should parse nested function calls", function() 132 | assert.same(fast(2, 1)(1, 2), maxi "((fast 2 1) 1 2)") 133 | end) 134 | it("should do prefix operator", function() 135 | assert.same(2, maxi "(+ 1 1)") 136 | end) 137 | it("should do currying for arithmetic", function() 138 | assert.same(2, maxi "((+ 1) 1)") 139 | assert.same(2, maxi "((+) 1 1)") 140 | end) 141 | -- TODO: curry for all functions 142 | end) 143 | -------------------------------------------------------------------------------- /spec/pattern_spec.lua: -------------------------------------------------------------------------------- 1 | local describe = require("busted").describe 2 | local it = require("busted").it 3 | local assert = require("busted").assert 4 | 5 | local M = require "modal" 6 | M() 7 | ut.Usecolor = false 8 | 9 | local Span, Event = M.Span, M.Event 10 | local Pattern, reify, pure = M.Pattern, M.reify, M.pure 11 | 12 | describe("new", function() 13 | it("should initialize with defaults", function() 14 | local pat = Pattern() 15 | assert.same({}, pat.query(Span())) 16 | end) 17 | it("should create with specified query", function() 18 | local pat = Pattern(function() 19 | return { Event() } 20 | end) 21 | local Events = pat.query(Span()) 22 | assert.same({ Event() }, Events) 23 | end) 24 | end) 25 | 26 | describe("withValue", function() 27 | it("should return new pattern with function mapped over Event values on query", function() 28 | local pat = pure(5) 29 | local func = function(v) 30 | return v + 5 31 | end 32 | local newPat = pat:withValue(func) 33 | local expected = { Event(Span(0, 1), Span(0, 1), 10) } 34 | return assert.same(expected, newPat(0, 1)) 35 | end) 36 | end) 37 | 38 | describe("onsetsOnly", function() 39 | it("should return only Events where the start of the whole equals the start of the part", function() 40 | local whole1 = Span(1 / 2, 2) 41 | local part1 = Span(1 / 2, 1) 42 | local Event1 = Event(whole1, part1, 1, {}, false) 43 | local whole2 = Span(2 / 3, 3) 44 | local part2 = Span(5 / 6, 1) 45 | local Event2 = Event(whole2, part2, 2, {}, false) 46 | local p = Pattern(function() 47 | return { Event1, Event2 } 48 | end) 49 | local patternWithOnsetsOnly = p:onsetsOnly() 50 | local actual = patternWithOnsetsOnly(0, 3) 51 | return assert.same({ Event1 }, actual) 52 | end) 53 | it("pure patterns should not behave like continuous signals... they should have discrete onsets", function() 54 | local p = pure "bd" 55 | local patternWithOnsetsOnly = p:onsetsOnly() 56 | local expected = { 57 | Event(Span(0, 1), Span(0, 1), "bd"), 58 | } 59 | local actual = patternWithOnsetsOnly(0, 1) 60 | assert.same(expected, actual) 61 | actual = patternWithOnsetsOnly(1 / 16, 1) 62 | assert.same({}, actual) 63 | end) 64 | end) 65 | 66 | describe("discreteOnly", function() 67 | it("should return only Events where the start of the whole equals the start of the part", function() 68 | local ev1 = { Event() } 69 | local pat = Pattern(function() 70 | return ev1 71 | end) 72 | pat = pat:discreteOnly() 73 | assert.same({}, pat(0, 1)) 74 | local ev2 = { Event(), Event(Span(), Span(), 1) } 75 | pat = Pattern(function() 76 | return ev2 77 | end) 78 | pat = pat:discreteOnly() 79 | local expected = { Event(Span(), Span(), 1) } 80 | return assert.same(expected, pat(0, 1)) 81 | end) 82 | end) 83 | 84 | describe("filterEvents", function() 85 | return it("should return new pattern with values removed based on filter func", function() 86 | local pat = slowcat { "bd", "sd", "hh", "mt" } 87 | local newPat = pat:filterEvents(function(e) 88 | return e.value == "bd" or e.value == "hh" 89 | end) 90 | local expected = { 91 | Event(Span(0, 1), Span(0, 1), "bd"), 92 | Event(Span(2, 3), Span(2, 3), "hh"), 93 | } 94 | return assert.same(expected, newPat(0, 4)) 95 | end) 96 | end) 97 | describe("withQuerySpan", function() 98 | it("should return new pattern with that modifies query Span with function when queried", function() 99 | local pat = pure(5) 100 | local func = function(span) 101 | return Span(span.start + 0.5, span.stop + 0.5) 102 | end 103 | local newPat = pat:withQuerySpan(func) 104 | local expected = { 105 | Event(Span(0, 1), Span(0.5, 1), 5), 106 | Event(Span(1, 2), Span(1, 1.5), 5), 107 | } 108 | assert.same(expected, newPat(0, 1)) 109 | end) 110 | end) 111 | 112 | describe("splitQueries", function() 113 | it("should break a query that Spans multiple cycles into multiple queries each Spanning one cycle", function() 114 | local query = function(span) 115 | return { Event(span, span, "a") } 116 | end 117 | local pat = Pattern(query) 118 | local splitPat = pat:splitQueries() 119 | local expectedPat = { Event(Span(0, 2), Span(0, 2), "a") } 120 | local expectedSplit = { 121 | Event(Span(0, 1), Span(0, 1), "a"), 122 | Event(Span(1, 2), Span(1, 2), "a"), 123 | } 124 | assert.same(expectedPat, pat(0, 2)) 125 | assert.same(expectedSplit, splitPat(0, 2)) 126 | end) 127 | end) 128 | 129 | describe("withQueryTime", function() 130 | it( 131 | "should return new pattern whose query function will pass the query timeSpan through a function before mapping it to Events", 132 | function() 133 | local pat = pure(5) 134 | local add1 = function(other) 135 | return other + 1 136 | end 137 | local newPat = pat:withQueryTime(add1) 138 | local expected = { 139 | Event(Span(1, 2), Span(1, 2), 5), 140 | } 141 | assert.same(expected, newPat(0, 1)) 142 | end 143 | ) 144 | end) 145 | 146 | describe("withEventTime", function() 147 | it("should return new pattern with function mapped over Event times", function() 148 | local pat = pure(5) 149 | local func = function(time) 150 | return time + 0.5 151 | end 152 | local newPat = pat:withEventTime(func) 153 | local expected = { 154 | Event(Span(0.5, 1.5), Span(0.5, 1.5), 5), 155 | } 156 | assert.same(expected, newPat(0, 1)) 157 | end) 158 | end) 159 | 160 | describe("transform as methods", function() 161 | it("special ones", function() 162 | local pat = pure(1):fastcat(2, 3) 163 | assert.equal(fastcat { 1, 2, 3 }, pat) 164 | end) 165 | end) 166 | 167 | describe("appRight", function() 168 | it("should take structure from right and appliy f", function() 169 | local add = function(a) 170 | return function(b) 171 | return a + b 172 | end 173 | end 174 | local left = reify({ 1, 2 }):fmap(add) 175 | local right = reify { 4, 5, 6 } 176 | local expected = { 177 | Event(Span(0, 1 / 3), Span(0, 1 / 3), 5), 178 | Event(Span(1 / 3, 2 / 3), Span(1 / 3, 1 / 2), 6), 179 | Event(Span(1 / 3, 2 / 3), Span(1 / 2, 2 / 3), 7), 180 | Event(Span(2 / 3, 1), Span(2 / 3, 1), 8), 181 | } 182 | local pat = left:appRight(right) 183 | assert.same(expected, pat(0, 1)) 184 | end) 185 | end) 186 | 187 | describe("appLeft", function() 188 | it("should take structure from left and appliy f", function() 189 | local add = function(a) 190 | return function(b) 191 | return a + b 192 | end 193 | end 194 | local left = reify({ 1, 2 }):fmap(add) 195 | local right = reify { 4, 5, 6 } 196 | local expected = { 197 | Event(Span(0, 1 / 2), Span(0, 1 / 3), 5), 198 | Event(Span(0, 1 / 2), Span(1 / 3, 1 / 2), 6), 199 | Event(Span(1 / 2, 1), Span(1 / 2, 2 / 3), 7), 200 | Event(Span(1 / 2, 1), Span(2 / 3, 1), 8), 201 | } 202 | local pat = left:appLeft(right) 203 | assert.same(expected, pat(0, 1)) 204 | end) 205 | end) 206 | describe("appRight", function() 207 | it("should take structure from right and appliy f", function() 208 | local add = function(a) 209 | return function(b) 210 | return a + b 211 | end 212 | end 213 | local left = reify({ 1, 2 }):fmap(add) 214 | local right = reify { 4, 5, 6 } 215 | local expected = { 216 | Event(Span(0, 1 / 3), Span(0, 1 / 3), 5), 217 | Event(Span(1 / 3, 2 / 3), Span(1 / 3, 1 / 2), 6), 218 | Event(Span(1 / 3, 2 / 3), Span(1 / 2, 2 / 3), 7), 219 | Event(Span(2 / 3, 1), Span(2 / 3, 1), 8), 220 | } 221 | local pat = left:appRight(right) 222 | assert.same(expected, pat(0, 1)) 223 | end) 224 | end) 225 | 226 | describe("appRight", function() 227 | it("should take structure from right and appliy f", function() 228 | local add = function(a) 229 | return function(b) 230 | return a + b 231 | end 232 | end 233 | local left = reify({ 1, 2 }):fmap(add) 234 | local right = reify { 4, 5, 6 } 235 | local expected = { 236 | Event(Span(0, 1 / 3), Span(0, 1 / 3), 5), 237 | Event(Span(1 / 3, 2 / 3), Span(1 / 3, 1 / 2), 6), 238 | Event(Span(1 / 3, 2 / 3), Span(1 / 2, 2 / 3), 7), 239 | Event(Span(2 / 3, 1), Span(2 / 3, 1), 8), 240 | } 241 | local pat = left:appRight(right) 242 | assert.same(expected, pat(0, 1)) 243 | end) 244 | end) 245 | 246 | describe("appBoth", function() 247 | it("should take structure from both sides and appliy f", function() 248 | local add = function(a) 249 | return function(b) 250 | return a + b 251 | end 252 | end 253 | local left = reify({ 1, 2 }):fmap(add) 254 | local right = reify { 4, 5, 6 } 255 | local expected = { 256 | Event(Span(0, 1 / 3), Span(0, 1 / 3), 5), 257 | Event(Span(1 / 3, 1 / 2), Span(1 / 3, 1 / 2), 6), 258 | Event(Span(1 / 2, 2 / 3), Span(1 / 2, 2 / 3), 7), 259 | Event(Span(2 / 3, 1), Span(2 / 3, 1), 8), 260 | } 261 | local pat = left:appBoth(right) 262 | assert.same(expected, pat(0, 1)) 263 | end) 264 | end) 265 | 266 | describe("squeezeJoin", function() 267 | it( 268 | "it should convert a pattern of patterns into a single pattern, takes whole cycles of the inner pattern to fit each Event in the outer pattern.\n ", 269 | function() 270 | local patOfPats = fastcat { fastcat { 1, 2, 3 }, fastcat { 1, 2, 3 } } 271 | local pat = squeezeJoin(patOfPats) 272 | local expected = { 273 | Event(Span(0, 1 / 6), Span(0, 1 / 6), 1), 274 | Event(Span(1 / 6, 1 / 3), Span(1 / 6, 1 / 3), 2), 275 | Event(Span(1 / 3, 1 / 2), Span(1 / 3, 1 / 2), 3), 276 | Event(Span(1 / 2, 2 / 3), Span(1 / 2, 2 / 3), 1), 277 | Event(Span(2 / 3, 5 / 6), Span(2 / 3, 5 / 6), 2), 278 | Event(Span(5 / 6, 1), Span(5 / 6, 1), 3), 279 | } 280 | assert.same(expected, pat(0, 1)) 281 | end 282 | ) 283 | end) 284 | 285 | describe("pure", function() 286 | it("should create Pattern of a single value repeating once per cycle", function() 287 | local atom = pure(5) 288 | local expected = { 289 | Event(Span(0, 1), Span(0, 1), 5, {}, false), 290 | } 291 | local actual = atom(0, 1) 292 | assert.same(#expected, #actual) 293 | assert.same(expected, actual) 294 | expected = { 295 | Event(Span(0, 1), Span(1 / 2, 1), 5, {}, false), 296 | } 297 | actual = atom(1 / 2, 1) 298 | assert.same(#expected, #actual) 299 | assert.same(expected, actual) 300 | end) 301 | end) 302 | 303 | describe("slowcat", function() 304 | it("should alternate between the patterns in the list, one pattern per cycle", function() 305 | local pat = slowcat { 1, 2, 3 } 306 | local expected = { 307 | Event(Span(0, 1), Span(0, 1), 1), 308 | Event(Span(1, 2), Span(1, 2), 2), 309 | Event(Span(2, 3), Span(2, 3), 3), 310 | } 311 | assert.same(expected, pat(0, 3)) 312 | end) 313 | end) 314 | 315 | describe("fastcat", function() 316 | it("should alternate between the patterns in the list, all in one cycle", function() 317 | local pat = fastcat { 1, 2, 3 } 318 | local expected = { 319 | Event(Span(0, 1 / 3), Span(0, 1 / 3), 1), 320 | Event(Span(1 / 3, 2 / 3), Span(1 / 3, 2 / 3), 2), 321 | Event(Span(2 / 3, 1), Span(2 / 3, 1), 3), 322 | } 323 | assert.same(expected, pat(0, 1)) 324 | end) 325 | end) 326 | 327 | describe("randcat", function() 328 | it("should randomly alternate between the patterns in the list, all in one cycle", function() 329 | local pat = randcat { 1, 2, 3 } 330 | assert.same(pure(1)(0, 1), pat(0, 1)) 331 | assert.same(pure(2)(1, 2), pat(1, 2)) 332 | assert.same(pure(2)(2, 3), pat(2, 3)) 333 | end) 334 | end) 335 | 336 | describe("stack", function() 337 | it("should stack up the pats to be played together", function() 338 | local pat = stack { pure "bd", pure "sd", pure "hh" } 339 | local expected = { 340 | Event(Span(0, 1), Span(0, 1), "bd"), 341 | Event(Span(0, 1), Span(0, 1), "sd"), 342 | Event(Span(0, 1), Span(0, 1), "hh"), 343 | } 344 | assert.same(expected, pat(0, 1)) 345 | end) 346 | end) 347 | 348 | describe("timecat", function() 349 | it("should return a pattern based one the time-pat 'tuples' passed in", function() 350 | local pat = timecat { 3, fast(4, "bd"), 1, fast(8, "hh") } 351 | local expected = { 352 | Event(Span(0, 3 / 16), Span(0, 3 / 16), "bd"), 353 | Event(Span(3 / 16, 3 / 8), Span(3 / 16, 3 / 8), "bd"), 354 | Event(Span(3 / 8, 9 / 16), Span(3 / 8, 9 / 16), "bd"), 355 | Event(Span(9 / 16, 3 / 4), Span(9 / 16, 3 / 4), "bd"), 356 | Event(Span(3 / 4, 25 / 32), Span(3 / 4, 25 / 32), "hh"), 357 | Event(Span(25 / 32, 13 / 16), Span(25 / 32, 13 / 16), "hh"), 358 | Event(Span(13 / 16, 27 / 32), Span(13 / 16, 27 / 32), "hh"), 359 | Event(Span(27 / 32, 7 / 8), Span(27 / 32, 7 / 8), "hh"), 360 | Event(Span(7 / 8, 29 / 32), Span(7 / 8, 29 / 32), "hh"), 361 | Event(Span(29 / 32, 15 / 16), Span(29 / 32, 15 / 16), "hh"), 362 | Event(Span(15 / 16, 31 / 32), Span(15 / 16, 31 / 32), "hh"), 363 | Event(Span(31 / 32, 1), Span(31 / 32, 1), "hh"), 364 | } 365 | assert.same(expected, pat(0, 1)) 366 | end) 367 | end) 368 | 369 | describe("polymeter", function() 370 | it("should stack up pats with right time compress ratios", function() 371 | local pat = polymeter({ reify { "bd", "sd" }, reify { 1, 2, 3 } }, 2) 372 | local expected1 = stack { reify { "bd", "sd" }, reify { 1, 2 } } 373 | assert.same(expected1(0, 1), pat(0, 1)) 374 | local expected2 = stack { reify { "bd", "sd" }, reify { 3, 1 } } 375 | assert.same(expected2(1, 2), pat(1, 2)) 376 | end) 377 | end) 378 | 379 | describe("fast", function() 380 | it("should return a pattern whose Events closer together in time", function() 381 | local pat = fast(2, pure "bd") 382 | local expected = { 383 | Event(Span(0, 0.5), Span(0, 0.5), "bd"), 384 | Event(Span(0.5, 1), Span(0.5, 1), "bd"), 385 | } 386 | assert.same(expected, pat(0, 1)) 387 | end) 388 | it("should return silence with factor 0", function() 389 | local pat = fast(0, "sd") 390 | assert.same({}, pat(0, 1)) 391 | end) 392 | it("should return reversed with negative factor", function() 393 | local pat = fast(-2, "bd sd") 394 | local expected = rev(fast(2, "bd sd")) 395 | assert.same(expected(0, 1), pat(0, 1)) 396 | end) 397 | end) 398 | 399 | describe("slow", function() 400 | it("should return a pattern whose Events closer together in time", function() 401 | local pat = slow(2, reify { "bd", "sd" }) 402 | local expected = { 403 | Event(Span(0, 1), Span(0, 1), "bd"), 404 | Event(Span(1, 2), Span(1, 2), "sd"), 405 | } 406 | assert.same(expected, pat(0, 2)) 407 | end) 408 | end) 409 | 410 | describe("early", function() 411 | it("should return a pattern whose Events moved backword in time", function() 412 | local pat = early(0.5, reify { "bd", "sd" }) 413 | local expected = reify { "sd", "bd" } 414 | assert.equal(expected, pat) 415 | end) 416 | end) 417 | 418 | describe("fastgap", function() 419 | it("should bring pattern closer together", function() 420 | local pat = fastgap(4, reify { "bd", "sd" }) 421 | local expected = timecat { 1, "bd", 1, "sd", 6, silence } 422 | assert.equal(expected, pat) 423 | end) 424 | end) 425 | 426 | describe("compress", function() 427 | it("should bring pattern closer together", function() 428 | local pat = compress(0.25, 0.75, fastcat { "bd", "sd" }) 429 | local expected = fastcat { silence, "bd", "sd", silence } 430 | assert.equal(expected, pat) 431 | end) 432 | end) 433 | 434 | describe("focus", function() 435 | it("should bring pattern closer together, but leave no gap, and focus can be bigger than a cycle", function() 436 | local pat = focus(1 / 4, 3 / 4, reify { "bd", "sd" }) 437 | local expected = fastcat { "sd", "bd", "sd", "bd" } 438 | assert.equal(expected, pat) 439 | end) 440 | end) 441 | 442 | describe("zoom", function() 443 | it("should play a portion of a pattern", function() 444 | local pat = zoom(1 / 4, 3 / 4, reify { "x", "bd", "sd", "x" }) 445 | local expected = fastcat { "bd", "sd" } 446 | assert.equal(expected, pat) 447 | end) 448 | end) 449 | 450 | describe("degrade_by", function() 451 | it("should randomly drop Events from a pattern", function() 452 | local pat = degradeBy(0.75, fast(8, "sd")) 453 | local expected = { 454 | Event(Span(1 / 8, 1 / 4), Span(1 / 8, 1 / 4), "sd"), 455 | Event(Span(1 / 2, 5 / 8), Span(1 / 2, 5 / 8), "sd"), 456 | Event(Span(3 / 4, 7 / 8), Span(3 / 4, 7 / 8), "sd"), 457 | } 458 | assert.same(expected, pat(0, 1)) 459 | end) 460 | end) 461 | 462 | describe("run", function() 463 | it("should gen 0 - n numbers", function() 464 | local pat = run(3) 465 | local expected = { 466 | Event(Span(0, 1 / 3), Span(0, 1 / 3), 0), 467 | Event(Span(1 / 3, 2 / 3), Span(1 / 3, 2 / 3), 1), 468 | Event(Span(2 / 3, 1), Span(2 / 3, 1), 2), 469 | } 470 | assert.same(expected, pat(0, 1)) 471 | end) 472 | end) 473 | 474 | describe("run", function() 475 | it("should gen 0 - n numbers", function() 476 | local pat = scan(3) 477 | for i = 1, 3 do 478 | assert.same(run(i)(i - 1, i), pat(i - 1, i)) 479 | end 480 | end) 481 | end) 482 | 483 | describe("euclid", function() 484 | it("shoudl gen euclid pats", function() 485 | local pat = euclidRot(3, 8, 1, "bd") 486 | local expected = struct(bjork(3, 8, 1), "bd") 487 | assert.equal(expected, pat) 488 | end) 489 | end) 490 | 491 | describe("off", function() 492 | -- TODO: 493 | it("should offset applying f", function() 494 | local inc1 = function(a) 495 | return a + 1 496 | end 497 | local pat = off(0.5, inc1, 1) 498 | local expected = { 499 | Event(Span(-0.5, 0.5), Span(0, 0.5), 2), 500 | Event(Span(0.5, 1.5), Span(0.5, 1), 2), 501 | Event(Span(0, 1), Span(0, 1), 1), 502 | } 503 | assert.same(expected, pat(0, 1)) 504 | end) 505 | 506 | it("should take string lambda that gets lib funcs env", function() 507 | -- Passed in: 508 | -- (table: 0x559b9be4fd20) { 509 | -- *[1] = (0/1-1/2)-1/1 | 2 510 | -- [2] = 0/1-(1/2-1/1) | 2 511 | -- [3] = (0/1-1/1) | 1 } 512 | -- Expected: 513 | -- (table: 0x559b9be4b780) { 514 | -- *[1] = -1/2-(0/1-1/2) | 2 515 | -- [2] = (1/2-1/1)-3/2 | 2 516 | -- [3] = (0/1-1/1) | 1 } 517 | -- Tidal 518 | -- (0>1/2)|2 519 | -- (0>1)|1 520 | -- (1/2>1)|2 521 | -- local pat = off(0.5, "(+ 1)", 1) 522 | -- local expected = { 523 | -- Event(Span(-0.5, 0.5), Span(0, 0.5), 2), 524 | -- Event(Span(0.5, 1.5), Span(0.5, 1), 2), 525 | -- Event(Span(0, 1), Span(0, 1), 1), 526 | -- } 527 | -- 528 | -- assert.same(expected, pat(0, 1)) 529 | end) 530 | end) 531 | 532 | describe("every", function() 533 | it("should apply f every n cycles", function() 534 | local inc1 = function(a) 535 | return a + 1 536 | end 537 | local pat = every(3, inc1, 1) 538 | local expected = slowcat { 2, 1, 1 } 539 | assert.equal(expected, pat) 540 | end) 541 | 542 | it("should take pattern of functions as second param", function() 543 | local pat = every(3, stack { fast(2), reify "(+ 1)" }, 1) 544 | local expected = stack { slowcat { fast(2, 1), 1, 1 }, slowcat { 2, 1, 1 } } 545 | assert.same(expected(0, 1), pat(0, 1)) 546 | end) 547 | 548 | -- it("should take string lambda that gets lib funcs env", function() 549 | -- local pat = every(3, "fast 2", 1) 550 | -- local expected = slowcat { fast(2, 1), 1, 1 } 551 | -- assert.equal(expected, pat) 552 | -- end) 553 | -- TODO: 554 | -- it("should take mini-notation of functions", function() 555 | -- local pat = every(3, "[(+ 1), (fast 2)]", 1) 556 | -- local expected = stack { slowcat { fast(2, 1), 1, 1 }, slowcat { 2, 1, 1 } } 557 | -- assert.equal(expected, pat) 558 | -- end) 559 | end) 560 | 561 | describe("scale", function() 562 | it("should `quantise` notes in scale", function() 563 | -- gong : { 0, 2, 4, 7, 9 } 564 | local pat = note("1 2 3"):scale "gong" 565 | local expected = note "2 4 7" 566 | assert.equal(expected, pat) 567 | end) 568 | it("should do mod", function() 569 | local pat = note("5 6 7"):scale "gong" 570 | local expected = note "12 14 16" -- 0, 2, 4 + 12 571 | assert.equal(expected, pat) 572 | end) 573 | end) 574 | 575 | describe("struct", function() 576 | it("should give bool struct to pat", function() 577 | assert.equal(reify { 1, 1 }, M.struct({ true, true }, 1)) 578 | assert.equal(reify { 1, silence, 1 }, M.struct({ true, false, true }, 1)) 579 | end) 580 | end) 581 | 582 | describe("ply", function() 583 | it("should repeat every element in the cycle with give times", function() 584 | assert.equal(reify { 1, 1, 1, 2, 2, 2 }, ply(3, reify { 1, 2 })) 585 | end) 586 | end) 587 | 588 | describe("Tidal operators", function() 589 | it("", function() end) 590 | -- it("register ops as pattern methods", function() 591 | -- local pat = n(1)["|>"](s "bd") 592 | -- local expected = op["|>"](n(1), s "bd") 593 | -- assert.equal(expected, pat) 594 | -- end) 595 | end) 596 | 597 | describe("layer", function() 598 | it("", function() 599 | local inc1 = function(x) 600 | return x + 1 601 | end 602 | assert.equal(stack { 2, 1 }, layer({ inc1, id }, 1)) 603 | end) 604 | end) 605 | 606 | -- describe("juxBy", function() 607 | -- it("", function() 608 | -- local expected = stack { s("bd"):pan(0.25), s("bd"):fast(2):pan(0.75) } 609 | -- local actual = juxBy(0.5, fast(2), s "bd") 610 | -- assert.same(expected(0, 1), actual(0, 1)) 611 | -- end) 612 | -- end) 613 | -- 614 | describe("striate", function() 615 | it("", function() 616 | local pat = striate(2, s "bd") 617 | local expected = 618 | fastcat { reify { ["begin"] = 0, ["end"] = 0.5, s = "bd" }, { ["begin"] = 0.5, ["end"] = 1, s = "bd" } } 619 | assert.equal(expected, pat) 620 | end) 621 | end) 622 | 623 | describe("chop", function() 624 | it("", function() 625 | local pat = chop(2, s "bd sd") 626 | local expected = fastcat { 627 | reify { ["begin"] = 0, ["end"] = 0.5, s = "bd" }, 628 | reify { ["begin"] = 0.5, ["end"] = 1, s = "bd" }, 629 | reify { ["begin"] = 0, ["end"] = 0.5, s = "sd" }, 630 | reify { ["begin"] = 0.5, ["end"] = 1, s = "sd" }, 631 | } 632 | assert.equal(expected, pat) 633 | end) 634 | end) 635 | -- 636 | -- describe("loopAt", function() 637 | -- it("", function() 638 | -- local pat = s("bd sd"):chop(2):loopAt(2) 639 | -- local expected = fastcat { 640 | -- reify { ["begin"] = 0, ["end"] = 0.5, s = "bd", speed = 0.5, unit = "c" }, 641 | -- reify { ["begin"] = 0.5, ["end"] = 1, s = "bd", speed = 0.5, unit = "c" }, 642 | -- } 643 | -- assert.equal(expected, pat) 644 | -- end) 645 | -- end) 646 | 647 | describe("fit", function() 648 | it("", function() 649 | local pat = fit(s "bd sd") 650 | local expected = fastcat { 651 | reify { speed = 2, unit = "c", s = "bd" }, 652 | reify { speed = 2, unit = "c", s = "sd" }, 653 | } 654 | assert.equal(expected, pat) 655 | end) 656 | end) 657 | 658 | -- describe("legato", function() 659 | -- it("", function() 660 | -- local pat = legato(2, "bd") 661 | -- local expected = fastcat { 662 | -- reify { speed = 2, unit = "c", s = "bd" }, 663 | -- reify { speed = 2, unit = "c", s = "sd" }, 664 | -- } 665 | -- assert.equal(expected, pat) 666 | -- end) 667 | -- end) 668 | -- 669 | describe("slice", function() 670 | it("", function() 671 | local pat = slice(8, " 0 1", "bd") 672 | local expected = fastcat { 673 | reify { ["begin"] = 0, ["end"] = 1 / 8, s = "bd" }, 674 | reify { ["begin"] = 1 / 8, ["end"] = 1 / 4, s = "bd" }, 675 | } 676 | assert.equal(expected, pat) 677 | end) 678 | end) 679 | -------------------------------------------------------------------------------- /spec/span_spec.lua: -------------------------------------------------------------------------------- 1 | local describe = require("busted").describe 2 | local it = require("busted").it 3 | local assert = require("busted").assert 4 | local Time = require("modal").Time 5 | local Span = require("modal").Span 6 | 7 | describe("new", function() 8 | it("should create with defaults", function() 9 | local t = Span() 10 | assert.are.same(Time(1), t.start) 11 | assert.are.same(Time(1), t.stop) 12 | end) 13 | it("should create with args", function() 14 | local t = Span(3, 4) 15 | assert.are.same(Time(3), t.start) 16 | assert.are.same(Time(4), t.stop) 17 | end) 18 | it("should promote numbers to fractions", function() 19 | local t = Span(0.5, 0.75) 20 | assert.are.same(Time(1, 2), t.start) 21 | assert.are.same(Time(3, 4), t.stop) 22 | end) 23 | end) 24 | describe("spanCycles", function() 25 | it("should break multi cycle span into pieces", function() 26 | local t = Span(3 / 4, 7 / 2) 27 | local spans = t:spanCycles() 28 | assert.are.same(4, #spans) 29 | assert.are.same(Time(3, 4), spans[1].start) 30 | assert.are.same(Time(1, 1), spans[1].stop) 31 | assert.are.same(Time(1, 1), spans[2].start) 32 | assert.are.same(Time(2, 1), spans[2].stop) 33 | assert.are.same(Time(2, 1), spans[3].start) 34 | assert.are.same(Time(3, 1), spans[3].stop) 35 | assert.are.same(Time(3, 1), spans[4].start) 36 | assert.are.same(Time(7, 2), spans[4].stop) 37 | end) 38 | it("should preserve subcycle length spans", function() 39 | local t = Span(1 / 16, 1) 40 | local spans = t:spanCycles() 41 | assert.are.same(1, #spans) 42 | assert.are.same(Time(1, 16), spans[1].start) 43 | assert.are.same(Time(1, 1), spans[1].stop) 44 | end) 45 | end) 46 | describe("duration", function() 47 | it("should return duration of the span", function() 48 | local t = Span(3 / 4, 7 / 2) 49 | assert.are.same(Time(11, 4), t:duration()) 50 | t = Span(6 / 7, 10 / 11) 51 | assert.are.same(Time(4, 77), t:duration()) 52 | end) 53 | end) 54 | describe("midpoint", function() 55 | it("should return the middle point between span begin and end", function() 56 | local t = Span(0, 1) 57 | assert.are.same(Time(1, 2), t:midpoint()) 58 | t = Span(7 / 11, 5 / 4) 59 | assert.are.same(Time(83, 88), t:midpoint()) 60 | end) 61 | end) 62 | describe("cycleSpan", function() 63 | it("should return the span as if it started in cycle 0", function() 64 | local t = Span(5 / 4, 11 / 4) 65 | assert.are.same(Span(1 / 4, 7 / 4), t:cycleSpan()) 66 | end) 67 | end) 68 | describe("equals", function() 69 | it("should compare properties", function() 70 | local t1 = Span(1 / 2, 5 / 4) 71 | local t2 = Span(1 / 2, 5 / 4) 72 | assert.are.same(t1, t2) 73 | t1 = Span(4 / 8, 5 / 4) 74 | t2 = Span(1 / 2, 10 / 8) 75 | assert.are.same(t1, t2) 76 | end) 77 | end) 78 | 79 | describe("withTime", function() 80 | it("should return new span with modified begin time", function() 81 | local add1 82 | add1 = function(frac) 83 | return frac + Time(1) 84 | end 85 | local t = Span(1 / 2, 5 / 6) 86 | local actual = t:withTime(add1) 87 | local expected = Span(3 / 2, 11 / 6) 88 | assert.are.same(expected, actual) 89 | end) 90 | end) 91 | 92 | describe("withEnd", function() 93 | it("should return new span with modified end time", function() 94 | local add1 95 | add1 = function(frac) 96 | return frac + Time(1) 97 | end 98 | local t = Span(1 / 2, 5 / 6) 99 | local actual = t:withEnd(add1) 100 | local expected = Span(1 / 2, 11 / 6) 101 | assert.are.same(expected, actual) 102 | end) 103 | end) 104 | 105 | describe("intersection", function() 106 | it("should return the common timespan between two spans", function() 107 | local ts1 = Span(1 / 2, 5 / 4) 108 | local ts2 = Span(2 / 3, 2 / 2) 109 | assert.are.same(Span(2 / 3, 2 / 2), ts1:sect(ts2)) 110 | assert.are.same(Span(2 / 3, 2 / 2), ts2:sect(ts1)) 111 | ts1 = Span(1 / 2, 5 / 4) 112 | ts2 = Span(5 / 4, 7 / 4) 113 | assert.is_nil(ts1:sect(ts2)) 114 | assert.is_nil(ts2:sect(ts1)) 115 | end) 116 | end) 117 | 118 | describe("intersection_e", function() 119 | it("should return the common timespan between two spans", function() 120 | local ts1 = Span(1 / 2, 5 / 4) 121 | local ts2 = Span(2 / 3, 2 / 2) 122 | assert.are.same(Span(2 / 3, 2 / 2), ts1:sect_e(ts2)) 123 | assert.are.same(Span(2 / 3, 2 / 2), ts2:sect_e(ts1)) 124 | ts1 = Span(1 / 2, 5 / 4) 125 | ts2 = Span(5 / 4, 7 / 4) 126 | assert.has_error(function() 127 | return ts1:sect_e(ts2) 128 | end) 129 | assert.has_error(function() 130 | return ts2:sect_e(ts1) 131 | end) 132 | end) 133 | end) 134 | -------------------------------------------------------------------------------- /spec/stream_spec.lua: -------------------------------------------------------------------------------- 1 | local it = require("busted").it 2 | local assert = require("busted").assert 3 | local describe = require("busted").describe 4 | local busted = require "busted" 5 | local spy = busted.spy 6 | local Clock = require("modal").Clock 7 | local Stream = require("modal").Stream 8 | local s = require("modal").s 9 | 10 | describe("new", function() 11 | it("should construct with SuperDirt target", function() 12 | local stream = Stream() 13 | assert.equal(stream.latency, 0.2) 14 | assert.is_nil(stream.pattern) 15 | end) 16 | end) 17 | 18 | describe("notifyTick", function() 19 | it("should return nil if no pattern", function() 20 | local stream = Stream() 21 | assert.is_nil(stream:notifyTick()) 22 | end) 23 | 24 | it("should call sendf with right value and timestamp", function() 25 | local clock = Clock() 26 | local stream = Stream(clock.callback) 27 | stream.pattern = s "bd" 28 | local stream_spy = spy.on(stream, "callback") 29 | local cps = (clock.sessionState:tempo() / clock.beatsPerCycle) / 60 30 | stream:notifyTick(0, 1, clock.sessionState, cps, clock.beatsPerCycle, 1000000, 0) 31 | assert.spy(stream_spy).was_called() 32 | -- assert.spy(stream_spy).was_called_with() 33 | end) 34 | end) 35 | -------------------------------------------------------------------------------- /spec/tdef_spec.lua: -------------------------------------------------------------------------------- 1 | local describe = require("busted").describe 2 | local M = require("modal").TDef 3 | local it = require("busted").it 4 | local assert = require("busted").assert 5 | 6 | describe("id", function() 7 | it("should return elem", function() 8 | assert.same({ { "a" }, ret = { "a" }, source = "a -> a" }, M "a -> a") 9 | end) 10 | it("should return elem", function() 11 | assert.same( 12 | { { "Time" }, ret = { constructor = "Pattern", "a" }, source = "Time -> Pattern a" }, 13 | M "Time -> Pattern a" 14 | ) 15 | end) 16 | end) 17 | 18 | describe("table", function() 19 | it("should return elem with table tag", function() 20 | assert.same({ { "a", istable = true }, ret = { "a" }, source = "[a] -> a" }, M "[a] -> a") 21 | end) 22 | it("should return list with constructors", function() 23 | -- assert.same({}, M"[Pattern a] -> Pattern a") 24 | end) 25 | end) 26 | 27 | describe("nested def", function() 28 | it("should return def", function() 29 | -- assert.same({ 30 | -- T = { { "a", type = "Table" }, ret = { "a" } }, 31 | -- }, M"(a -> a) -> a") 32 | end) 33 | end) 34 | 35 | describe("def name", function() 36 | it("should return def", function() 37 | assert.same({ { "a" }, ret = { "a" }, name = "id", source = "id :: a -> a" }, M "id :: a -> a") 38 | end) 39 | end) 40 | -------------------------------------------------------------------------------- /spec/time_spec.lua: -------------------------------------------------------------------------------- 1 | local Time = require("modal").Time 2 | local Span = require("modal").Span 3 | local describe = require("busted").describe 4 | local it = require("busted").it 5 | local assert = require("busted").assert 6 | 7 | describe("Time", function() 8 | it("should new with arguments", function() 9 | local f = Time() 10 | assert.equal(f.numerator, 0) 11 | assert.equal(f.denominator, 1) 12 | f = Time(3, 4) 13 | assert.equal(f.numerator, 3) 14 | assert.equal(f.denominator, 4) 15 | f = Time(6, 8) 16 | assert.equal(f.numerator, 3) 17 | assert.equal(f.denominator, 4) 18 | f = Time(-4, 8) 19 | assert.equal(f.numerator, -1) 20 | assert.equal(f.denominator, 2) 21 | f = Time(4, -8) 22 | assert.equal(f.numerator, -1) 23 | assert.equal(f.denominator, 2) 24 | f = Time(-4, -8) 25 | assert.equal(f.numerator, 1) 26 | assert.equal(f.denominator, 2) 27 | f = Time(1.5) 28 | assert.equal(f.numerator, 3) 29 | assert.equal(f.denominator, 2) 30 | f = Time(-1.5) 31 | assert.equal(f.numerator, -3) 32 | assert.equal(f.denominator, 2) 33 | f = Time(0.777) 34 | assert.equal(f.numerator, 777) 35 | assert.equal(f.denominator, 1000) 36 | f = Time(0.0) 37 | assert.equal(f.numerator, 0) 38 | return assert.equal(f.denominator, 1) 39 | end) 40 | it("should throw on divide by zero", function() 41 | return assert.has_error(function() 42 | return Time(1, 0) 43 | end) 44 | end) 45 | it("should add", function() 46 | local f1 = Time(1, 2) 47 | local f2 = Time(1, 2) 48 | assert.equal(f1 + f2, Time(1)) 49 | f1 = Time(1, 2) 50 | f2 = Time(1, 3) 51 | assert.equal(f1 + f2, Time(5, 6)) 52 | f1 = Time(1, 2) 53 | f2 = Time(-1, 3) 54 | return assert.equal(f1 + f2, Time(1, 6)) 55 | end) 56 | it("should subtract", function() 57 | local f1 = Time(1, 2) 58 | local f2 = Time(1, 2) 59 | assert.equal(f1 - f2, Time(0)) 60 | f1 = Time(1, 2) 61 | f2 = Time(1, 3) 62 | assert.equal(f1 - f2, Time(1, 6)) 63 | f1 = Time(1, 2) 64 | f2 = Time(-1, 3) 65 | return assert.equal(f1 - f2, Time(5, 6)) 66 | end) 67 | it("should multiply", function() 68 | local f1 = Time(1, 2) 69 | local f2 = Time(1, 2) 70 | assert.equal(f1 * f2, Time(1, 4)) 71 | f1 = Time(1, 2) 72 | f2 = Time(1, 3) 73 | assert.equal(f1 * f2, Time(1, 6)) 74 | f1 = Time(1, 2) 75 | f2 = Time(-1, 3) 76 | return assert.equal(f1 * f2, Time(-1, 6)) 77 | end) 78 | it("should divide", function() 79 | local f1 = Time(1, 2) 80 | local f2 = Time(1, 2) 81 | assert.equal(f1 / f2, Time(1)) 82 | f1 = Time(1, 2) 83 | f2 = Time(1, 3) 84 | assert.equal(f1 / f2, Time(3, 2)) 85 | f1 = Time(1, 2) 86 | f2 = Time(-1, 3) 87 | return assert.equal(f1 / f2, Time(-3, 2)) 88 | end) 89 | it("should support mod", function() 90 | local f1 = Time(1, 2) 91 | local f2 = Time(2, 3) 92 | assert.equal(f1 % f2, Time(1, 2)) 93 | f1 = Time(3, 4) 94 | f2 = Time(2, 3) 95 | return assert.equal(f1 % f2, Time(1, 12)) 96 | end) 97 | it("should be able to be raised to a power", function() 98 | local f1 = Time(1, 4) 99 | local f2 = Time(1, 2) 100 | assert.equal(f1 ^ f2, 0.5) 101 | f1 = Time(1, 4) 102 | f2 = Time(2, 1) 103 | return assert.equal(f1 ^ f2, Time(1, 16)) 104 | end) 105 | it("should support negative operator", function() 106 | local f1 = Time(1, 4) 107 | return assert.equal(-f1, Time(-1, 4)) 108 | end) 109 | it("should be able to be floored", function() 110 | local f1 = Time(1, 4) 111 | assert.equal(f1:floor(), 0) 112 | f1 = Time(5, 4) 113 | assert.equal(f1:floor(), 1) 114 | f1 = Time(9, 4) 115 | return assert.equal(f1:floor(), 2) 116 | end) 117 | it("should support greater than comparison", function() 118 | assert.is_true(Time(3, 4) > Time(1, 3)) 119 | assert.is_true(Time(5, 4) > Time(1, 1)) 120 | assert.is_false(Time(1, 3) > Time(1, 2)) 121 | return assert.is_false(Time(5, 4) > Time(7, 4)) 122 | end) 123 | it("should support less than comparison", function() 124 | assert.is_true(Time(1, 4) < Time(1, 3)) 125 | assert.is_true(Time(1, 4) < Time(1, 3)) 126 | assert.is_true(Time(5, 4) < Time(7, 3)) 127 | assert.is_false(Time(2, 3) < Time(1, 2)) 128 | return assert.is_false(Time(9, 1) < Time(7, 4)) 129 | end) 130 | it("should support greater than or equal to comparison", function() 131 | assert.is_true(Time(3, 4) >= Time(1, 3)) 132 | assert.is_true(Time(1, 3) >= Time(1, 3)) 133 | assert.is_true(Time(-1, 3) >= Time(-7, 3)) 134 | assert.is_true(Time(5, 4) >= Time(5, 4)) 135 | assert.is_false(Time(1, 3) >= Time(1, 2)) 136 | return assert.is_false(Time(5, 4) >= Time(7, 4)) 137 | end) 138 | it("should support less than or equal to comparison", function() 139 | assert.is_true(Time(1, 4) <= Time(1, 3)) 140 | assert.is_true(Time(1, 4) <= Time(1, 4)) 141 | assert.is_true(Time(5, 4) <= Time(7, 3)) 142 | assert.is_true(Time(-5, 4) <= Time(7, 3)) 143 | assert.is_false(Time(2, 3) <= Time(1, 2)) 144 | return assert.is_false(Time(9, 1) <= Time(7, 4)) 145 | end) 146 | it("should support equal to comparison", function() 147 | assert.is_true(Time(1, 4) == Time(1, 4)) 148 | assert.is_true(Time(5, 4) == Time(10, 8)) 149 | assert.is_true(Time(-2, 3) == Time(8, -12)) 150 | assert.is_true(Time(-1, 3) == Time(-3, 9)) 151 | return assert.is_false(Time(254, 255) == Time(255, 256)) 152 | end) 153 | it("should support min", function() 154 | assert.equal(Time(3, 4):min(Time(5, 6)), Time(3, 4)) 155 | assert.equal(Time(3, 4):min(Time(3, 6)), Time(3, 6)) 156 | assert.equal(Time(3, 4):min(Time(-5, 6)), Time(-5, 6)) 157 | return assert.equal(Time(-3, 4):min(Time(-5, 6)), Time(-5, 6)) 158 | end) 159 | it("should support max", function() 160 | assert.equal(Time(3, 4):max(Time(5, 6)), Time(5, 6)) 161 | assert.equal(Time(3, 4):max(Time(3, 6)), Time(3, 4)) 162 | assert.equal(Time(3, 4):max(Time(-5, 6)), Time(3, 4)) 163 | return assert.equal(Time(-3, 4):max(Time(-5, 6)), Time(-3, 4)) 164 | end) 165 | return it("should show string representation", function() 166 | return assert.equal(Time(1, 2):show(), "1/2") 167 | end) 168 | end) 169 | 170 | describe("wholeCycle", function() 171 | it("should return the large cycle that contains the span", function() 172 | local f1 = Time(1, 2) 173 | local actual = f1:wholeCycle() 174 | local expected = Span(0, 1) 175 | assert.same(expected, actual) 176 | local f2 = Time(3, 2) 177 | actual = f2:wholeCycle() 178 | expected = Span(1, 2) 179 | assert.same(expected, actual) 180 | end) 181 | end) 182 | 183 | describe("cyclePos", function() 184 | it("should return the position within the cycle as a proper fraction", function() 185 | local f1 = Time(7, 2) 186 | local actual = f1:cyclePos() 187 | local expected = Time(1, 2) 188 | assert.same(expected, actual) 189 | end) 190 | end) 191 | -------------------------------------------------------------------------------- /spec/util_spec.lua: -------------------------------------------------------------------------------- 1 | local M = require("modal").ut 2 | local drawLine = require("modal").drawLine 3 | local pat = require "modal" 4 | local describe = require("busted").describe 5 | local it = require("busted").it 6 | local assert = require("busted").assert 7 | M.Usecolor = false 8 | 9 | describe("compare", function() 10 | return it("should deeply compare table values", function() 11 | local table1 = { first = "red fish", second = { low = 5 } } 12 | local table2 = { first = "red fish", second = { low = 5 } } 13 | local table3 = { first = "blue fish", second = { low = 5 } } 14 | local table4 = { first = "red fish", second = { low = 6 } } 15 | local table5 = {} 16 | local table6 = {} 17 | assert.is_true(M.compare(table1, table2)) 18 | assert.is_true(M.compare(table5, table6)) 19 | assert.is_false(M.compare(table1, table3)) 20 | assert.is_False(M.compare(table1, table4)) 21 | return assert.is_False(M.compare(table1, table5)) 22 | end) 23 | end) 24 | 25 | describe("compare", function() 26 | it("should split list by index", function() 27 | local fst, lst = M.splitAt(3, { 1, 2, 3, 4, 5, 6 }) 28 | assert.same({ 1, 2, 3 }, fst) 29 | assert.same({ 4, 5, 6 }, lst) 30 | end) 31 | end) 32 | 33 | describe("rotate", function() 34 | it("should split list by index", function() 35 | local res = M.rotate(3, { 1, 2, 3, 4, 5, 6 }) 36 | assert.same({ 4, 5, 6, 1, 2, 3 }, res) 37 | end) 38 | end) 39 | 40 | describe("union", function() 41 | it("should union 2 map", function() 42 | local res = M.union({ s = "bd", room = 0.3 }, { delay = 0.3 }) 43 | assert.same({ s = "bd", room = 0.3, delay = 0.3 }, res) 44 | end) 45 | end) 46 | 47 | describe("concat", function() 48 | it("should concat 2 lists", function() 49 | local res = M.concat({ "bd", 0.3 }, { 0.4 }) 50 | assert.same({ "bd", 0.3, 0.4 }, res) 51 | end) 52 | end) 53 | 54 | describe("flatten", function() 55 | it("should flatten 2 lists", function() 56 | local res = M.flatten { { { 1 }, 2, 3 }, 4 } 57 | assert.same({ 1, 2, 3, 4 }, res) 58 | end) 59 | end) 60 | 61 | -- describe("string lambda", function() 62 | -- it("should parse lambdas", function() 63 | -- local res = M.string_lambda(_G) " x -> x + 1"(2) 64 | -- assert.same(3, res) 65 | -- end) 66 | -- it("should do tidal funcs", function() 67 | -- local res = M.string_lambda(pat) " x -> x:fast(2)"(pat.pure(2)) 68 | -- assert.same(pat.pure(2):fast(2)(0, 1), res(0, 1)) 69 | -- end) 70 | -- it("should parse tidal ops", function() 71 | -- local f = M.string_lambda(pat) "|+ n 1" 72 | -- -- TODO: 73 | -- end) 74 | -- end) 75 | 76 | describe("auto curry", function() 77 | it("shoudl do felixable curry", function() 78 | assert.same(pat.fast(2, 1)(0, 1), pat.fast(2)(1)(0, 1)) 79 | assert.same(pat.fast(2, 1)(0, 1), pat.fast(2)(1)(0, 1)) 80 | end) 81 | end) 82 | 83 | describe("get_args", function() 84 | it("should print the name of function's args", function() 85 | assert.same( 86 | { "x", "y" }, 87 | M.get_args(function(x, y) 88 | return x + y 89 | end) 90 | ) 91 | end) 92 | end) 93 | 94 | describe("drawLine", function() 95 | it("should return a line representation of pattern", function() 96 | assert.same("|aaa|aaa", drawLine(pat.fast(3, "a"), 6)) 97 | end) 98 | end) 99 | 100 | describe("reduce", function() 101 | it("should foldl", function() 102 | assert.same( 103 | 10, 104 | M.reduce(function(a, b) 105 | return a + b 106 | end, 0, { 1, 2, 3, 4 }) 107 | ) 108 | end) 109 | end) 110 | -------------------------------------------------------------------------------- /spec/valuemap_spec.lua: -------------------------------------------------------------------------------- 1 | local describe = require("busted").describe 2 | local it = require("busted").it 3 | local assert = require("busted").assert 4 | 5 | require "modal"() 6 | 7 | assert.pat = function(a, b) 8 | assert.same(a(0, 1), b(0, 1)) 9 | end 10 | 11 | describe("table arith", function() 12 | it("", function() 13 | -- assert.pat(reify { pan = 0.2 }, reify { pan = 0.1 } + 0.1) 14 | -- assert.pat(reify { pan = 0.2 }, reify { pan = 0.1 } + pure(0.1)) 15 | -- assert.pat(reify { note = 2, pan = 0.2 }, reify { note = 1, pan = 0.1 } + { note = 1, pan = 0.1 }) 16 | -- assert.pat( 17 | -- reify { s = "bd", note = 2, pan = 0.2 }, 18 | -- reify { s = "bd", note = 1, pan = 0.1 } + { note = 1, pan = 0.1 } 19 | -- ) 20 | assert.same(ValueMap { pan = 0.2 }, ValueMap { pan = 0.1 } + ValueMap { pan = 0.1 }) 21 | end) 22 | it("does chain", function() 23 | assert.same(ValueMap { pan = 3 }, ValueMap { pan = 1 } + ValueMap { pan = 1 } + ValueMap { pan = 1 }) 24 | end) 25 | end) 26 | 27 | describe("control", function() 28 | it("", function() 29 | assert.pat(reify { note = 1 }, n(1)) 30 | assert.pat(reify { note = 1, s = "bd" }, n(1):s "bd") 31 | end) 32 | end) 33 | 34 | -- describe("note", function() 35 | -- it("shoudl parse chords", function() 36 | -- ---TODO: 37 | -- assert.pat(stack { n(0), n(4), n(7) }, note "c'maj") 38 | -- end) 39 | -- end) 40 | -------------------------------------------------------------------------------- /src/a2s.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2006-2013 Fabien Fleutot and others. 2 | local a2s = {} 3 | a2s.__index = a2s 4 | 5 | local tconcat = table.concat 6 | local str_match = string.match 7 | local str_format = string.format 8 | local unpack = unpack or rawget(table, "unpack") 9 | 10 | -- TODO: check AST 11 | 12 | -- Instanciate a new AST->source synthetizer 13 | function a2s.new() 14 | local self = { 15 | _acc = {}, -- Accumulates pieces of source as strings 16 | current_indent = 0, -- Current level of line indentation 17 | indent_step = " ", -- Indentation symbol, normally spaces or '\t' 18 | } 19 | return setmetatable(self, a2s) 20 | end 21 | 22 | -------------------------------------------------------------------------------- 23 | -- Run a synthetizer on the `ast' arg and return the source as a string. 24 | -- Can also be used as a static method `M.run (ast)'; in this case, 25 | -- a temporary Metizer is instanciated on the fly. 26 | -------------------------------------------------------------------------------- 27 | function a2s:run(ast) 28 | if not ast then 29 | self, ast = a2s.new(), self 30 | end 31 | self._acc = {} 32 | self:node(ast) 33 | return tconcat(self._acc) 34 | end 35 | 36 | -------------------------------------------------------------------------------- 37 | -- Accumulate a piece of source file in the synthetizer. 38 | -------------------------------------------------------------------------------- 39 | function a2s:acc(x) 40 | if x then 41 | self._acc[#self._acc + 1] = x 42 | end 43 | end 44 | 45 | -------------------------------------------------------------------------------- 46 | -- Accumulate an indented newline. 47 | -- Jumps an extra line if indentation is 0, so that 48 | -- toplevel definitions are separated by an extra empty line. 49 | -------------------------------------------------------------------------------- 50 | function a2s:nl() 51 | if self.current_indent == 0 then 52 | self:acc "\n" 53 | end 54 | self:acc("\n" .. self.indent_step:rep(self.current_indent)) 55 | end 56 | 57 | -------------------------------------------------------------------------------- 58 | -- Increase indentation and accumulate a new line. 59 | -------------------------------------------------------------------------------- 60 | function a2s:nlindent() 61 | self.current_indent = self.current_indent + 1 62 | self:nl() 63 | end 64 | 65 | -------------------------------------------------------------------------------- 66 | -- Decrease indentation and accumulate a new line. 67 | -------------------------------------------------------------------------------- 68 | function a2s:nldedent() 69 | self.current_indent = self.current_indent - 1 70 | self:acc("\n" .. self.indent_step:rep(self.current_indent)) 71 | end 72 | 73 | -------------------------------------------------------------------------------- 74 | -- Keywords, which are illegal as identifiers. 75 | -------------------------------------------------------------------------------- 76 | local keywords_list = { 77 | "and", 78 | "break", 79 | "do", 80 | "else", 81 | "elseif", 82 | "end", 83 | "false", 84 | "for", 85 | "function", 86 | "if", 87 | "in", 88 | "local", 89 | "nil", 90 | "not", 91 | "or", 92 | "repeat", 93 | "return", 94 | "then", 95 | "true", 96 | "until", 97 | "while", 98 | } 99 | local keywords = {} 100 | for _, kw in pairs(keywords_list) do 101 | keywords[kw] = true 102 | end 103 | 104 | -------------------------------------------------------------------------------- 105 | -- Return true iff string `id' is a legal identifier name. 106 | -------------------------------------------------------------------------------- 107 | local function is_ident(id) 108 | return str_match(id, "^[%a_][%w_]*$") and not keywords[id] 109 | end 110 | 111 | -- Return true iff ast represents a legal function name for 112 | -- syntax sugar ``function foo.bar.gnat() ... end'': 113 | -- a series of nested string indexes, with an identifier as 114 | -- the innermost node. 115 | local function is_idx_stack(ast) 116 | local tag = ast.tag 117 | if tag == "Index" then 118 | return is_idx_stack(ast[1]) 119 | elseif tag == "Id" then 120 | return true 121 | else 122 | return false 123 | end 124 | end 125 | 126 | -------------------------------------------------------------------------------- 127 | -- Operator precedences, in increasing order. 128 | -- This is not directly used, it's used to generate op_prec below. 129 | -------------------------------------------------------------------------------- 130 | local op_preprec = { 131 | { "or", "and" }, 132 | { "lt", "le", "eq", "ne" }, 133 | { "concat" }, 134 | { "add", "sub" }, 135 | { "mul", "div", "mod" }, 136 | { "unm", "unary", "not", "len" }, ---TODO: 137 | { "pow" }, 138 | { "index" }, 139 | } 140 | 141 | -------------------------------------------------------------------------------- 142 | -- operator --> precedence table, generated from op_preprec. 143 | -------------------------------------------------------------------------------- 144 | local op_prec = {} 145 | 146 | for prec, ops in ipairs(op_preprec) do 147 | for _, op in ipairs(ops) do 148 | op_prec[op] = prec 149 | end 150 | end 151 | 152 | -------------------------------------------------------------------------------- 153 | -- operator --> source representation. 154 | -------------------------------------------------------------------------------- 155 | local op_symbol = { 156 | add = " + ", 157 | sub = " - ", 158 | mul = " * ", 159 | div = " / ", 160 | mod = " % ", 161 | pow = " ^ ", 162 | concat = " .. ", 163 | eq = " == ", 164 | ne = " ~= ", 165 | lt = " < ", 166 | le = " <= ", 167 | ["and"] = " and ", 168 | ["or"] = " or ", 169 | ["not"] = "not ", 170 | len = "# ", 171 | unm = "-", 172 | } 173 | -- Accumulate the source representation of AST `node' in 174 | -- the synthetizer. Most of the work is done by delegating to 175 | -- the method having the name of the AST tag. 176 | -- If something can't be converted to normal sources, it's 177 | -- instead dumped as a `-{ ... }' splice in the source accumulator. 178 | function a2s:node(node) 179 | assert(self ~= a2s and self._acc, "wrong ast_to_src compiler?") 180 | if node == nil then 181 | self:acc "<>" 182 | return 183 | end 184 | if not node.tag then -- tagless block. 185 | self:list(node, self.nl) 186 | else 187 | local f = a2s[node.tag] 188 | if type(f) == "function" then -- Delegate to tag method. 189 | f(self, node, unpack(node)) 190 | elseif type(f) == "string" then -- tag string. 191 | self:acc(f) 192 | end 193 | end 194 | end 195 | 196 | -------------------------------------------------------------------------------- 197 | -- Convert every node in the AST list `list' passed as 1st arg. 198 | -- `sep' is an optional separator to be accumulated between each list element, 199 | -- it can be a string or a synth method. 200 | -- `start' is an optional number (default == 1), indicating which is the 201 | -- first element of list to be converted, so that we can skip the begining 202 | -- of a list. 203 | -------------------------------------------------------------------------------- 204 | function a2s:list(list, sep, start) 205 | for i = start or 1, #list do 206 | self:node(list[i]) 207 | if list[i + 1] then 208 | if not sep then 209 | return -- HACK: 210 | elseif type(sep) == "function" then 211 | sep(self) 212 | elseif type(sep) == "string" then 213 | self:acc(sep) 214 | else 215 | error "Invalid list separator" 216 | end 217 | end 218 | end 219 | end 220 | 221 | -------------------------------------------------------------------------------- 222 | -- 223 | -- Tag methods. 224 | -- ------------ 225 | -- 226 | -- Specific AST node dumping methods, associated to their node kinds 227 | -- by their name, which is the corresponding AST tag. 228 | -- synth:node() is in charge of delegating a node's treatment to the 229 | -- appropriate tag method. 230 | -- 231 | -- Such tag methods are called with the AST node as 1st arg. 232 | -- As a convenience, the n node's children are passed as args #2 ... n+1. 233 | -- 234 | -- There are several things that could be refactored into common subroutines 235 | -- here: statement blocks dumping, function dumping... 236 | -- However, given their small size and linear execution 237 | -- (they basically perform series of :acc(), :node(), :list(), 238 | -- :nl(), :nlindent() and :nldedent() calls), it seems more readable 239 | -- to avoid multiplication of such tiny functions. 240 | -- 241 | -- To make sense out of these, you need to know metalua's AST syntax, as 242 | -- found in the reference manual or in metalua/doc/ast.txt. 243 | -- 244 | -------------------------------------------------------------------------------- 245 | 246 | function a2s:Chunk(node) 247 | -- TODO: check ret last 248 | for _, v in ipairs(node) do 249 | self:node(v) 250 | self:acc "; " 251 | end 252 | end 253 | 254 | function a2s:Do(node) 255 | self:acc "do" 256 | self:nlindent() 257 | self:list(node, self.nl) 258 | self:nldedent() 259 | self:acc "end" 260 | end 261 | 262 | function a2s:Set(node) 263 | local lhs = node[1] 264 | local rhs = node[2] 265 | -- ``function foo:bar(...) ... end'' -- 266 | if 267 | lhs[1].tag == "Index" 268 | and rhs[1].tag == "Function" 269 | and rhs[1][1][1] == "self" 270 | and is_idx_stack(lhs) 271 | and is_ident(lhs[1][2][1]) 272 | then 273 | local method = lhs[1][2][1] 274 | local params = rhs[1][1] 275 | local body = rhs[1][2] 276 | self:acc "function " 277 | self:node(lhs) 278 | self:acc ":" 279 | self:acc(method) 280 | self:acc "(" 281 | self:list(params, ", ", 2) 282 | self:acc ")" 283 | self:nlindent() 284 | self:list(body, self.nl) 285 | self:nldedent() 286 | self:acc "end" 287 | elseif rhs[1].tag == "Function" and is_idx_stack(lhs) then 288 | -- | `Set{ { lhs }, { `Function{ params, body } } } if is_idx_stack (lhs) -> 289 | -- ``function foo(...) ... end'' -- 290 | local params = rhs[1][1] 291 | local body = rhs[1][2] 292 | self:acc "function " 293 | self:node(lhs) 294 | self:acc "(" 295 | self:list(params, ", ") 296 | self:acc ")" 297 | self:nlindent() 298 | self:list(body, self.nl) 299 | self:nldedent() 300 | self:acc "end" 301 | else 302 | self:list(lhs, ", ") 303 | self:acc " = " 304 | self:list(rhs, ", ") 305 | end 306 | end 307 | 308 | function a2s:While(_, cond, body) 309 | self:acc "while " 310 | self:node(cond) 311 | self:acc " do" 312 | self:nlindent() 313 | self:list(body, self.nl) 314 | self:nldedent() 315 | self:acc "end" 316 | end 317 | 318 | function a2s:Repeat(_, body, cond) 319 | self:acc "repeat" 320 | self:nlindent() 321 | self:list(body, self.nl) 322 | self:nldedent() 323 | self:acc "until " 324 | self:node(cond) 325 | end 326 | 327 | function a2s:If(node) 328 | for i = 1, #node - 1, 2 do 329 | -- for each ``if/then'' and ``elseif/then'' pair -- 330 | local cond, body = node[i], node[i + 1] 331 | self:acc(i == 1 and "if " or "elseif ") 332 | self:node(cond) 333 | self:acc " then" 334 | self:nlindent() 335 | self:list(body, self.nl) 336 | self:nldedent() 337 | end 338 | -- odd number of children --> last one is an `else' clause -- 339 | if #node % 2 == 1 then 340 | self:acc "else" 341 | self:nlindent() 342 | self:list(node[#node], self.nl) 343 | self:nldedent() 344 | end 345 | self:acc "end" 346 | end 347 | 348 | function a2s:Fornum(node, var, first, last) 349 | local body = node[#node] 350 | self:acc "for " 351 | self:node(var) 352 | self:acc " = " 353 | self:node(first) 354 | self:acc ", " 355 | self:node(last) 356 | if #node == 5 then -- 5 children --> child #4 is a step increment. 357 | self:acc ", " 358 | self:node(node[4]) 359 | end 360 | self:acc " do" 361 | self:nlindent() 362 | self:list(body, self.nl) 363 | self:nldedent() 364 | self:acc "end" 365 | end 366 | 367 | function a2s:Forin(_, vars, generators, body) 368 | self:acc "for " 369 | self:list(vars, ", ") 370 | self:acc " in " 371 | self:list(generators, ", ") 372 | self:acc " do" 373 | self:nlindent() 374 | self:list(body, self.nl) 375 | self:nldedent() 376 | self:acc "end" 377 | end 378 | 379 | function a2s:Local(_, lhs, rhs, annots) 380 | self:acc "local " 381 | if annots then 382 | local n = #lhs 383 | for i = 1, n do 384 | self:node(lhs) 385 | local a = annots[i] 386 | if a then 387 | self:acc " #" 388 | self:node(a) 389 | end 390 | if i ~= n then 391 | self:acc ", " 392 | end 393 | end 394 | else 395 | self:list(lhs, ", ") 396 | end 397 | if rhs[1] then 398 | self:acc " = " 399 | self:list(rhs, ", ") 400 | end 401 | end 402 | 403 | function a2s:Localrec(_, lhs, rhs) 404 | -- ``local function name() ... end'' -- 405 | self:acc "local function " 406 | self:acc(lhs[1][1]) 407 | self:acc "(" 408 | self:list(rhs[1][1], ", ") 409 | self:acc ")" 410 | self:nlindent() 411 | self:list(rhs[1][2], self.nl) 412 | self:nldedent() 413 | self:acc "end" 414 | end 415 | 416 | function a2s:Call(node, f) 417 | -- TODO: wrong paren condition... 418 | local parens 419 | if node[2].tag == "String" or node[2].tag == "Table" then 420 | parens = false 421 | else 422 | parens = true 423 | end 424 | self:node(f) 425 | -- self:acc(parens and "(" or " ") 426 | self:acc "(" 427 | self:list(node, ", ", 2) -- skip `f'. 428 | self:acc ")" 429 | -- self:acc(parens and ")") 430 | end 431 | 432 | function a2s:Invoke(node, f, method) 433 | -- single string or table literal arg ==> no need for parentheses. -- 434 | local parens 435 | if node[2].tag == "String" or node[2].tag == "Table" then 436 | parens = false 437 | else 438 | parens = true 439 | end 440 | self:node(f) 441 | self:acc ":" 442 | self:acc(method[1]) 443 | self:acc(parens and "(" or " ") 444 | self:list(node, ", ", 3) -- Skip args #1 and #2, object and method name. 445 | self:acc(parens and ")") 446 | end 447 | 448 | function a2s:Return(node) 449 | self:acc "return " 450 | self:list(node, ", ") 451 | end 452 | 453 | a2s.Break = "break" 454 | a2s.Nil = "nil" 455 | a2s.False = "false" 456 | a2s.True = "true" 457 | a2s.Dots = "..." 458 | 459 | function a2s:Number(_, n) 460 | self:acc(tostring(n)) 461 | end 462 | 463 | function a2s:String(_, str) 464 | -- format "%q" prints '\n' in an umpractical way IMO, 465 | -- so this is fixed with the :gsub( ) call. 466 | self:acc(str_format("%q", str):gsub("\\\n", "\\n")) 467 | end 468 | 469 | function a2s:Function(_, params, body, annots) 470 | self:acc "function(" 471 | if annots then 472 | local n = #params 473 | for i = 1, n do 474 | local p, a = params[i], annots[i] 475 | self:node(p) 476 | if annots then 477 | self:acc " #" 478 | self:node(a) 479 | end 480 | if i ~= n then 481 | self:acc ", " 482 | end 483 | end 484 | else 485 | self:list(params, ", ") 486 | end 487 | self:acc ")" 488 | self:nlindent() 489 | self:list(body, self.nl) 490 | self:nldedent() 491 | self:acc "end" 492 | end 493 | 494 | function a2s:Table(node) 495 | if not node[1] then 496 | self:acc "{ }" 497 | else 498 | self:acc "{ " 499 | for i, elem in ipairs(node) do 500 | if elem.tag == "Pair" then 501 | -- `Pair{ `String{ key }, value } 502 | if elem[1].tag == "String" and is_ident(elem[1][1]) then 503 | self:acc(elem[1][1]) 504 | self:acc " = " 505 | self:node(elem[2]) 506 | else 507 | self:acc "[" 508 | self:node(elem[1]) 509 | self:acc "] = " 510 | self:node(elem[2]) 511 | end 512 | else 513 | self:node(elem) 514 | end 515 | if node[i + 1] then 516 | self:acc ", " 517 | end 518 | end 519 | self:acc " }" 520 | end 521 | end 522 | 523 | -- TODO: understand associatitivity 524 | function a2s:Op(node, op, a, b) 525 | if op == "not" and (node[2][1][1] == "eq") then ---TODO:??? 526 | op, a, b = "ne", node[2][1][2], node[2][1][3] 527 | end 528 | if b then -- binary operator. 529 | local left_paren, right_paren 530 | if a.tag == "Op" and op_prec[op] >= op_prec[a[1]] then 531 | left_paren = true 532 | else 533 | left_paren = false 534 | end 535 | if b.tag == "Op" and op_prec[op] >= op_prec[b[1]] then 536 | right_paren = true 537 | else 538 | right_paren = false 539 | end 540 | self:acc(left_paren and "(") 541 | self:node(a) 542 | self:acc(left_paren and ")") 543 | 544 | self:acc(op_symbol[op]) 545 | 546 | self:acc(right_paren and "(") 547 | self:node(b) 548 | self:acc(right_paren and ")") 549 | else -- unary operator. 550 | local paren 551 | if a.tag == "Op" and op_prec[op] >= op_prec[a[1]] then 552 | paren = true 553 | else 554 | paren = false 555 | end 556 | self:acc(op_symbol[op]) 557 | self:acc(paren and "(") 558 | self:node(a) 559 | self:acc(paren and ")") 560 | end 561 | end 562 | 563 | function a2s:Paren(_, content) 564 | self:acc "(" 565 | self:node(content) 566 | self:acc ")" 567 | end 568 | 569 | function a2s:Index(_, table, key) 570 | local paren_table 571 | if table.tag == "Op" and op_prec[table[1][1]] < op_prec.index then 572 | paren_table = true 573 | else 574 | paren_table = false 575 | end 576 | 577 | self:acc(paren_table and "(") 578 | self:node(table) 579 | self:acc(paren_table and ")") 580 | 581 | -- ``table [key]'' 582 | if key.tag == "String" and is_ident(key[1]) then 583 | self:acc "." 584 | self:acc(key[1]) 585 | else 586 | self:acc "[" 587 | self:node(key) 588 | self:acc "]" 589 | -- ``table.key'' 590 | end 591 | end 592 | 593 | function a2s:Id(_, name) 594 | if is_ident(name) then 595 | self:acc(name) 596 | else 597 | error "invalid identifier" 598 | end 599 | end 600 | 601 | function a2s:Goto(node, name) 602 | self:acc "goto " 603 | if type(name) == "string" then 604 | self:Id(node, name) 605 | else 606 | self:Id(node[1], node[1][1]) 607 | end 608 | end 609 | 610 | function a2s:Label(node, name) 611 | self:acc "::" 612 | if type(name) == "string" then 613 | self:Id(node, name) 614 | else 615 | self:Id(node[1], node[1][1]) 616 | end 617 | self:acc "::" 618 | end 619 | 620 | return a2s 621 | -------------------------------------------------------------------------------- /src/clock.lua: -------------------------------------------------------------------------------- 1 | local has_al, al = pcall(require, "abletonlink") 2 | local losc = require "losc"() 3 | _G.struct = nil 4 | local types = require "types" 5 | local Stream = types.Stream 6 | local uv = require "luv" 7 | 8 | local floor = math.floor 9 | local type = type 10 | local pairs = pairs 11 | 12 | local sleep = function(sec) 13 | return os.execute("sleep " .. sec) 14 | end 15 | 16 | local target = { 17 | name = "SuperDirt", 18 | address = "127.0.0.1", 19 | port = 57120, 20 | latency = 0.2, 21 | handshake = true, 22 | } 23 | 24 | local typeMap = { table = "b", number = "f", string = "s" } 25 | 26 | local typesString = function(msg) 27 | local ts = "" 28 | for i = 1, #msg do 29 | local x = msg[i] 30 | if typeMap[type(x)] then 31 | ts = ts .. typeMap[type(x)] 32 | else 33 | ts = ts .. "b" 34 | end 35 | end 36 | return ts 37 | end 38 | 39 | local Timetag = losc.Timetag 40 | local Pattern = losc.Pattern 41 | local Packet = losc.Packet 42 | 43 | local udp = {} 44 | udp.__index = udp 45 | --- Fractional precision for bundle scheduling. 46 | -- 1000 is milliseconds. 1000000 is microseconds etc. Any precision is valid 47 | -- that makes sense for the plugin's scheduling function. 48 | udp.precision = 1000 49 | 50 | --- Create a new instance. 51 | -- @tparam[options] table options Options. 52 | -- @usage local udp = plugin.new() 53 | -- @usage 54 | -- local udp = plugin.new { 55 | -- sendAddr = '127.0.0.1', 56 | -- sendPort = 9000, 57 | -- recvAddr = '127.0.0.1', 58 | -- recvPort = 8000, 59 | -- ignore_late = true, -- ignore late bundles 60 | -- } 61 | function udp.new(options) 62 | local self = setmetatable({}, udp) 63 | self.options = options or {} 64 | self.handle = uv.new_udp "inet" 65 | assert(self.handle, "Could not create UDP handle.") 66 | return self 67 | end 68 | 69 | --- Create a Timetag with the current time. 70 | -- Precision is in milliseconds. 71 | -- @return Timetag object with current time. 72 | function udp:now() -- luacheck: ignore 73 | local s, m = uv.gettimeofday() 74 | return Timetag.new(s, m / udp.precision) 75 | end 76 | 77 | --- Schedule a OSC method for dispatch. 78 | -- 79 | -- @tparam number timestamp When to schedule the bundle. 80 | -- @tparam function handler The OSC handler to call. 81 | function udp:schedule(timestamp, handler) -- luacheck: ignore 82 | timestamp = math.max(0, timestamp) 83 | if timestamp > 0 then 84 | local timer = uv.new_timer() 85 | timer:start(timestamp, 0, handler) 86 | else 87 | handler() 88 | end 89 | end 90 | 91 | --- Start UDP server. 92 | -- This function is blocking. 93 | -- @tparam string host IP address (e.g. '127.0.0.1'). 94 | -- @tparam number port The port to listen on. 95 | function udp:open(host, port) 96 | host = host or self.options.recvAddr 97 | port = port or self.options.recvPort 98 | self.handle:bind(host, port, { reuseaddr = true }) 99 | self.handle:recv_start(function(err, data, addr) 100 | assert(not err, err) 101 | if data then 102 | self.remote_info = addr 103 | local ok, errormsg = pcall(Pattern.dispatch, data, self) 104 | if not ok then 105 | print(errormsg) 106 | end 107 | end 108 | end) 109 | -- updated if port 0 is passed in as default (chooses a random port) 110 | self.options.recvPort = self.handle:getsockname().port 111 | end 112 | 113 | function udp:run_non_blocking() 114 | -- Run the event loop once and return 115 | uv.run "nowait" 116 | end 117 | 118 | --- Close UDP server. 119 | function udp:close() 120 | self.handle:recv_stop() 121 | if not self.handle:is_closing() then 122 | self.handle:close() 123 | end 124 | uv.walk(uv.close) 125 | end 126 | 127 | --- Send a OSC packet. 128 | -- @tparam table packet The packet to send. 129 | -- @tparam[opt] string address The IP address to send to. 130 | -- @tparam[opt] number port The port to send to. 131 | function udp:send(packet, address, port) 132 | address = address or self.options.sendAddr 133 | port = port or self.options.sendPort 134 | packet = assert(Packet.pack(packet)) 135 | self.handle:try_send(packet, address, port) 136 | end 137 | 138 | local osc, sendOSC 139 | local plugin = udp.new { 140 | -- recvAddr = "127.0.0.1", 141 | -- recvPort = 6010, 142 | sendPort = target.port, 143 | sendAddr = target.address, 144 | -- ignore_late = true, -- ignore late bundles 145 | } 146 | osc = losc.new { plugin = plugin } 147 | 148 | sendOSC = function(value, ts) 149 | local msg = {} 150 | for key, val in pairs(value) do 151 | msg[#msg + 1] = key 152 | msg[#msg + 1] = val 153 | end 154 | msg.types = typesString(msg) 155 | msg.address = "/dirt/play" 156 | local b = osc.new_message(msg) 157 | -- local b = osc.new_bundle(ts, osc.new_message(msg)) 158 | osc:send(b) 159 | end 160 | 161 | State = {} 162 | 163 | osc:add_handler("/ctrl", function(data) 164 | State[1] = data.message[2] 165 | -- print(State[1]) 166 | -- print(ut.dump(data)) 167 | end) 168 | 169 | osc:add_handler("/dirt/handshake/reply", function(data) 170 | print(ut.dump(data)) 171 | end) 172 | 173 | local mt = { __class = "clock" } 174 | 175 | function mt:start() 176 | if not self.running then 177 | self.running = true 178 | -- osc:open() -- ??? 179 | return self:createNotifyCoroutine() 180 | end 181 | end 182 | 183 | function mt:stop() 184 | self.running = false 185 | print "Clock: stopped" 186 | end 187 | 188 | function mt:subscribe(key, pattern) 189 | if not self.subscribers[key] then 190 | self.subscribers[key] = Stream(self.callback) 191 | end 192 | self.subscribers[key].pattern = pattern 193 | end 194 | 195 | function mt:unsubscribe(key) 196 | self.subscribers[key] = nil 197 | end 198 | 199 | function mt:setbpm(bpm) 200 | self.sessionState:set_tempo(bpm, 0) 201 | self.link:commit_audio_session_state(self.sessionState) 202 | end 203 | 204 | function mt:setcps(cps) 205 | self.sessionState:set_tempo(cps * self.beatsPerCycle * 60, 0) 206 | self.link:commit_audio_session_state(self.sessionState) 207 | end 208 | 209 | function mt:createNotifyCoroutine() 210 | self.co = coroutine.create(function(f) 211 | local start = self.link:clock_micros() 212 | local ticks = 0 213 | local mill = 1000000 214 | local frame = self.sampleRate * mill 215 | while self.running do 216 | uv.run "nowait" 217 | ticks = ticks + 1 218 | local logicalNow = floor(start + (ticks * frame)) 219 | local logicalNext = floor(start + ((ticks + 1) * frame)) 220 | local now = self.link:clock_micros() 221 | local wait = (logicalNow - now) / mill 222 | if wait > 0 then 223 | sleep(wait) 224 | end 225 | if not self.running then 226 | break 227 | end 228 | self.link:capture_audio_session_state(self.sessionState) 229 | local cps = (self.sessionState:tempo() / self.beatsPerCycle) / 60 230 | local cycleFrom = self.sessionState:beat_at_time(logicalNow, 0) / self.beatsPerCycle 231 | local cycleTo = self.sessionState:beat_at_time(logicalNext, 0) / self.beatsPerCycle 232 | -- print(string.format("cycleFrom : %d; cycleTo : %d", cycleFrom, cycleTo)) 233 | if f then 234 | f() 235 | end 236 | for _, sub in pairs(self.subscribers) do 237 | sub:notifyTick(cycleFrom, cycleTo, self.sessionState, cps, self.beatsPerCycle, mill, now, State) 238 | end 239 | coroutine.yield() 240 | end 241 | self.linkEnabled = false 242 | end) 243 | end 244 | 245 | mt.__index = mt 246 | 247 | function Clock(bpm, sampleRate, beatsPerCycle, callback) 248 | bpm = bpm or 120 249 | sampleRate = sampleRate or (1 / 20) 250 | beatsPerCycle = beatsPerCycle or 4 251 | callback = callback or sendOSC 252 | return setmetatable({ 253 | callback = callback, 254 | bpm = bpm, 255 | sampleRate = sampleRate, 256 | beatsPerCycle = beatsPerCycle, 257 | link = has_al and al.create(bpm) or {}, -- HACK: 258 | sessionState = has_al and al.create_session_state() or {}, 259 | subscribers = {}, 260 | running = false, 261 | latency = 0.2, 262 | }, mt) 263 | end 264 | 265 | return Clock 266 | -------------------------------------------------------------------------------- /src/control.lua: -------------------------------------------------------------------------------- 1 | local control = {} 2 | control.genericParams = { 3 | { "s", "n", "gain" }, 4 | { "cutoff", "resonance" }, 5 | { "hcutoff", "hresonance" }, 6 | 7 | { "delay", "delaytime", "delayfeedback" }, 8 | { "room", "size" }, 9 | { "bandf", "bandq" }, --bpenv 10 | "toArg", 11 | "from", 12 | "to", 13 | "accelerate", 14 | "amp", 15 | "attack", 16 | "bandq", 17 | "begin", 18 | "legato", 19 | "clhatdecay", 20 | "crush", 21 | "coarse", 22 | "channel", 23 | "cut", 24 | "cutoff", 25 | "cutoffegint", 26 | "decay", 27 | "delayfeedback", 28 | "delaytime", 29 | "detune", 30 | "djf", 31 | "dry", 32 | "end", 33 | "fadeTime", 34 | "fadeInTime", 35 | "freq", 36 | "gain", 37 | "gate", 38 | "hatgrain", 39 | "hold", 40 | "hresonance", 41 | "lagogo", 42 | "lclap", 43 | "lclaves", 44 | "lclhat", 45 | "lcrash", 46 | "leslie", 47 | "lrate", 48 | "lfodelay", 49 | "lfosync", 50 | "lock", 51 | "metatune", 52 | "mtranspose", 53 | "octaveR", 54 | "ophatdecay", 55 | "orbit", 56 | "pan", 57 | "panorient", 58 | "portamento", 59 | "sagogo", 60 | "semitone", 61 | "speed", 62 | "sustain", 63 | "unit", 64 | "voice", 65 | "vowel", 66 | "modwheel", 67 | "tremolorate", 68 | "fshiftnote", 69 | "kcutoff", 70 | "octer", 71 | "octersub", 72 | "octersubsub", 73 | "ring", 74 | "ringf", 75 | "ringdf", 76 | "distort", 77 | "freeze", 78 | "xsdelay", 79 | "tsdelay", 80 | "real", 81 | "imag", 82 | "enhance", 83 | "partials", 84 | "comb", 85 | "smear", 86 | "scram", 87 | "binshift", 88 | "hbrick", 89 | "lbrick", 90 | } 91 | 92 | control.aliasParams = { 93 | s = "sound", 94 | note = "up", 95 | attack = "att", 96 | bandf = "bpf", 97 | bandq = "bpq", 98 | clhatdecay = "chdecay", 99 | cutoff = { "ctf", "lpf" }, 100 | cutoffegint = "ctfg", 101 | delayfeedback = { "dfb", "delayfb" }, 102 | delaytime = { "dt", "delayt" }, 103 | detune = "det", 104 | fadeTime = "fadeOutTime", 105 | gate = "gat", 106 | hatgrain = "hg", 107 | hcutoff = "hpf", 108 | hresonance = "hpq", 109 | lagogo = "lag", 110 | lkick = "lbd", 111 | lclhat = "lch", 112 | lclaves = "lcl", 113 | lclap = "lcp", 114 | lcrash = "lcr", 115 | lfocutoffint = "lfoc", 116 | lfoint = "lfoi", 117 | lfopitchint = "lfop", 118 | lhitom = "lht", 119 | llotom = "llt", 120 | lophat = "loh", 121 | resonance = "lpq", 122 | lsnare = "lsn", 123 | n = "number", 124 | ophatdecay = "ohdecay", 125 | phaserdepth = "phasdp", 126 | phaserrate = "phasr", 127 | pitch1 = "pit1", 128 | pitch2 = "pit2", 129 | pitch3 = "pit3", 130 | portamento = "por", 131 | release = "rel", 132 | sagogo = "sag", 133 | sclaves = "scl", 134 | sclap = "scp", 135 | scrash = "scr", 136 | size = "sz", 137 | slide = "sld", 138 | stutterdepth = "std", 139 | stuttertime = "stt", 140 | sustain = "sus", 141 | tomdecay = "tdecay", 142 | tremolodepth = "tremdp", 143 | tremolorate = "tremr", 144 | vcfegint = "vcf", 145 | vcoegint = "vco", 146 | voice = "voi", 147 | } 148 | 149 | return control 150 | -------------------------------------------------------------------------------- /src/factory.lua: -------------------------------------------------------------------------------- 1 | -- local clock = require "clock" 2 | local DefaultClock = Clock() 3 | local factory = {} 4 | 5 | function factory.p(key, pattern) 6 | DefaultClock:subscribe(key, pattern) 7 | return pattern 8 | end 9 | 10 | -- TODO: cause server to freeze ... 11 | function factory._p(key) 12 | DefaultClock:unsubscribe(key) 13 | end 14 | 15 | factory.p_ = factory._p 16 | 17 | function factory.hush() 18 | for i, _ in pairs(DefaultClock.subscribers) do 19 | DefaultClock:unsubscribe(i) 20 | end 21 | end 22 | 23 | -- function M.panic() 24 | -- M.hush() 25 | -- once(s "superpanic") 26 | -- end 27 | -- panic :: Tidally => IO () 28 | -- panic = hush >> once (sound "superpanic") 29 | 30 | for i = 1, 16 do 31 | if i <= 12 then 32 | factory["d" .. i] = function(a) 33 | return factory.p(i, a:orbit(i - 1)) 34 | end 35 | else 36 | factory["d" .. i] = function(a) 37 | return factory.p(i, a) 38 | end 39 | end 40 | factory["_d" .. i] = function() 41 | return factory._p(i) 42 | end 43 | factory["d" .. i .. "_"] = function() 44 | return factory._p(i) 45 | end 46 | end 47 | 48 | factory.DefaultClock = DefaultClock 49 | 50 | function factory.setcps(cps) 51 | DefaultClock:setcps(cps) 52 | end 53 | 54 | function factory.setbpm(bpm) 55 | DefaultClock:setbpm(bpm) 56 | end 57 | 58 | factory.bpm = factory.setbpm 59 | factory.cps = factory.setcps 60 | 61 | return factory 62 | -------------------------------------------------------------------------------- /src/ideas/iter.lua: -------------------------------------------------------------------------------- 1 | require "modal"() 2 | 3 | -- local wrap = coroutine.wrap 4 | local wrap = coroutine.create 5 | 6 | local yield = coroutine.yield 7 | 8 | local function Pattern(query) 9 | query = query or function() 10 | return {} 11 | end 12 | return setmetatable({ query = wrap(query) }, mt) 13 | end 14 | 15 | function pure(value) 16 | local query = function(state) 17 | local cycles = state.span:spanCycles() 18 | local f = function(span) 19 | local whole = span._begin:wholeCycle() 20 | return Event(whole, span, value) 21 | end 22 | for _, v in pairs(cycles) do 23 | yield(f(v)) 24 | end 25 | return cycles 26 | end 27 | return Pattern(query) 28 | end 29 | 30 | local function querySpan(pat, b, e) 31 | local state = State(Span(b, e)) 32 | return function() 33 | local code, res = coroutine.resume(pat.query, state) 34 | if code then 35 | return res 36 | else 37 | return nil 38 | end 39 | end 40 | end 41 | mt.querySpan = querySpan 42 | 43 | function mt:__call(b, e) 44 | return querySpan(self, b, e) 45 | end 46 | 47 | for v in pure "1"(0, 1) do 48 | print(v) 49 | end 50 | -------------------------------------------------------------------------------- /src/ideas/norns.lua: -------------------------------------------------------------------------------- 1 | local t_concat = table.concat 2 | local params = require("modal").params 3 | local state = { 4 | A = { 5 | "[sd bd]", 6 | "fast 2", 7 | "room [9.2]", 8 | }, -- fast 2 $ s [sd bd] # room 9.2 9 | B = { 10 | "sd = 808sd", 11 | "[bd 'sd]", 12 | }, -- sd = "808sd"; s [bd 'sd] 13 | M = { 14 | "mode: stack", -- ['a, 'b ...] 15 | "mode: arrange", 16 | "16 a", 17 | "32 b", 18 | }, 19 | } 20 | -- require "moon.all" 21 | 22 | ---takes a table of strings and concat it correctly 23 | ---@param name string 24 | ---@param scene any 25 | local function compile_maxi(name, scene) 26 | local after = {} 27 | local trans = {} 28 | local decl = { "" } 29 | local center 30 | -- local center = table.remove(scene, 1) 31 | for _, v in pairs(scene) do 32 | local method = v:match "^%w+" 33 | print(method) 34 | if not method then 35 | center = "s " .. v 36 | elseif params[method] then 37 | after[#after + 1] = "# " .. v 38 | elseif v:find "=" then 39 | print(v) 40 | decl[#decl + 1] = v .. "; " 41 | else 42 | trans[#trans + 1] = v .. " $" 43 | end 44 | end 45 | -- p(after) 46 | -- p(trans) 47 | -- p(decl) 48 | -- p(center) 49 | return ("%s%s = (%s %s %s)"):format(t_concat(decl), name:lower(), t_concat(trans, " "), center, t_concat(after, " ")) 50 | end 51 | 52 | -- res = compile_maxi("B", a[1]) 53 | res = compile_maxi("C", state[2]) 54 | print(res) 55 | -------------------------------------------------------------------------------- /src/ideas/sample.lua: -------------------------------------------------------------------------------- 1 | -- TODO: fetch sample from url 2 | -- TODO: read strudel.json file 3 | -- TODO: map samples and auto switch symbols 4 | -- TODO: add sample names to auto completion sources 5 | -------------------------------------------------------------------------------- /src/init.lua: -------------------------------------------------------------------------------- 1 | local pattern = require "pattern" 2 | local notation = require "notation" 3 | local factory = require "factory" 4 | local theory = require "theory" 5 | local ut = require "ut" 6 | local control = require "control" 7 | local mt = pattern.mt 8 | local types = require "types" 9 | -- local Clock = require "clock" 10 | -- require "modal.ui" 11 | 12 | local modal = {} 13 | modal.version = "modal dev-1" 14 | modal.url = "https://github.com/noearc/modal" 15 | 16 | local pairs = pairs 17 | 18 | -- FIXME: later use this, not directly in global scope if not imported 19 | modal.Clock = Clock 20 | 21 | for name, func in pairs(notation) do 22 | modal[name] = func 23 | end 24 | 25 | for name, func in pairs(theory) do 26 | modal[name] = func 27 | end 28 | 29 | for name, func in pairs(factory) do 30 | modal[name] = func 31 | mt[name] = ut.method_wrap(func) 32 | end 33 | 34 | for name, func in pairs(types) do 35 | modal[name] = func 36 | end 37 | 38 | for name, func in pairs(pattern) do 39 | modal[name] = func 40 | end 41 | 42 | modal.notation = notation 43 | 44 | -- modal.maxi = notation.maxi(modal) 45 | 46 | setmetatable(modal, { 47 | __index = _G, 48 | }) 49 | 50 | setmetatable(modal, { 51 | __call = function(t, verb, override) 52 | for k, v in pairs(t) do 53 | if _G[k] ~= nil then 54 | local msg = "function " .. k .. " already exists in global scope." 55 | if verb then 56 | print("WARNING: " .. msg) 57 | end 58 | if override then 59 | _G[k] = v 60 | print("WARNING: " .. msg .. " Overwritten.") 61 | end 62 | else 63 | _G[k] = v 64 | end 65 | end 66 | end, 67 | }) 68 | -- require("modedebug").start() 69 | 70 | return modal 71 | -------------------------------------------------------------------------------- /src/luacats.lua: -------------------------------------------------------------------------------- 1 | ---@meta 2 | 3 | ---@class Fraction 4 | ---@field denomenator number 5 | ---@field numerator number 6 | ---@field reverse function 7 | ---@field cyclePos function 8 | ---@operator sub (Fraction) : Fraction 9 | 10 | ---@alias Time Fraction | number | Pattern 11 | ---@alias ValueMap table 12 | ---@alias Int number | Pattern 13 | ---@alias State table 14 | 15 | ---@param value any 16 | ---@return Pattern 17 | pure = function(value) end 18 | 19 | ---stack up pats in polymeter way 20 | ---@param steps Int 21 | ---@param pats Pattern[] 22 | ---@return Pattern 23 | polymeter = function(steps, pats) end 24 | 25 | ---stack up pats 26 | ---@param pats Pattern[] 27 | ---@return Pattern 28 | stack = function(pats) end 29 | 30 | ---@param pats any[] 31 | ---@return Pattern 32 | slowcat = function(pats) end 33 | 34 | ---Like slowcat, but the items are crammed into one cycle. 35 | ---fastcat("e5", "b4", "d5", "c5") // "e5 b4 d5 c5" 36 | ---@param pats any[] 37 | ---@return Pattern 38 | fastcat = function(pats) end 39 | 40 | ---Like [slowcat](lua://slowcat), but each step has a length, relative to the whole. 41 | ---@param tups (Pattern | Time)[] 42 | ---@return Pattern 43 | timecat = function(tups) end 44 | 45 | ---Allows to arrange multiple patterns together over multiple cycles. 46 | ---Takes a variable number of arrays with two elements specifying the number of cycles and the pattern to use. 47 | ---@param tups (Pattern | number)[] 48 | ---@return Pattern 49 | arrange = function(tups) end 50 | 51 | ---generate ons amount of events evenly in steps, with an offset 52 | ---@param ons number 53 | ---@param steps number 54 | ---@param offset number 55 | ---@return table 56 | bjork = function(ons, steps, offset) end 57 | 58 | ---@param factor Time 59 | ---@param pat any 60 | ---@return Pattern 61 | fast = function(factor, pat) end 62 | 63 | ---@param factor Time 64 | ---@param pat any 65 | ---@return Pattern 66 | slow = function(factor, pat) end 67 | 68 | ---@param factor Time 69 | ---@param pat any 70 | ---@return Pattern 71 | early = function(factor, pat) end 72 | 73 | ---@param factor Time 74 | ---@param pat any 75 | ---@return Pattern 76 | late = function(factor, pat) end 77 | 78 | ---@param np Time 79 | ---@param f fun(b: any): Pattern | Pattern 80 | ---@param pat Pattern 81 | inside = function(np, f, pat) end 82 | 83 | ---@param np Time 84 | ---@param f Pattern | function 85 | ---@param pat Pattern 86 | outside = function(np, f, pat) end 87 | 88 | ---@param name Scales 89 | ---@param pat Pattern 90 | ---@return Pattern 91 | scale = function(name, pat) end 92 | 93 | -- auto generated 94 | ---@param v string | number 95 | ---@return Pattern 96 | n = function(v) end 97 | 98 | ---@param v string | number | Chords 99 | ---@return Pattern 100 | note = function(v) end 101 | 102 | ---@alias vowels "a" | "e" | "i" | "o" | "u" 103 | ---@param v vowels | string 104 | ---@return Pattern 105 | vowel = function(v) end 106 | ---@param v number 107 | ---@return Pattern 108 | channel = function(v) end 109 | ---@param v number 110 | ---@return Pattern 111 | cut = function(v) end 112 | ---@param v 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 113 | ---@return Pattern 114 | orbit = function(v) end 115 | ---@param v table 116 | ---@return Pattern 117 | array = function(v) end 118 | 119 | ---@param v string | number 120 | ---@return Pattern 121 | s = function(v) end 122 | 123 | toArg = function(v) end 124 | from = function(v) end 125 | to = function(v) end 126 | 127 | accelerate = function(v) end 128 | amp = function(v) end 129 | attack = function(v) end 130 | bandf = function(v) end 131 | bandq = function(v) end 132 | begin = function(v) end 133 | legato = function(v) end 134 | clhatdecay = function(v) end 135 | crush = function(v) end 136 | coarse = function(v) end 137 | cutoff = function(v) end 138 | cutoffegint = function(v) end 139 | decay = function(v) end 140 | delay = function(v) end 141 | delayfeedback = function(v) end 142 | delaytime = function(v) end 143 | detune = function(v) end 144 | djf = function(v) end 145 | dry = function(v) end 146 | _end = function(v) end 147 | fadeTime = function(v) end 148 | fadeInTime = function(v) end 149 | freq = function(v) end 150 | gain = function(v) end 151 | gate = function(v) end 152 | hatgrain = function(v) end 153 | hcutoff = function(v) end 154 | hold = function(v) end 155 | hresonance = function(v) end 156 | lagogo = function(v) end 157 | lclap = function(v) end 158 | lclaves = function(v) end 159 | lclhat = function(v) end 160 | lcrash = function(v) end 161 | leslie = function(v) end 162 | lrate = function(v) end 163 | lsize = function(v) end 164 | lfo = function(v) end 165 | lfocutoffint = function(v) end 166 | lfodelay = function(v) end 167 | lfoint = function(v) end 168 | lfopitchint = function(v) end 169 | lfoshape = function(v) end 170 | lfosync = function(v) end 171 | lhitom = function(v) end 172 | lkick = function(v) end 173 | llotom = function(v) end 174 | lock = function(v) end 175 | loop = function(v) end 176 | lophat = function(v) end 177 | lsnare = function(v) end 178 | metatune = function(v) end 179 | degree = function(v) end 180 | mtranspose = function(v) end 181 | ctranspose = function(v) end 182 | harmonic = function(v) end 183 | stepsPerOctave = function(v) end 184 | octaveR = function(v) end 185 | nudge = function(v) end 186 | octave = function(v) end 187 | offset = function(v) end 188 | ophatdecay = function(v) end 189 | overgain = function(v) end 190 | overshape = function(v) end 191 | pan = function(v) end 192 | panspan = function(v) end 193 | pansplay = function(v) end 194 | panwidth = function(v) end 195 | panorient = function(v) end 196 | pitch1 = function(v) end 197 | pitch2 = function(v) end 198 | pitch3 = function(v) end 199 | portamento = function(v) end 200 | rate = function(v) end 201 | release = function(v) end 202 | resonance = function(v) end 203 | room = function(v) end 204 | sagogo = function(v) end 205 | sclap = function(v) end 206 | sclaves = function(v) end 207 | scrash = function(v) end 208 | semitone = function(v) end 209 | shape = function(v) end 210 | size = function(v) end 211 | slide = function(v) end 212 | speed = function(v) end 213 | squiz = function(v) end 214 | stutterdepth = function(v) end 215 | stuttertime = function(v) end 216 | sustain = function(v) end 217 | timescale = function(v) end 218 | timescalewin = function(v) end 219 | tomdecay = function(v) end 220 | unit = function(v) end 221 | velocity = function(v) end 222 | vcfegint = function(v) end 223 | vcoegint = function(v) end 224 | voice = function(v) end 225 | waveloss = function(v) end 226 | dur = function(v) end 227 | modwheel = function(v) end 228 | expression = function(v) end 229 | sustainpedal = function(v) end 230 | tremolodepth = function(v) end 231 | tremolorate = function(v) end 232 | phaserdepth = function(v) end 233 | phaserrate = function(v) end 234 | fshift = function(v) end 235 | fshiftnote = function(v) end 236 | fshiftphase = function(v) end 237 | triode = function(v) end 238 | krush = function(v) end 239 | kcutoff = function(v) end 240 | octer = function(v) end 241 | octersub = function(v) end 242 | octersubsub = function(v) end 243 | ring = function(v) end 244 | ringf = function(v) end 245 | ringdf = function(v) end 246 | distort = function(v) end 247 | freeze = function(v) end 248 | xsdelay = function(v) end 249 | tsdelay = function(v) end 250 | real = function(v) end 251 | imag = function(v) end 252 | enhance = function(v) end 253 | partials = function(v) end 254 | comb = function(v) end 255 | smear = function(v) end 256 | scram = function(v) end 257 | binshift = function(v) end 258 | hbrick = function(v) end 259 | lbrick = function(v) end 260 | midichan = function(v) end 261 | control = function(v) end 262 | ccn = function(v) end 263 | ccv = function(v) end 264 | polyTouch = function(v) end 265 | midibend = function(v) end 266 | miditouch = function(v) end 267 | ctlNum = function(v) end 268 | frameRate = function(v) end 269 | frames = function(v) end 270 | hours = function(v) end 271 | midicmd = function(v) end 272 | minutes = function(v) end 273 | progNum = function(v) end 274 | seconds = function(v) end 275 | songPtr = function(v) end 276 | uid = function(v) end 277 | val = function(v) end 278 | cps = function(v) end 279 | 280 | ---@class Event: { [string] : T } 281 | 282 | ---@generic T 283 | ---@alias query fun(st: State) : Event 284 | ---@class Pattern: {query : query} 285 | ---@operator add(Pattern) : Pattern 286 | ---@field scale fun(self: Pattern, name: Scales): Pattern 287 | ---@field fast fun(self: Pattern, factor: Time): Pattern 288 | ---@field slow fun(self: Pattern, factor: Time): Pattern 289 | ---@field vowel fun(self: Pattern, v:vowels | string): Pattern 290 | ---@field channel fun(self: Pattern, v:any): Pattern 291 | ---@field cut fun(self: Pattern, v:any): Pattern 292 | ---@field orbit fun(self: Pattern, v:any): Pattern 293 | ---@field array fun(self: Pattern, v:any): Pattern 294 | ---@field s fun(self: Pattern, v:any): Pattern 295 | ---@field n fun(self: Pattern, v:any): Pattern 296 | ---@field note fun(self: Pattern, v: string | number | Chords): Pattern 297 | ---@field toArg fun(self: Pattern, v:any): Pattern 298 | ---@field from fun(self: Pattern, v:any): Pattern 299 | ---@field to fun(self: Pattern, v:any): Pattern 300 | ---@field accelerate fun(self: Pattern, v:any): Pattern 301 | ---@field amp fun(self: Pattern, v:any): Pattern 302 | ---@field attack fun(self: Pattern, v:any): Pattern 303 | ---@field bandf fun(self: Pattern, v:any): Pattern 304 | ---@field bandq fun(self: Pattern, v:any): Pattern 305 | ---@field begin fun(self: Pattern, v:any): Pattern 306 | ---@field legato fun(self: Pattern, v:any): Pattern 307 | ---@field clhatdecay fun(self: Pattern, v:any): Pattern 308 | ---@field crush fun(self: Pattern, v:any): Pattern 309 | ---@field coarse fun(self: Pattern, v:any): Pattern 310 | ---@field cutoff fun(self: Pattern, v:any): Pattern 311 | ---@field cutoffegint fun(self: Pattern, v:any): Pattern 312 | ---@field decay fun(self: Pattern, v:any): Pattern 313 | ---@field delay fun(self: Pattern, v:any): Pattern 314 | ---@field delayfeedback fun(self: Pattern, v:any): Pattern 315 | ---@field delaytime fun(self: Pattern, v:any): Pattern 316 | ---@field detune fun(self: Pattern, v:any): Pattern 317 | ---@field djf fun(self: Pattern, v:any): Pattern 318 | ---@field dry fun(self: Pattern, v:any): Pattern 319 | ---@field _end fun(self: Pattern, v:any): Pattern 320 | ---@field fadeTime fun(self: Pattern, v:any): Pattern 321 | ---@field fadeInTime fun(self: Pattern, v:any): Pattern 322 | ---@field freq fun(self: Pattern, v:any): Pattern 323 | ---@field gain fun(self: Pattern, v:any): Pattern 324 | ---@field gate fun(self: Pattern, v:any): Pattern 325 | ---@field hatgrain fun(self: Pattern, v:any): Pattern 326 | ---@field hcutoff fun(self: Pattern, v:any): Pattern 327 | ---@field hold fun(self: Pattern, v:any): Pattern 328 | ---@field hresonance fun(self: Pattern, v:any): Pattern 329 | ---@field lagogo fun(self: Pattern, v:any): Pattern 330 | ---@field lclap fun(self: Pattern, v:any): Pattern 331 | ---@field lclaves fun(self: Pattern, v:any): Pattern 332 | ---@field lclhat fun(self: Pattern, v:any): Pattern 333 | ---@field lcrash fun(self: Pattern, v:any): Pattern 334 | ---@field leslie fun(self: Pattern, v:any): Pattern 335 | ---@field lrate fun(self: Pattern, v:any): Pattern 336 | ---@field lsize fun(self: Pattern, v:any): Pattern 337 | ---@field lfo fun(self: Pattern, v:any): Pattern 338 | ---@field lfocutoffint fun(self: Pattern, v:any): Pattern 339 | ---@field lfodelay fun(self: Pattern, v:any): Pattern 340 | ---@field lfoint fun(self: Pattern, v:any): Pattern 341 | ---@field lfopitchint fun(self: Pattern, v:any): Pattern 342 | ---@field lfoshape fun(self: Pattern, v:any): Pattern 343 | ---@field lfosync fun(self: Pattern, v:any): Pattern 344 | ---@field lhitom fun(self: Pattern, v:any): Pattern 345 | ---@field lkick fun(self: Pattern, v:any): Pattern 346 | ---@field llotom fun(self: Pattern, v:any): Pattern 347 | ---@field lock fun(self: Pattern, v:any): Pattern 348 | ---@field loop fun(self: Pattern, v:any): Pattern 349 | ---@field lophat fun(self: Pattern, v:any): Pattern 350 | ---@field lsnare fun(self: Pattern, v:any): Pattern 351 | ---@field metatune fun(self: Pattern, v:any): Pattern 352 | ---@field degree fun(self: Pattern, v:any): Pattern 353 | ---@field mtranspose fun(self: Pattern, v:any): Pattern 354 | ---@field ctranspose fun(self: Pattern, v:any): Pattern 355 | ---@field harmonic fun(self: Pattern, v:any): Pattern 356 | ---@field stepsPerOctave fun(self: Pattern, v:any): Pattern 357 | ---@field octaveR fun(self: Pattern, v:any): Pattern 358 | ---@field nudge fun(self: Pattern, v:any): Pattern 359 | ---@field octave fun(self: Pattern, v:any): Pattern 360 | ---@field offset fun(self: Pattern, v:any): Pattern 361 | ---@field ophatdecay fun(self: Pattern, v:any): Pattern 362 | ---@field overgain fun(self: Pattern, v:any): Pattern 363 | ---@field overshape fun(self: Pattern, v:any): Pattern 364 | ---@field pan fun(self: Pattern, v:any): Pattern 365 | ---@field panspan fun(self: Pattern, v:any): Pattern 366 | ---@field pansplay fun(self: Pattern, v:any): Pattern 367 | ---@field panwidth fun(self: Pattern, v:any): Pattern 368 | ---@field panorient fun(self: Pattern, v:any): Pattern 369 | ---@field pitch1 fun(self: Pattern, v:any): Pattern 370 | ---@field pitch2 fun(self: Pattern, v:any): Pattern 371 | ---@field pitch3 fun(self: Pattern, v:any): Pattern 372 | ---@field portamento fun(self: Pattern, v:any): Pattern 373 | ---@field rate fun(self: Pattern, v:any): Pattern 374 | ---@field release fun(self: Pattern, v:any): Pattern 375 | ---@field resonance fun(self: Pattern, v:any): Pattern 376 | ---@field room fun(self: Pattern, v:any): Pattern 377 | ---@field sagogo fun(self: Pattern, v:any): Pattern 378 | ---@field sclap fun(self: Pattern, v:any): Pattern 379 | ---@field sclaves fun(self: Pattern, v:any): Pattern 380 | ---@field scrash fun(self: Pattern, v:any): Pattern 381 | ---@field semitone fun(self: Pattern, v:any): Pattern 382 | ---@field shape fun(self: Pattern, v:any): Pattern 383 | ---@field size fun(self: Pattern, v:any): Pattern 384 | ---@field slide fun(self: Pattern, v:any): Pattern 385 | ---@field speed fun(self: Pattern, v:any): Pattern 386 | ---@field squiz fun(self: Pattern, v:any): Pattern 387 | ---@field stutterdepth fun(self: Pattern, v:any): Pattern 388 | ---@field stuttertime fun(self: Pattern, v:any): Pattern 389 | ---@field sustain fun(self: Pattern, v:any): Pattern 390 | ---@field timescale fun(self: Pattern, v:any): Pattern 391 | ---@field timescalewin fun(self: Pattern, v:any): Pattern 392 | ---@field tomdecay fun(self: Pattern, v:any): Pattern 393 | ---@field unit fun(self: Pattern, v:any): Pattern 394 | ---@field velocity fun(self: Pattern, v:any): Pattern 395 | ---@field vcfegint fun(self: Pattern, v:any): Pattern 396 | ---@field vcoegint fun(self: Pattern, v:any): Pattern 397 | ---@field voice fun(self: Pattern, v:any): Pattern 398 | ---@field waveloss fun(self: Pattern, v:any): Pattern 399 | ---@field dur fun(self: Pattern, v:any): Pattern 400 | ---@field modwheel fun(self: Pattern, v:any): Pattern 401 | ---@field expression fun(self: Pattern, v:any): Pattern 402 | ---@field sustainpedal fun(self: Pattern, v:any): Pattern 403 | ---@field tremolodepth fun(self: Pattern, v:any): Pattern 404 | ---@field tremolorate fun(self: Pattern, v:any): Pattern 405 | ---@field phaserdepth fun(self: Pattern, v:any): Pattern 406 | ---@field phaserrate fun(self: Pattern, v:any): Pattern 407 | ---@field fshift fun(self: Pattern, v:any): Pattern 408 | ---@field fshiftnote fun(self: Pattern, v:any): Pattern 409 | ---@field fshiftphase fun(self: Pattern, v:any): Pattern 410 | ---@field triode fun(self: Pattern, v:any): Pattern 411 | ---@field krush fun(self: Pattern, v:any): Pattern 412 | ---@field kcutoff fun(self: Pattern, v:any): Pattern 413 | ---@field octer fun(self: Pattern, v:any): Pattern 414 | ---@field octersub fun(self: Pattern, v:any): Pattern 415 | ---@field octersubsub fun(self: Pattern, v:any): Pattern 416 | ---@field ring fun(self: Pattern, v:any): Pattern 417 | ---@field ringf fun(self: Pattern, v:any): Pattern 418 | ---@field ringdf fun(self: Pattern, v:any): Pattern 419 | ---@field distort fun(self: Pattern, v:any): Pattern 420 | ---@field freeze fun(self: Pattern, v:any): Pattern 421 | ---@field xsdelay fun(self: Pattern, v:any): Pattern 422 | ---@field tsdelay fun(self: Pattern, v:any): Pattern 423 | ---@field real fun(self: Pattern, v:any): Pattern 424 | ---@field imag fun(self: Pattern, v:any): Pattern 425 | ---@field enhance fun(self: Pattern, v:any): Pattern 426 | ---@field partials fun(self: Pattern, v:any): Pattern 427 | ---@field comb fun(self: Pattern, v:any): Pattern 428 | ---@field smear fun(self: Pattern, v:any): Pattern 429 | ---@field scram fun(self: Pattern, v:any): Pattern 430 | ---@field binshift fun(self: Pattern, v:any): Pattern 431 | ---@field hbrick fun(self: Pattern, v:any): Pattern 432 | ---@field lbrick fun(self: Pattern, v:any): Pattern 433 | ---@field midichan fun(self: Pattern, v:any): Pattern 434 | ---@field control fun(self: Pattern, v:any): Pattern 435 | ---@field ccn fun(self: Pattern, v:any): Pattern 436 | ---@field ccv fun(self: Pattern, v:any): Pattern 437 | ---@field polyTouch fun(self: Pattern, v:any): Pattern 438 | ---@field midibend fun(self: Pattern, v:any): Pattern 439 | ---@field miditouch fun(self: Pattern, v:any): Pattern 440 | ---@field ctlNum fun(self: Pattern, v:any): Pattern 441 | ---@field frameRate fun(self: Pattern, v:any): Pattern 442 | ---@field frames fun(self: Pattern, v:any): Pattern 443 | ---@field hours fun(self: Pattern, v:any): Pattern 444 | ---@field midicmd fun(self: Pattern, v:any): Pattern 445 | ---@field minutes fun(self: Pattern, v:any): Pattern 446 | ---@field progNum fun(self: Pattern, v:any): Pattern 447 | ---@field seconds fun(self: Pattern, v:any): Pattern 448 | ---@field songPtr fun(self: Pattern, v:any): Pattern 449 | ---@field uid fun(self: Pattern, v:any): Pattern 450 | ---@field val fun(self: Pattern, v:any): Pattern 451 | ---@field cps fun(self: Pattern, v:any): Pattern 452 | -------------------------------------------------------------------------------- /src/mml.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2014-2015 Andrew Abbott 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | -- Using A as a base note, these are how many 22 | -- semitones/steps away a note on the same octave is. 23 | local steps = { 24 | a = 0, 25 | b = 2, 26 | c = -9, 27 | d = -7, 28 | e = -5, 29 | f = -4, 30 | g = -2, 31 | } 32 | 33 | local REF_FREQ = 440 -- A4 34 | local REF_OCTAVE = 4 35 | local ROOT_MULT = 2 ^ (1 / 12) -- A constant: the twelfth root of two. 36 | 37 | -- See http://www.phy.mtu.edu/~suits/NoteFreqCalcs.html 38 | -- for information on calculating note frequencies. 39 | 40 | local function calculateNoteFrequency(n) 41 | return REF_FREQ * (ROOT_MULT ^ n) 42 | end 43 | 44 | local function calculateNoteSteps(str) 45 | local note, sharp, octave = string.match(str, "(%a)(#?)(%d)") 46 | return (octave - REF_OCTAVE) * 12 + steps[note] + (sharp == "" and 0 or 1) 47 | end 48 | 49 | -- Calculates how long a note is in seconds given a note fraction 50 | -- (quarter note = 4, half note = 2, etc.) and a tempo (in beats per minute). 51 | local function calculateNoteTime(notefrac, bpm) 52 | return (240 / notefrac) / bpm 53 | end 54 | 55 | local function calculateNote(note, outputType) 56 | local steps = calculateNoteSteps(note) 57 | if outputType == "frequency" then 58 | return calculateNoteFrequency(steps) 59 | elseif outputType == "steps" then 60 | return steps 61 | elseif outputType == "multiplier" then 62 | return ROOT_MULT ^ steps 63 | end 64 | end 65 | 66 | --[[ 67 | Phrases: 68 | Note: n[#][l] 69 | n is the note (a-g) 70 | a # or + makes the note sharp and a - makes it flat 71 | l is the length of the note 72 | 4 is quarter note (1/4), 2 is half note (1/2), etc. 73 | Excluding this uses the default length, set with "l" 74 | 75 | Rest: r[l] 76 | l is the length of the rest, specified the same way as note length 77 | 78 | Commands: 79 | t[n] - set tempo to n 80 | o[n] - set octave to n 81 | l[n] - set default note length to n 82 | v[n] - set volume to v 83 | > - increment octave by one 84 | < - decrement octave by one 85 | ]] 86 | 87 | -- Receives a string of MML and returns a player. 88 | 89 | -- When resumed, the player yields with the note (output set by outputType), 90 | -- the time in seconds the note is to be played and the volume it should be played at. 91 | -- It also yields for rests, with nil as the note and for the volume. 92 | -- When the player reaches the end of the song, it will raise an error which 93 | -- will be caught by coroutine.resume. 94 | 95 | -- outputType can be: 96 | -- "steps", outputs the number of semitones away from A 440 the note is. 97 | -- "frequency", outputs the frequency of the note. 98 | -- "multiplier", outputs frequency/440. 99 | 100 | local function newPlayer(str, outputType) 101 | return coroutine.create(function() 102 | local octave = 4 103 | local tempo = 60 104 | local notelength = 4 105 | local volume = 10 106 | 107 | local pos = 1 108 | 109 | repeat 110 | local c, args, newpos = string.match(string.sub(str, pos), "^([%a<>])(%A-)%s-()[%a<>]") 111 | 112 | if not c then -- Might be the last command in the string. 113 | c, args = string.match(string.sub(str, pos), "^([%a<>])(%A-)") 114 | newpos = 0 115 | end 116 | 117 | if not c then -- Probably bad syntax. 118 | error "Malformed MML" 119 | end 120 | 121 | pos = pos + (newpos - 1) 122 | 123 | if c == "o" then -- Set octave 124 | octave = tonumber(args) 125 | elseif c == "t" then -- Set tempo 126 | tempo = tonumber(args) 127 | elseif c == "v" then -- Set volume 128 | volume = tonumber(args) 129 | elseif c == "r" then -- Rest 130 | local delay 131 | if args ~= "" then 132 | delay = calculateNoteTime(tonumber(args), tempo) 133 | else 134 | delay = calculateNoteTime(notelength, tempo) 135 | end 136 | coroutine.yield(nil, delay, nil) 137 | elseif c == "l" then -- Set note length 138 | notelength = tonumber(args) 139 | elseif c == ">" then -- Increase octave 140 | octave = octave + 1 141 | elseif c == "<" then -- Decrease octave 142 | octave = octave - 1 143 | elseif c:find "[a-g]" then -- Play note 144 | local note 145 | local mod = string.match(args, "[+#-]") 146 | if mod then 147 | if mod == "#" or mod == "+" then 148 | note = c .. "#" .. octave 149 | elseif mod == "-" then 150 | note = c .. "-" .. octave 151 | end 152 | else 153 | note = c .. octave 154 | end 155 | 156 | local notetime 157 | local len = string.match(args, "%d+") 158 | if len then 159 | notetime = calculateNoteTime(tonumber(len), tempo) 160 | else 161 | notetime = calculateNoteTime(notelength, tempo) 162 | end 163 | 164 | -- Dotted notes 165 | if string.find(args, "%.") then 166 | notetime = notetime * 1.5 167 | end 168 | 169 | local output = calculateNote(note, outputType) 170 | coroutine.yield(output, notetime, volume) 171 | end 172 | until newpos == 0 173 | -- The coroutine deliberately raises an error when it 174 | -- finishes so coroutine.resume returns false as 175 | -- its first argument. 176 | error "Player finished" 177 | end) 178 | end 179 | 180 | local canon = "t110 l16 o5 a8f#g a8f#g ac#def#g f#8de f#8<" ^ 2 + P "#") / id 116 | local arith = (S "+-*/^%" - P "|") / id 117 | local step = ws * ((step_char ^ 1 - P ".") / pStep) * ws 118 | local minus = P "-" 119 | local plus = P "+" 120 | local zero = P "0" 121 | local digit = R "09" 122 | local decimal_point = P "." 123 | local digit1_9 = R "19" 124 | local e = S "eE" 125 | local int = zero + (digit1_9 * digit ^ 0) 126 | local exp = e * (minus + plus) ^ -1 * digit ^ 1 127 | local frac = decimal_point * digit ^ 1 128 | local number = (minus ^ -1 * int * frac ^ -1 * exp ^ -1) / pNumber 129 | 130 | local function pFast(a) 131 | return function(x) 132 | return Call("fast", a, x) 133 | end 134 | end 135 | 136 | local function pSlow(a) 137 | return function(x) 138 | return Call("slow", a, x) 139 | end 140 | end 141 | 142 | local function pDegrade(a) 143 | if a == "?" then 144 | a = Num(0.5) 145 | end 146 | return function(x) 147 | seed = seed + 1 148 | return Call("degradeBy", a, x) 149 | end 150 | end 151 | 152 | local function pTail(b) 153 | return function(a) 154 | return Call("chain", a, b) 155 | end 156 | end 157 | 158 | local function pEuclid(p, s, r) 159 | r = r or Num(0) 160 | return function(x) 161 | return Call("euclidRot", p, s, r, x) 162 | end 163 | end 164 | 165 | local function pRange(s) 166 | return function(x) 167 | x.range = s[1] 168 | x.reps = nil 169 | return x 170 | end 171 | end 172 | 173 | local function pWeight(a) 174 | return function(x) 175 | x.weight = (x.weight or 1) + (tonumber(a[1]) or 2) - 1 176 | return x 177 | end 178 | end 179 | 180 | local function pReplicate(a) 181 | return function(x) 182 | x.reps = (x.reps or 1) + (tonumber(a[1]) or 2) - 1 183 | return x 184 | end 185 | end 186 | 187 | local function rReps(ast) 188 | local res = {} 189 | for _, node in ipairs(ast) do 190 | if node.reps then 191 | local reps = node.reps 192 | for _ = 1, reps do 193 | node.reps = nil 194 | res[#res + 1] = node 195 | end 196 | elseif node.range then 197 | for i = node[1], node.range do 198 | res[#res + 1] = Num(i) 199 | end 200 | else 201 | res[#res + 1] = node 202 | end 203 | end 204 | return res 205 | end 206 | 207 | local function pSlices(sli, ...) 208 | for _, v in ipairs { ... } do 209 | sli = v(sli) 210 | end 211 | return sli 212 | end 213 | 214 | local function addWeight(a, b) 215 | b = b.weight and b.weight or 1 216 | return a + b 217 | end 218 | 219 | local function rWeight(args) 220 | local acc = {} 221 | for _, v in ipairs(args) do 222 | acc[#acc + 1] = v.weight and Num(v.weight) or Num(1) 223 | acc[#acc + 1] = v 224 | end 225 | return acc 226 | end 227 | 228 | local function pSeq(isSlow) 229 | return function(args) 230 | local weightSum = reduce(addWeight, 0, args) 231 | if weightSum > #args then 232 | return Call(isSlow and "arrange" or "timecat", Table(rWeight(args))) 233 | else 234 | if #args == 1 then 235 | if isSlow then 236 | return Call("pure", args[1]) 237 | end 238 | return args[1] 239 | end 240 | return Call(isSlow and "slowcat" or "fastcat", Table(args)) 241 | end 242 | end 243 | end 244 | 245 | local function pStack(...) 246 | local args = map(rReps, { ... }) 247 | return rReps(args), "Stack" 248 | end 249 | 250 | local function pChoose(...) 251 | local args = map(rReps, { ... }) 252 | return rReps(args), "Choose" 253 | end 254 | 255 | local function pDotStack(...) 256 | local args = map(rReps, { ... }) 257 | return rReps(args), "DotStack" 258 | end 259 | 260 | local opsymb = { 261 | ["+"] = "add", 262 | ["-"] = "sub", 263 | ["*"] = "mul", 264 | ["/"] = "div", 265 | ["^"] = "pow", 266 | ["%"] = "mod", 267 | -- ["+"] = { "add", true }, 268 | -- ["-"] = { "sub", true }, 269 | -- ["*"] = { "mul", true }, 270 | -- ["/"] = { "div", true }, 271 | -- ["^"] = { "pow", true }, 272 | -- ["%"] = { "mod", true }, 273 | -- ["."] = { "pipe", false }, 274 | } 275 | 276 | local function is_op(a) 277 | return opsymb[a] 278 | end 279 | 280 | local function pDollar(...) 281 | local args = { ... } 282 | if #args == 1 then 283 | return args 284 | end 285 | return rTails(args) 286 | end 287 | 288 | local function pList(...) 289 | local args = { ... } 290 | if is_op(args[1]) then 291 | local opname = opsymb[args[1]] 292 | if #args == 3 then 293 | tremove(args, 1) 294 | return { tag = "Op", opname, unpack(args) } 295 | elseif #args == 2 then 296 | return { 297 | tag = "Paren", 298 | { tag = "Function", { Id "x" }, { { tag = "Return", { tag = "Op", opname, Id "x", args[2] } } } }, 299 | } 300 | else 301 | return args[1] 302 | end 303 | end 304 | return rTails(args) 305 | end 306 | 307 | local function pTailop(...) 308 | local args = { ... } 309 | local symb = tremove(args, 1) 310 | args = pDollar(unpack(args)) 311 | return function(x) 312 | return { tag = "Call", { tag = "Index", Id "op", Str(symb) }, x, args } 313 | end 314 | end 315 | 316 | local function pSubCycle(args, tag) 317 | args = map(pSeq(false), args) 318 | if tag == "Stack" then 319 | return Call("stack", Table(args)) 320 | elseif tag == "Choose" then 321 | return Call("randcat", Table(args)) 322 | elseif tag == "DotStack" then 323 | return Call("fastcat", Table(args)) 324 | end 325 | end 326 | 327 | local function pPolymeterSteps(s) 328 | return (s ~= "") and s or -1 329 | end 330 | 331 | local function pPolymeter(args, _, steps) 332 | steps = (steps == -1) and Num(#args[1]) or steps 333 | args = map(pSeq(false), args) 334 | return Call("polymeter", Table(args), steps) 335 | end 336 | 337 | local function pSlowSeq(args, tag) 338 | if tag then 339 | args = map(pSeq(false), args) 340 | if tag == "DotStack" then 341 | return Call("slowcat", Table(args)) 342 | elseif tag == "Choose" then 343 | return Call("randcat", Table(args)) 344 | end 345 | end 346 | return pSeq(true)(rReps(args)) 347 | end 348 | 349 | local function pRoot(...) 350 | local stats = { ... } 351 | for i, a in ipairs(stats) do 352 | stats[i] = a 353 | end 354 | ---@diagnostic disable-next-line: inject-field 355 | stats.tag = "Chunk" 356 | return stats 357 | end 358 | 359 | local function pRet(a) 360 | return { tag = "Return", a } 361 | end 362 | 363 | -- require "moon.all" 364 | local function pSet(lhs, rhs) 365 | lhs.tag = "Id" 366 | return { tag = "Set", { lhs }, { rhs } } 367 | end 368 | 369 | local function pStat(...) 370 | if select("#", ...) == 1 then 371 | return pRet { ... } 372 | end 373 | if select(2, ...) == "=" then 374 | return pSet(select(1, ...), select(3, ...)) 375 | end 376 | return pRet(rTails { ... }) 377 | end 378 | 379 | local function pDot(...) 380 | return { ... } 381 | end 382 | local tab = V "tab" 383 | 384 | local function pTab(...) 385 | return Table { ... } 386 | end 387 | 388 | local semi = P ";" ^ -1 389 | local grammar = { 390 | [1] = "root", 391 | root = (ret * semi) ^ 1 / pRoot, 392 | ret = (list + mini + dollar) / pRet, 393 | list = ws * P "(" * ws * (expr + arith) * expr ^ 0 * ws * P ")" * ws / pList, 394 | tab = ws * P "'(" * ws * expr ^ 1 * ws * P ")" * ws / pTab, 395 | dollar = S "$>" * ws * step * ws * expr ^ 0 * ws / pDollar, 396 | expr = ws * (tab + mini + list + dollar + tailop) * ws, 397 | sequence = (mini ^ 1) / pDot, 398 | stack = sequence * (comma * sequence) ^ 0 / pStack, 399 | choose = sequence * (pipe * sequence) ^ 1 / pChoose, 400 | dotStack = sequence * (dot * sequence) ^ 1 / pDotStack, 401 | tailop = tidalop * ws * step * ws * mini * ws / pTailop, 402 | mini = (slice * op ^ 0) / pSlices, 403 | slice = step + number + sub_cycle + polymeter + slow_sequence + list, 404 | sub_cycle = P "[" * ws * (dotStack + choose + stack) * ws * P "]" / pSubCycle, 405 | slow_sequence = P "<" * ws * (dotStack + choose + sequence) * ws * P ">" / pSlowSeq, 406 | polymeter = P "{" * ws * stack * ws * P "}" * polymeter_steps * ws / pPolymeter, 407 | polymeter_steps = (P "%" * slice) ^ -1 / pPolymeterSteps, 408 | op = fast + slow + tail + range + replicate + degrade + weight + euclid, 409 | fast = P "*" * slice / pFast, 410 | slow = P "/" * slice / pSlow, 411 | tail = P ":" * slice / pTail, 412 | range = P ".." * ws * slice / pRange, 413 | degrade = P "?" * (number ^ -1) / pDegrade, 414 | replicate = ws * P "!" * (number ^ -1) / pReplicate, 415 | weight = ws * (P "@" + P "_") * (number ^ -1) / pWeight, 416 | euclid = P "(" * ws * mini * comma * mini * ws * comma ^ -1 * mini ^ -1 * ws * P ")" / pEuclid, 417 | } 418 | 419 | local function make_gen(top_level) 420 | if top_level then 421 | stat = expr * (P "=" / id) ^ -1 * expr ^ 0 * ws / pStat 422 | grammar.root = (stat * semi) ^ 1 / pRoot 423 | else 424 | grammar.root = (ret * semi) ^ 1 / pRoot 425 | end 426 | 427 | local rules = Ct(C(grammar)) 428 | 429 | local function read(str) 430 | return rules:match(str)[2] 431 | end 432 | 433 | return function(env) 434 | local to_str = function(src) 435 | local ok, ast 436 | ok, ast = pcall(read, src) 437 | if not ok then 438 | return false 439 | end 440 | local lua_src = a2s.run(ast) -- TODO: imporve api 441 | return lua_src 442 | end 443 | 444 | local function to_f(src) 445 | if not top_level then 446 | src = "[" .. src .. "]" 447 | end 448 | local ok, fn 449 | local lua_src = to_str(src) 450 | if not lua_src then 451 | return false 452 | end 453 | ok, fn = pcall(loadstring, lua_src) 454 | if not ok then 455 | return false 456 | end 457 | setfenv(fn and fn or function() 458 | print "not a valid maxi notation" 459 | end, env) 460 | return fn() 461 | end 462 | return memoize(to_f) 463 | end 464 | end 465 | 466 | notation.maxi = make_gen(true) 467 | notation.mini = make_gen(false) 468 | 469 | return notation 470 | -------------------------------------------------------------------------------- /src/repl.lua: -------------------------------------------------------------------------------- 1 | local function repl() 2 | local socket = require "socket" 3 | local host = "localhost" 4 | local port = 9000 5 | local has_RL, RL = pcall(require, "readline") 6 | -- local modal = require "modal" 7 | -- local notation = require "modal.notation" 8 | local maxi = notation.maxi(modal) 9 | 10 | local keywords = {} 11 | for i, _ in pairs(modal) do 12 | keywords[#keywords + 1] = i 13 | end 14 | 15 | if has_RL then 16 | RL.set_complete_list(keywords) 17 | RL.set_options { keeplines = 1000, histfile = "~/.synopsis_history" } 18 | RL.set_readline_name "modal" 19 | end 20 | 21 | local ok, c = pcall(socket.connect, host, port) 22 | 23 | local optf = { 24 | ["?"] = function() 25 | return [[ 26 | :v show _VERSION 27 | :t get type for lib func (TODO: for expression) 28 | :q quit repl ]] 29 | end, 30 | t = function(a) 31 | return tostring(modal.t[a]) 32 | end, 33 | v = function() 34 | return modal._VERSION 35 | end, 36 | -- info = function(name) 37 | -- return dump(doc[name]) 38 | -- end, 39 | q = function() 40 | if c then 41 | c:close() 42 | end 43 | os.exit() 44 | end, 45 | } 46 | 47 | -- TODO: see luaish, first run as lua with multiline? no ambiguiaty?> 48 | local eval = function(a) 49 | if a:sub(1, 1) == ":" then 50 | local name, param = a:match "(%a+)%s(%a*)" 51 | name = name and name or a:sub(2, #a) 52 | param = param and param or nil 53 | return optf[name](param) 54 | else 55 | local fn = modal.ut.dump(maxi(a)) 56 | return fn 57 | end 58 | end 59 | 60 | local function readline(a) 61 | io.write(a) 62 | return io.read() 63 | end 64 | 65 | local read = has_RL and RL.readline or readline 66 | 67 | local line 68 | print "modal repl :? for help" 69 | while true do 70 | line = read "> " 71 | if line == "exit" then 72 | if c then 73 | c:close() 74 | end 75 | break 76 | end 77 | 78 | if line ~= "" then 79 | local res = eval(line) 80 | if res then 81 | print(res) 82 | end 83 | if has_RL then 84 | RL.add_history(line) 85 | -- RL.save_history() 86 | end 87 | if c then 88 | c:send(line .. "\n") 89 | end 90 | end 91 | end 92 | 93 | c:close() 94 | os.exit() 95 | end 96 | modal.repl = repl 97 | 98 | return repl 99 | -------------------------------------------------------------------------------- /src/server.lua: -------------------------------------------------------------------------------- 1 | local function server() 2 | local socket = require "socket" 3 | -- local ut = require "modal.utils" 4 | -- local modal = require "modal" 5 | -- local notation = require "modal.notation" 6 | local maxi = notation.maxi(modal) 7 | local log = ut.log 8 | 9 | local clock = modal.DefaultClock 10 | clock:start() 11 | 12 | local host = "*" 13 | local port = 9000 14 | local sock = assert(socket.bind(host, port)) 15 | local i, p = sock:getsockname() 16 | assert(i, p) 17 | 18 | print("Waiting connection from repl on " .. i .. ":" .. p .. "...") 19 | local c = assert(sock:accept()) 20 | c:settimeout(0) 21 | 22 | print "Connected" 23 | 24 | local eval = function(a) 25 | local ok, fn = pcall(maxi, a) 26 | if not ok then 27 | log.warn("syntax error: " .. fn) 28 | else 29 | print(fn) 30 | end 31 | end 32 | 33 | local l, e 34 | 35 | local listen = function() 36 | l, e = c:receive() 37 | if not e then 38 | print(l) 39 | eval(l) 40 | end 41 | end 42 | 43 | repeat 44 | coroutine.resume(clock.co, listen) 45 | until false 46 | end 47 | 48 | modal.server = server 49 | 50 | return server 51 | -------------------------------------------------------------------------------- /src/theory.lua: -------------------------------------------------------------------------------- 1 | local ut = require "ut" 2 | local lpeg = require "lpeg" 3 | local theory = {} 4 | local P, S, V, R, C, Ct, Cc = lpeg.P, lpeg.S, lpeg.V, lpeg.R, lpeg.C, lpeg.Ct, lpeg.Cc 5 | 6 | local concat, map = ut.concat, ut.map 7 | local qsort = ut.quicksort 8 | 9 | -- TODO: handle error for wrong chord names ... 10 | ---@enum (key) Chords 11 | local chordTable = { 12 | major = { 0, 4, 7 }, 13 | aug = { 0, 4, 8 }, 14 | six = { 0, 4, 7, 9 }, 15 | sixNine = { 0, 4, 7, 9, 14 }, 16 | major7 = { 0, 4, 7, 11 }, 17 | major9 = { 0, 4, 7, 11, 14 }, 18 | add9 = { 0, 4, 7, 14 }, 19 | major11 = { 0, 4, 7, 11, 14, 17 }, 20 | add11 = { 0, 4, 7, 17 }, 21 | major13 = { 0, 4, 7, 11, 14, 21 }, 22 | add13 = { 0, 4, 7, 21 }, 23 | dom7 = { 0, 4, 7, 10 }, 24 | dom9 = { 0, 4, 7, 14 }, 25 | dom11 = { 0, 4, 7, 17 }, 26 | dom13 = { 0, 4, 7, 21 }, 27 | sevenFlat5 = { 0, 4, 6, 10 }, 28 | sevenSharp5 = { 0, 4, 8, 10 }, 29 | sevenFlat9 = { 0, 4, 7, 10, 13 }, 30 | nine = { 0, 4, 7, 10, 14 }, 31 | eleven = { 0, 4, 7, 10, 14, 17 }, 32 | thirteen = { 0, 4, 7, 10, 14, 17, 21 }, 33 | minor = { 0, 3, 7 }, 34 | diminished = { 0, 3, 6 }, 35 | minorSharp5 = { 0, 3, 8 }, 36 | minor6 = { 0, 3, 7, 9 }, 37 | minorSixNine = { 0, 3, 9, 7, 14 }, 38 | minor7flat5 = { 0, 3, 6, 10 }, 39 | minor7 = { 0, 3, 7, 10 }, 40 | minor7sharp5 = { 0, 3, 8, 10 }, 41 | minor7flat9 = { 0, 3, 7, 10, 13 }, 42 | minor7sharp9 = { 0, 3, 7, 10, 15 }, 43 | diminished7 = { 0, 3, 6, 9 }, 44 | minor9 = { 0, 3, 7, 10, 14 }, 45 | minor11 = { 0, 3, 7, 10, 14, 17 }, 46 | minor13 = { 0, 3, 7, 10, 14, 17, 21 }, 47 | minorMajor7 = { 0, 3, 7, 11 }, 48 | one = { 0 }, 49 | five = { 0, 7 }, 50 | sus2 = { 0, 2, 7 }, 51 | sus4 = { 0, 5, 7 }, 52 | sevenSus2 = { 0, 2, 7, 10 }, 53 | sevenSus4 = { 0, 5, 7, 10 }, 54 | nineSus4 = { 0, 5, 7, 10, 14 }, 55 | sevenFlat10 = { 0, 4, 7, 10, 15 }, 56 | nineSharp5 = { 0, 1, 13 }, 57 | minor9sharp5 = { 0, 1, 14 }, 58 | sevenSharp5flat9 = { 0, 4, 8, 10, 13 }, 59 | minor7sharp5flat9 = { 0, 3, 8, 10, 13 }, 60 | elevenSharp = { 0, 4, 7, 10, 14, 18 }, 61 | minor11sharp = { 0, 3, 7, 10, 14, 18 }, 62 | } 63 | 64 | local alias = { 65 | major = { "maj", "M" }, 66 | minor = { "min", "m" }, 67 | aug = { "plus", "sharp5" }, 68 | diminished = "dim", 69 | diminished7 = "dim7", 70 | one = "1", 71 | five = "5", 72 | six = "6", 73 | nine = "9", -- ????? 74 | eleven = "11", 75 | thirteen = "13", 76 | 77 | major7 = "maj7", 78 | major9 = "maj9", 79 | major11 = "maj11", 80 | major13 = "maj13", 81 | 82 | minor7 = { "min7", "m7" }, 83 | minor9 = { "min9", "m9" }, 84 | minor11 = { "min11", "m11" }, 85 | minor13 = { "min13", "m13" }, 86 | 87 | sixNine = { "six9", "sixby9", "6by9" }, 88 | 89 | sevenFlat5 = "7f5", 90 | sevenSharp5 = "7s5", 91 | sevenFlat9 = "7f9", 92 | minorSharp5 = { "msharp5", "mS5" }, 93 | minor6 = { "min6", "m6" }, 94 | minorSixNine = { "minor69", "min69", "minSixNine", "m69", "mSixNine", "m6by9" }, 95 | 96 | minor7flat5 = { "minor7f5", "min7flat5", "m7flat5", "m7f5" }, 97 | minor7sharp5 = { "minor7s5", "min7sharp5", "m7sharp5", "m7s5" }, 98 | minor7flat9 = { "minor7f9", "min7flat9", "m7flat9", "min7f9", "m7f9" }, 99 | minor7sharp9 = { "minor7s9", "min7sharp9", "m7sharp9", "min7s9", "m7s9" }, 100 | minor9sharp5 = { "minor9s5", "min9sharp5", "min9s5", "m9sharp5", "m9s5" }, 101 | minor7sharp5flat9 = "m7sharp5flat9", 102 | minor11sharp = "m11s", 103 | 104 | sevenSus2 = "7sus2", 105 | sevenSus4 = "7sus4", 106 | nineSus4 = { "ninesus4", "9sus4" }, 107 | sevenFlat10 = "7f10", 108 | nineSharp5 = { "9sharp5", "9s5" }, 109 | sevenSharp5flat9 = "7s5f9", 110 | elevenSharp = "11s", 111 | 112 | minorMajor7 = { "minMaj7", "mmaj7" }, 113 | } 114 | 115 | local alias_lookup = {} 116 | 117 | for k, v in pairs(alias) do 118 | if type(v) == "table" then 119 | for _, al in ipairs(v) do 120 | alias_lookup[al] = k 121 | end 122 | else 123 | alias_lookup[v] = k 124 | end 125 | end 126 | 127 | setmetatable(chordTable, { 128 | __index = function(t, k) 129 | return t[alias_lookup[k]] 130 | end, 131 | }) 132 | 133 | local token = function(id) 134 | return Ct(Cc(id) * C(V(id))) 135 | end 136 | local note = token "note" 137 | local chordname = token "chordname" 138 | local chordmods = token "chordmods" 139 | local notename = token "notename" 140 | local notemods = token "notemods" 141 | local range = token "range" 142 | local open = token "open" 143 | local drop = token "drop" 144 | local invert = token "invert" 145 | local offset = token "offset" 146 | local octave = token "octave" 147 | local number = token "number" 148 | local sep = V "sep" 149 | 150 | local grammar = { 151 | [1] = "chord", 152 | chord = note * sep ^ -1 * chordname ^ -1 * chordmods ^ -1, 153 | note = notename * notemods ^ -1, 154 | chordname = R("az", "09") ^ 1, 155 | chordmods = (sep * (range + open + drop + invert)) ^ 0, 156 | notename = R "ag", 157 | notemods = offset ^ -1 * octave ^ -1, 158 | offset = S "sfn", 159 | octave = R "05", 160 | range = number, 161 | open = P "o", 162 | drop = P "d" * number, 163 | invert = P "i" * number, 164 | number = R "09", 165 | sep = P "'", 166 | } 167 | 168 | grammar = Ct(C(grammar)) 169 | 170 | local notes = { c = 0, d = 2, e = 4, f = 5, g = 7, a = 9, b = 11 } 171 | 172 | open = function(chord) 173 | chord[1] = chord[1] - 12 174 | chord[3] = chord[3] - 12 175 | return chord 176 | end 177 | 178 | drop = function(n, chord) 179 | chord = qsort(chord) 180 | local index = #chord - (n - 1) 181 | chord[index] = chord[index] - 12 182 | return chord 183 | end 184 | 185 | invert = function(n, chord) 186 | chord = qsort(chord) 187 | for i = 1, n do 188 | local index = i % #chord 189 | if index == 0 then 190 | index = #chord 191 | end 192 | chord[index] = chord[index] + 12 193 | end 194 | return chord 195 | end 196 | 197 | range = function(n, chord) 198 | local new_tones = {} 199 | n = tonumber(n) 200 | if #chord > n then 201 | local acc = {} 202 | for i = 1, n < 0 and #chord + n or n do 203 | acc[i] = chord[i] 204 | end 205 | return acc 206 | else 207 | for i = #chord + 1, n do 208 | local index = i % #chord 209 | octave = math.ceil(i / #chord) - 1 210 | if index == 0 then 211 | index = #chord 212 | end 213 | local new_tone = chord[index] + (12 * octave) 214 | new_tones[#new_tones + 1] = new_tone 215 | end 216 | return concat(chord, new_tones) 217 | end 218 | end 219 | 220 | local parseChord = function(chord) 221 | if type(chord) == "number" then 222 | return chord 223 | end 224 | local ast = grammar:match(chord) 225 | if not ast then 226 | return false 227 | end 228 | notename = notes[ast[2][3][2]] 229 | offset = 0 230 | octave = 5 231 | if ast[2][4] ~= nil then 232 | local mods = ast[2][4] 233 | local _max_0 = #mods 234 | for _index_0 = 3, _max_0 < 0 and #mods + _max_0 or _max_0 do 235 | local mod = mods[_index_0] 236 | if mod[1] == "offset" then 237 | local _exp_0 = mod[2] 238 | if "s" == _exp_0 then 239 | offset = 1 240 | elseif "f" == _exp_0 then 241 | offset = -1 242 | else 243 | offset = 0 244 | end 245 | end 246 | if mod[1] == "octave" then 247 | octave = tonumber(mod[2]) 248 | end 249 | end 250 | end 251 | local rootnote = notename + offset + (octave - 5) * 12 252 | if ast[3][2] == "" then 253 | return rootnote 254 | end 255 | local chordtable = chordTable[ast[3][2]] 256 | chordtable = map(function(x) 257 | return x + rootnote 258 | end, chordtable) 259 | if ast[4][2] ~= "" then 260 | local _list_0 = ast[4] 261 | local _max_0 = #ast[4] 262 | for _index_0 = 3, _max_0 < 0 and #_list_0 + _max_0 or _max_0 do 263 | local mod = _list_0[_index_0] 264 | if mod[1] == "open" then 265 | chordtable = open(chordtable) 266 | end 267 | if mod[1] == "drop" then 268 | chordtable = drop(mod[3][2], chordtable) 269 | end 270 | if mod[1] == "range" then 271 | chordtable = range(mod[3][2], chordtable) 272 | end 273 | if mod[1] == "invert" then 274 | chordtable = invert(mod[3][2], chordtable) 275 | end 276 | end 277 | end 278 | return chordtable 279 | end 280 | 281 | ---@enum (key) Scales 282 | local scaleTable = { 283 | minPent = { 0, 3, 5, 7, 10 }, 284 | majPent = { 0, 2, 4, 7, 9 }, 285 | ritusen = { 0, 2, 5, 7, 9 }, 286 | egyptian = { 0, 2, 5, 7, 10 }, 287 | kumai = { 0, 2, 3, 7, 9 }, 288 | hirajoshi = { 0, 2, 3, 7, 8 }, 289 | iwato = { 0, 1, 5, 6, 10 }, 290 | chinese = { 0, 4, 6, 7, 11 }, 291 | indian = { 0, 4, 5, 7, 10 }, 292 | pelog = { 0, 1, 3, 7, 8 }, 293 | prometheus = { 0, 2, 4, 6, 11 }, 294 | scriabin = { 0, 1, 4, 7, 9 }, 295 | gong = { 0, 2, 4, 7, 9 }, 296 | shang = { 0, 2, 5, 7, 10 }, 297 | jiao = { 0, 3, 5, 8, 10 }, 298 | zhi = { 0, 2, 5, 7, 9 }, 299 | yu = { 0, 3, 5, 7, 10 }, 300 | whole = { 0, 2, 4, 6, 8, 10 }, 301 | augmented = { 0, 3, 4, 7, 8, 11 }, 302 | augmented2 = { 0, 1, 4, 5, 8, 9 }, 303 | hexMajor7 = { 0, 2, 4, 7, 9, 11 }, 304 | hexDorian = { 0, 2, 3, 5, 7, 10 }, 305 | hexPhrygian = { 0, 1, 3, 5, 8, 10 }, 306 | hexSus = { 0, 2, 5, 7, 9, 10 }, 307 | hexMajor6 = { 0, 2, 4, 5, 7, 9 }, 308 | hexAeolian = { 0, 3, 5, 7, 8, 10 }, 309 | major = { 0, 2, 4, 5, 7, 9, 11 }, 310 | ionian = { 0, 2, 4, 5, 7, 9, 11 }, 311 | dorian = { 0, 2, 3, 5, 7, 9, 10 }, 312 | phrygian = { 0, 1, 3, 5, 7, 8, 10 }, 313 | lydian = { 0, 2, 4, 6, 7, 9, 11 }, 314 | mixolydian = { 0, 2, 4, 5, 7, 9, 10 }, 315 | aeolian = { 0, 2, 3, 5, 7, 8, 10 }, 316 | minor = { 0, 2, 3, 5, 7, 8, 10 }, 317 | locrian = { 0, 1, 3, 5, 6, 8, 10 }, 318 | harmonicMinor = { 0, 2, 3, 5, 7, 8, 11 }, 319 | harmonicMajor = { 0, 2, 4, 5, 7, 8, 11 }, 320 | melodicMinor = { 0, 2, 3, 5, 7, 9, 11 }, 321 | melodicMinorDesc = { 0, 2, 3, 5, 7, 8, 10 }, 322 | melodicMajor = { 0, 2, 4, 5, 7, 8, 10 }, 323 | bartok = { 0, 2, 4, 5, 7, 8, 10 }, 324 | hindu = { 0, 2, 4, 5, 7, 8, 10 }, 325 | todi = { 0, 1, 3, 6, 7, 8, 11 }, 326 | purvi = { 0, 1, 4, 6, 7, 8, 11 }, 327 | marva = { 0, 1, 4, 6, 7, 9, 11 }, 328 | bhairav = { 0, 1, 4, 5, 7, 8, 11 }, 329 | ahirbhairav = { 0, 1, 4, 5, 7, 9, 10 }, 330 | superLocrian = { 0, 1, 3, 4, 6, 8, 10 }, 331 | romanianMinor = { 0, 2, 3, 6, 7, 9, 10 }, 332 | hungarianMinor = { 0, 2, 3, 6, 7, 8, 11 }, 333 | neapolitanMinor = { 0, 1, 3, 5, 7, 8, 11 }, 334 | enigmatic = { 0, 1, 4, 6, 8, 10, 11 }, 335 | spanish = { 0, 1, 4, 5, 7, 8, 10 }, 336 | leadingWhole = { 0, 2, 4, 6, 8, 10, 11 }, 337 | lydianMinor = { 0, 2, 4, 6, 7, 8, 10 }, 338 | neapolitanMajor = { 0, 1, 3, 5, 7, 9, 11 }, 339 | locrianMajor = { 0, 2, 4, 5, 6, 8, 10 }, 340 | diminished = { 0, 1, 3, 4, 6, 7, 9, 10 }, 341 | diminished2 = { 0, 2, 3, 5, 6, 8, 9, 11 }, 342 | messiaen1 = { 0, 2, 4, 6, 8, 10 }, 343 | messiaen2 = { 0, 1, 3, 4, 6, 7, 9, 10 }, 344 | messiaen3 = { 0, 2, 3, 4, 6, 7, 8, 10, 11 }, 345 | messiaen4 = { 0, 1, 2, 5, 6, 7, 8, 11 }, 346 | messiaen5 = { 0, 1, 5, 6, 7, 11 }, 347 | messiaen6 = { 0, 2, 4, 5, 6, 8, 10, 11 }, 348 | messiaen7 = { 0, 1, 2, 3, 5, 6, 7, 8, 9, 11 }, 349 | bayati = { 0, 1.5, 3, 5, 7, 8, 10 }, 350 | hijaz = { 0, 1, 4, 5, 7, 8.5, 10 }, 351 | sikah = { 0, 1.5, 3.5, 5.5, 7, 8.5, 10.5 }, 352 | rast = { 0, 2, 3.5, 5, 7, 9, 10.5 }, 353 | iraq = { 0, 1.5, 3.5, 5, 6.5, 8.5, 10.5 }, 354 | saba = { 0, 1.5, 3, 4, 6, 8, 10 }, 355 | chromatic = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }, 356 | } 357 | 358 | local floor = math.floor 359 | 360 | local getScale = function(name) 361 | return function(num) 362 | local istab = false 363 | if type(num) == "table" and num.note then 364 | num = num.note 365 | istab = true 366 | end 367 | num = tonumber(num) 368 | local scale = scaleTable[name] 369 | local index = (num + 1) % #scale 370 | local octave = floor(num / #scale) 371 | if index == 0 then 372 | index = #scale 373 | end 374 | local note = scale[index] + octave * 12 375 | if istab then 376 | return { ["note"] = note } 377 | end 378 | return note 379 | end 380 | end 381 | 382 | local flatten, zipWith, splitAt, rotate = ut.flatten, ut.zipWith, ut.splitAt, ut.rotate 383 | local min = math.min 384 | 385 | local function left(n, m) 386 | local ons, offs = n[1], n[2] 387 | local xs, ys = m[1], m[2] 388 | local _xs, __xs = splitAt(offs, xs) 389 | return { offs, ons - offs }, { zipWith(concat, _xs, ys), __xs } 390 | end 391 | 392 | local function right(n, m) 393 | local ons, offs = n[1], n[2] 394 | local xs, ys = m[1], m[2] 395 | local _ys, __ys = splitAt(ons, ys) 396 | return { ons, offs - ons }, { zipWith(concat, xs, _ys), __ys } 397 | end 398 | 399 | local function _bjork(n, m) 400 | local ons, offs = n[1], n[2] 401 | if min(ons, offs) <= 1 then 402 | return { n, m } 403 | else 404 | if ons > offs then 405 | return _bjork(left(n, m)) 406 | else 407 | return _bjork(right(n, m)) 408 | end 409 | end 410 | end 411 | 412 | local function bjork(ons, steps, offset) 413 | offset = offset or 0 414 | local offs = steps - ons 415 | local x, y = {}, {} 416 | for i = 1, ons do 417 | x[i] = { true } 418 | end 419 | for i = 1, offs do 420 | y[i] = { false } 421 | end 422 | local result = _bjork({ ons, offs }, { x, y }) 423 | result = concat(flatten(result[2][1]), flatten(result[2][2])) 424 | return rotate(offset, result) 425 | end 426 | 427 | theory = { getScale = getScale, parseChord = parseChord, bjork = bjork } 428 | 429 | return theory 430 | -------------------------------------------------------------------------------- /src/types.lua: -------------------------------------------------------------------------------- 1 | local ut = require "ut" 2 | local losc = require "losc" -- TODO: get rid of ??? core should be pure 3 | local types = {} 4 | 5 | local T = ut.T 6 | local union = ut.union 7 | local abs = math.abs 8 | local floor = math.floor 9 | local setmetatable = setmetatable 10 | local tremove = table.remove 11 | local tconcat = table.concat 12 | local unpack = _G.unpack or table.unpack 13 | local is_array = ut.is_array 14 | 15 | local Time, Span, Event 16 | local time = { __class = "time" } 17 | local span = { __class = "span" } 18 | local event = { __class = "event" } 19 | span.__index = span 20 | event.__index = event 21 | time.__index = time 22 | 23 | function span:spanCycles() 24 | local spans = {} 25 | local b, e = self.start, self.stop 26 | local e_sam = e:sam() 27 | -- TODO: zero width??? 28 | -- if b == e then 29 | -- return { Span(b, e) } 30 | -- end 31 | while e > b do 32 | if b:sam() == e_sam then 33 | spans[#spans + 1] = Span(b, self.stop) 34 | break 35 | end 36 | local next_b = b:nextSam() 37 | spans[#spans + 1] = Span(b, next_b) 38 | b = next_b 39 | end 40 | return spans 41 | end 42 | 43 | function span:duration() 44 | return self.stop - self.start 45 | end 46 | 47 | function span:midpoint() 48 | return self.start + (self:duration() / 2) 49 | end 50 | 51 | function span:cycleSpan() 52 | local b = self.start:cyclePos() 53 | return Span(b, b + self:duration()) 54 | end 55 | 56 | function span:__eq(rhs) 57 | return self.start == rhs.start and self.stop == rhs.stop 58 | end 59 | 60 | function span:__tostring() 61 | return self.start:show() .. " → " .. self.stop:show() 62 | end 63 | 64 | function span:show() 65 | return self:__tostring() 66 | end 67 | 68 | function span:withTime(func) 69 | return Span(func(self.start), func(self.stop)) 70 | end 71 | 72 | function span:withEnd(func) 73 | return Span(self.start, func(self.stop)) 74 | end 75 | 76 | function span:withCycle(func) 77 | local sam = self.start:sam() 78 | local b = sam + func(self.start - sam) 79 | local e = sam + func(self.stop - sam) 80 | return Span(b, e) 81 | end 82 | 83 | function span:sect(other) 84 | local maxOfStart = self.start:max(other.start) 85 | local minOfEnd = self.stop:min(other.stop) 86 | if maxOfStart > minOfEnd then 87 | return nil 88 | end 89 | if maxOfStart == minOfEnd then 90 | if maxOfStart == self.stop and self.start < self.stop then 91 | return nil 92 | end 93 | if maxOfStart == other.stop and other.start < other.stop then 94 | return nil 95 | end 96 | end 97 | return Span(maxOfStart, minOfEnd) 98 | end 99 | 100 | function span:sect_e(other) 101 | local result = self:sect(other) 102 | if not result then 103 | error "Span: spans do not intersect" 104 | end 105 | return result 106 | end 107 | 108 | function Span(b, e) 109 | b = b or 1 110 | e = e or 1 111 | return setmetatable({ 112 | start = Time(b), 113 | stop = Time(e), 114 | }, span) 115 | end 116 | 117 | function event:__eq(other) 118 | -- return (self.part == other.part) 119 | -- and (self.whole == other.whole) 120 | -- and (compare(self.value, other.value)) 121 | -- and (compare(self.context, other.context)) 122 | -- and (self.stateful == other.stateful) 123 | return self:__tostring() == other:__tostring() 124 | end 125 | 126 | function event:duration() 127 | return self.whole.stop - self.whole.start 128 | end 129 | 130 | function event:wholeOrPart() 131 | if self.whole ~= nil then 132 | return self.whole 133 | end 134 | return self.part 135 | end 136 | 137 | function event:hasWhole() 138 | return self.whole ~= nil 139 | end 140 | 141 | function event:hasOnset() 142 | return self.whole ~= nil and self.whole.start == self.part.start 143 | end 144 | 145 | function event:withSpan(func) 146 | local whole = self.whole 147 | if whole ~= nil then 148 | whole = func(whole) 149 | end 150 | return Event(whole, func(self.part), self.value, self.context, self.stateful) 151 | end 152 | 153 | function event:withValue(func) 154 | return Event(self.whole, self.part, func(self.value), self.context, self.stateful) 155 | end 156 | 157 | function event:show() 158 | return self:__tostring() 159 | end 160 | 161 | function event:__tostring() 162 | local part = self.part:__tostring() 163 | local h, t = "", "" 164 | if self:hasWhole() then 165 | h = (self.whole.start ~= self.part.start) and self.whole.start:show() .. "-" or "" 166 | t = (self.whole.stop ~= self.part.stop) and "-" .. self.whole.stop:show() or "" 167 | end 168 | return ("%s(%s)%s | %s"):format(h, part, t, ut.tdump(self.value)) 169 | end 170 | 171 | function event:spanEquals(other) 172 | return ((other.whole == nil) and (self.whole == nil)) or (other.whole == self.whole) 173 | end 174 | 175 | function event:setContext(newContext) 176 | return Event(self.whole, self.part, self.value, newContext, self.stateful) 177 | end 178 | 179 | function event:combineContext(other) 180 | local newContext = {} 181 | for key, value in pairs(self.context) do 182 | newContext[key] = value 183 | end 184 | for key, value in pairs(other.context) do 185 | newContext[key] = value 186 | end 187 | local loc1 = self.context.locations or {} 188 | local loc2 = other.context.locations or {} 189 | for i = 1, #loc2 do 190 | loc1[#loc1 + 1] = loc2[i] 191 | end 192 | newContext.locations = loc1 193 | return newContext 194 | end 195 | 196 | function Event(whole, part, value, context, stateful) 197 | part = part or Span() 198 | context = context or {} 199 | stateful = stateful or false 200 | if stateful and T(value) ~= "function" then 201 | error "Event: stateful event values must be of type function" 202 | end 203 | return setmetatable({ 204 | whole = whole, 205 | part = part, 206 | value = value, 207 | context = context, 208 | stateful = stateful, 209 | }, event) 210 | end 211 | 212 | local function decimaltofraction(x0, err) 213 | err = err or 0.0000000001 214 | local num, den 215 | if type(x0) == "table" then 216 | print(T(x0)) 217 | end 218 | local g = abs(x0) 219 | local sign = x0 / g 220 | local a, b, c, d = 0, 1, 1, 0 221 | local s 222 | local iter = 0 223 | while iter < 1000000 do 224 | s = floor(g) 225 | num = a + s * c 226 | den = b + s * d 227 | a, b, c, d = c, d, num, den 228 | g = 1.0 / (g - s) 229 | iter = iter + 1 230 | if err > abs(sign * num / den - x0) then 231 | return sign * num, den 232 | end 233 | end 234 | error("Time: failed to find a fraction for " .. x0) 235 | return 0, 1 236 | end 237 | 238 | local function gcd(a, b) 239 | return (b == 0) and a or gcd(b, a % b) 240 | end 241 | 242 | local function lcm(a, b) 243 | return (a == 0 or b == 0) and 0 or abs(a * b) / gcd(a, b) 244 | end 245 | 246 | function time:wholeCycle() 247 | return Span(self:sam(), self:nextSam()) 248 | end 249 | 250 | function time:cyclePos() 251 | return self - self:sam() 252 | end 253 | 254 | function time:__add(f2) 255 | f2 = Time(f2) 256 | local na = self.numerator 257 | local nb = f2.numerator 258 | local da = self.denominator 259 | local db = f2.denominator 260 | local g = gcd(da, db) 261 | if g == 1 then 262 | Time(na * db + da * nb, da * db, false) 263 | end 264 | local s = floor(da / g) 265 | local t = na * floor(db / g) + nb * s 266 | local g2 = gcd(t, g) 267 | if g2 == 1 then 268 | Time(t, s * db, false) 269 | end 270 | return Time(floor(t / g2), s * floor(db / g2), false) 271 | end 272 | 273 | function time:__sub(f2) 274 | f2 = Time(f2) 275 | local na = self.numerator 276 | local nb = f2.numerator 277 | local da = self.denominator 278 | local db = f2.denominator 279 | local g = gcd(da, db) 280 | if g == 1 then 281 | Time(na * db - da * nb, da * db, false) 282 | end 283 | local s = floor(da / g) 284 | local t = na * floor(db / g) - nb * s 285 | local g2 = gcd(t, g) 286 | if g2 == 1 then 287 | Time(t, s * db, false) 288 | end 289 | return Time(floor(t / g2), s * floor(db / g2), false) 290 | end 291 | 292 | function time:__div(f2) 293 | f2 = Time(f2) 294 | local na = self.numerator 295 | local nb = f2.numerator 296 | local da = self.denominator 297 | local db = f2.denominator 298 | local g1 = gcd(na, nb) 299 | if g1 > 1 then 300 | na = floor(na / g1) 301 | nb = floor(nb / g1) 302 | end 303 | local g2 = gcd(db, da) 304 | if g2 > 1 then 305 | da = floor(da / g2) 306 | db = floor(db / g2) 307 | end 308 | local n = na * db 309 | local d = nb * da 310 | if d < 0 then 311 | n = -n 312 | d = -d 313 | end 314 | return Time(n, d, false) 315 | end 316 | 317 | function time:__mul(f2) 318 | f2 = Time(f2) 319 | local na = self.numerator 320 | local nb = f2.numerator 321 | local da = self.denominator 322 | local db = f2.denominator 323 | local g1 = gcd(na, db) 324 | if g1 > 1 then 325 | na = floor(na / g1) 326 | db = floor(db / g1) 327 | end 328 | local g2 = gcd(nb, da) 329 | if g2 > 1 then 330 | nb = floor(nb / g2) 331 | da = floor(da / g2) 332 | end 333 | return Time(na * nb, da * db, false) 334 | end 335 | 336 | function time:__pow(f2) 337 | f2 = Time(f2) 338 | if f2.denominator == 1 then 339 | local power = f2.numerator 340 | if power >= 0 then 341 | return Time(self.numerator ^ power, self.denominator ^ power, false) 342 | elseif self.numerator >= 0 then 343 | return Time(self.denominator ^ -power, self.numerator ^ -power, false) 344 | else 345 | return Time((-self.numerator) ^ -power, (-self.denominator) ^ -power, false) 346 | end 347 | else 348 | return (self.numerator / self.denominator) ^ (f2.numerator / f2.denominator) 349 | end 350 | end 351 | 352 | function time:__mod(f2) 353 | f2 = Time(f2) 354 | local da = self.denominator 355 | local db = f2.denominator 356 | local na = self.numerator 357 | local nb = f2.numerator 358 | return Time((na * db) % (nb * da), da * db) 359 | end 360 | 361 | function time:__unm() 362 | return Time(-self.numerator, self.denominator, false) 363 | end 364 | 365 | function time:__eq(rhs) 366 | return self.numerator / self.denominator == rhs.numerator / rhs.denominator 367 | end 368 | 369 | function time:__lt(rhs) 370 | return self.numerator / self.denominator < rhs.numerator / rhs.denominator 371 | end 372 | 373 | function time:__lte(rhs) 374 | return self.numerator / self.denominator <= rhs.numerator / rhs.denominator 375 | end 376 | 377 | function time:eq(rhs) 378 | return self == (Time(rhs)) 379 | end 380 | 381 | function time:lt(rhs) 382 | return self < Time(rhs) 383 | end 384 | 385 | function time:gt(rhs) 386 | return self > Time(rhs) 387 | end 388 | 389 | function time:lte(rhs) 390 | return self <= Time(rhs) 391 | end 392 | 393 | function time:gte(rhs) 394 | return self <= Time(rhs) 395 | end 396 | 397 | function time:reverse() 398 | return Time(1) / self 399 | end 400 | 401 | function time:floor() 402 | return floor(self.numerator / self.denominator) 403 | end 404 | 405 | function time:sam() 406 | return Time(self:floor()) 407 | end 408 | 409 | function time:nextSam() 410 | return self:sam() + 1 411 | end 412 | 413 | function time:min(other) 414 | other = Time(other) 415 | if self < other then 416 | return self 417 | else 418 | return other 419 | end 420 | end 421 | 422 | function time:max(other) 423 | other = Time(other) 424 | if self > other then 425 | return self 426 | else 427 | return other 428 | end 429 | end 430 | 431 | function time:gcd(other) 432 | other = Time(other) 433 | local gcd_numerator = gcd(self.numerator, other.numerator) 434 | local lcm_denominator = lcm(self.denominator, other.denominator) 435 | return Time(gcd_numerator, lcm_denominator) 436 | end 437 | 438 | function time:asFloat() 439 | return self.numerator / self.denominator 440 | end 441 | 442 | function time:__tostring() 443 | return ("%d/%d"):format(self.numerator, self.denominator) 444 | end 445 | 446 | function time:show() 447 | return self:__tostring() 448 | end 449 | 450 | ---@class Fraction 451 | function Time(n, d, normalize) 452 | -- HACK: 453 | if T(n) == "time" then 454 | return n 455 | end 456 | n = n or 0 457 | d = d or 1 458 | if normalize == nil then 459 | normalize = true 460 | end 461 | if n % 1 ~= 0 then 462 | n, d = decimaltofraction(n) 463 | end 464 | if d == 0 then 465 | error "Time: divide by zero" 466 | end 467 | if normalize and (n ~= 0) then 468 | local g = floor(gcd(n, d)) 469 | n = floor(n / g) 470 | d = floor(d / g) 471 | end 472 | return setmetatable({ 473 | numerator = n, 474 | denominator = d, 475 | }, time) 476 | end 477 | 478 | local stream = { __class = "stream" } 479 | 480 | function stream:notifyTick(cycleFrom, cycleTo, s, cps, bpc, mill, now, State) 481 | if not self.pattern then 482 | return 483 | end 484 | local events = self.pattern:onsetsOnly()(cycleFrom, cycleTo, State) 485 | for _, ev in ipairs(events) do 486 | local cycleOn = ev.whole.start 487 | local cycleOff = ev.whole.stop 488 | local linkOn = s:time_at_beat(cycleOn:asFloat() * bpc, 0) 489 | local linkOff = s:time_at_beat(cycleOff:asFloat() * bpc, 0) 490 | local deltaSeconds = (linkOff - linkOn) / mill 491 | local value = ev.value 492 | value.cps = ev.value.cps or cps 493 | value.cycle = cycleOn:asFloat() 494 | value.delta = deltaSeconds 495 | local link_secs = now / mill 496 | local nudge = 0 497 | local diff = losc:now() + -link_secs 498 | -- print(link_secs) 499 | -- print(diff:seconds()) 500 | local ts = diff + (linkOn / mill) + self.latency + nudge 501 | self.callback(value, ts) 502 | end 503 | end 504 | 505 | stream.__index = stream 506 | 507 | local function Stream(callback) 508 | return setmetatable({ latency = 0.2, callback = callback }, stream) 509 | end 510 | 511 | local lpeg = require "lpeg" 512 | local P, S, V, R, C, Ct = lpeg.P, lpeg.S, lpeg.V, lpeg.R, lpeg.C, lpeg.Ct 513 | 514 | local function pId(...) 515 | return { tconcat { ... } } 516 | end 517 | 518 | local function pComp(const, tvar) 519 | return { constructor = const[1], tvar[1] } 520 | end 521 | 522 | local function pDef(...) 523 | local args = { ... } 524 | local name 525 | if args[1].isname then 526 | name = tremove(args, 1)[1] 527 | end 528 | local ret = tremove(args, #args) 529 | return { ret = ret, name = name, unpack(args) } 530 | end 531 | 532 | local function pTab(a) 533 | a.istable = true 534 | return a 535 | end 536 | 537 | local typedef = V "typedef" 538 | local fdef = V "fdef" 539 | local tab = V "tab" 540 | local elem = V "elem" 541 | local comp_type = V "comp_type" 542 | local char = R("AZ", "az") 543 | local name = V "name" 544 | local ws = S " \n\r\t" ^ 0 545 | local id = ws * ((char ^ 1) / pId) * ws 546 | 547 | local rules = { 548 | [1] = "typedef", 549 | name = id * ws * P "::" * ws / function(a) 550 | a.isname = true 551 | return a 552 | end, 553 | typedef = name ^ -1 * (elem * ws * P "->" * ws) ^ 1 * elem / pDef, 554 | elem = comp_type + id + fdef + tab, 555 | fdef = P "(" * ws * typedef * ws * P ")", 556 | tab = P "[" * ws * elem * ws * P "]" / pTab, 557 | comp_type = id * ws * id / pComp, 558 | } 559 | 560 | local grammar = Ct(C(rules)) 561 | 562 | local function TDef(a) 563 | local tdef = grammar:match(a)[2] 564 | tdef.source = a 565 | setmetatable(tdef, { 566 | __tostring = function(self) 567 | return self.source 568 | end, 569 | }) 570 | return tdef, tdef.name 571 | end 572 | 573 | local valuemap = { 574 | -- TODO: cover if other types 575 | __add = function(t1, t2) 576 | if type(t2) == "number" then 577 | local k, v = next(t1) 578 | return { [k] = v + t2 } 579 | elseif type(t2) == "table" and not is_array(t2) then 580 | for k, v in pairs(t1) do 581 | if type(v) == "number" or tonumber(v) then 582 | t1[k] = v + (t2[k] or 0) 583 | end 584 | end 585 | for k, v in pairs(t2) do 586 | if not t1[k] then 587 | t1[k] = v 588 | end 589 | end 590 | return t1 591 | else 592 | error "bad table arith" 593 | end 594 | end, 595 | __sub = function(t1, t2) 596 | if type(t2) == "number" then 597 | local k, v = next(t1) 598 | return { [k] = v - t2 } 599 | elseif type(t2) == "table" and not is_array(t2) then 600 | for k, v in pairs(t1) do 601 | if type(v) == "number" or tonumber(v) then 602 | t1[k] = v - (t2[k] or 0) 603 | end 604 | end 605 | for k, v in pairs(t2) do 606 | if not t1[k] then 607 | t1[k] = v 608 | end 609 | end 610 | return t1 611 | else 612 | error "bad table arith" 613 | end 614 | end, 615 | __unm = function(t) 616 | local k, v = next(t) 617 | -- TODO: check 618 | return { [k] = -v } 619 | end, 620 | __concat = function(lhs, rhs) 621 | return union(lhs, rhs) 622 | end, 623 | } 624 | valuemap.__index = valuemap 625 | 626 | local function ValueMap(valmap) 627 | return setmetatable(valmap, valuemap) 628 | end 629 | 630 | types = { Span = Span, Event = Event, Time = Time, Stream = Stream, TDef = TDef, ValueMap = ValueMap } 631 | 632 | return types 633 | -------------------------------------------------------------------------------- /src/ut.lua: -------------------------------------------------------------------------------- 1 | local ut = {} 2 | local pairs = pairs 3 | local ipairs = ipairs 4 | local tostring = tostring 5 | local loadstring = loadstring or load 6 | local setmetatable = setmetatable 7 | local type = type 8 | local unpack = unpack or rawget(table, "unpack") 9 | local str_dump = string.dump 10 | local str_char = string.char 11 | local tconcat = table.concat 12 | local tremove = table.remove 13 | local floor = math.floor 14 | local ceil = math.ceil 15 | local abs = math.abs 16 | local huge = math.huge 17 | local d_getinfo = debug.getinfo 18 | local d_getlocal = debug.getlocal 19 | local d_sethook = debug.sethook 20 | local d_gethook = debug.gethook 21 | local d_getupvalue = debug.getupvalue 22 | local d_setupvalue = debug.setupvalue 23 | 24 | ut.Usecolor = true 25 | 26 | local envs = { "vim", "norns", "love", "pd" } 27 | 28 | for _, v in pairs(envs) do 29 | if rawget(_G, v) then 30 | ut.Usecolor = false 31 | end 32 | end 33 | 34 | -- from https://www.lua.org/gems/sample.pdf 35 | -- TODO: smarter cache over time maybe 36 | local function memoize(f) 37 | local mem = {} -- memoizing table 38 | setmetatable(mem, { __mode = "kv" }) -- make it weak 39 | return function(x) -- new version of ’f’, with memoizing 40 | local r = mem[x] 41 | if r == nil then -- no previous result? 42 | r = f(x) -- calls original function 43 | mem[x] = r -- store result for reuse 44 | end 45 | return r 46 | end 47 | end 48 | ut.memoize = memoize 49 | ut.loadstring = memoize(loadstring) 50 | 51 | ---@table term colors 52 | local colors = {} 53 | 54 | local colormt = {} 55 | 56 | function colormt:__tostring() 57 | return self.value 58 | end 59 | 60 | function colormt:__concat(other) 61 | return tostring(self) .. tostring(other) 62 | end 63 | 64 | function colormt:__call(s) 65 | return self .. s .. colors.reset 66 | end 67 | 68 | local function makecolor(value) 69 | return setmetatable({ value = str_char(27) .. "[" .. tostring(value) .. "m" }, colormt) 70 | end 71 | 72 | local colorvalues = { 73 | -- attributes 74 | reset = 0, 75 | clear = 0, 76 | default = 0, 77 | bright = 1, 78 | dim = 2, 79 | underscore = 4, 80 | blink = 5, 81 | reverse = 7, 82 | hidden = 8, 83 | 84 | -- foreground 85 | black = 30, 86 | red = 31, 87 | green = 32, 88 | yellow = 33, 89 | blue = 34, 90 | magenta = 35, 91 | cyan = 36, 92 | white = 37, 93 | 94 | -- background 95 | onblack = 40, 96 | onred = 41, 97 | ongreen = 42, 98 | onyellow = 43, 99 | onblue = 44, 100 | onmagenta = 45, 101 | oncyan = 46, 102 | onwhite = 47, 103 | } 104 | 105 | for c, v in pairs(colorvalues) do 106 | colors[c] = makecolor(v) 107 | end 108 | 109 | ut.colors = colors 110 | 111 | ---@table bitwise ops 112 | local bit = {} 113 | local MOD = 2 ^ 32 114 | local MODM = MOD - 1 115 | 116 | -- TODO: replace with memoize ...? 117 | local bit_memo = function(f) 118 | local mt = {} 119 | local t = setmetatable({}, mt) 120 | mt.__index = function(self, k) 121 | local v = f(k) 122 | self.k = v 123 | return v 124 | end 125 | return t 126 | end 127 | 128 | local make_bitop_uncached = function(t, m) 129 | local bitop = function(a, b) 130 | local res, p = 0, 1 131 | while a ~= 0 and b ~= 0 do 132 | local am, bm = a % m, b % m 133 | res = res + t[am][bm] * p 134 | a = (a - am) / m 135 | b = (b - bm) / m 136 | p = p * m 137 | end 138 | res = res + (a + b) * p 139 | return res 140 | end 141 | return bitop 142 | end 143 | 144 | local make_bitop = function(t) 145 | local op1 = make_bitop_uncached(t, 2 ^ 1) 146 | local op2 = bit_memo(function(a) 147 | return bit_memo(function(b) 148 | return op1(a, b) 149 | end) 150 | end) 151 | return make_bitop_uncached(op2, 2 ^ (t.n or 1)) 152 | end 153 | 154 | local tobit = function(x) 155 | return x % 2 ^ 32 156 | end 157 | bit.tobit = tobit 158 | 159 | local bxor = make_bitop { 160 | [0] = { [0] = 0, [1] = 1 }, 161 | [1] = { [0] = 1, [1] = 0 }, 162 | n = 4, 163 | } 164 | bit.bxor = bxor 165 | 166 | local bnot = function(a) 167 | return MODM - a 168 | end 169 | bit.bnot = bnot 170 | 171 | local band = function(a, b) 172 | return ((a + b) - bxor(a, b)) / 2 173 | end 174 | bit.band = band 175 | 176 | local bor = function(a, b) 177 | return MODM - band(MODM - a, MODM - b) 178 | end 179 | bit.bor = bor 180 | 181 | local lshift, rshift 182 | rshift = function(a, disp) 183 | if disp < 0 then 184 | return lshift(a, -disp) 185 | end 186 | return floor(a % 2 ^ 32 / 2 ^ disp) 187 | end 188 | bit.rshift = rshift 189 | 190 | lshift = function(a, disp) 191 | if disp < 0 then 192 | return rshift(a, -disp) 193 | end 194 | return (a * 2 ^ disp) % 2 ^ 32 195 | end 196 | bit.lshift = lshift 197 | 198 | ut.bit = bit 199 | 200 | ---Copyright (c) 2016 rxi 201 | ---@table log 202 | local log = { _version = "0.1.0" } 203 | ut.log = log 204 | 205 | log.usecolor = true 206 | log.outfile = nil 207 | log.level = "trace" 208 | 209 | local modes = { 210 | { name = "trace", color = "\27[34m" }, 211 | { name = "debug", color = "\27[36m" }, 212 | { name = "info", color = "\27[32m" }, 213 | { name = "warn", color = "\27[33m" }, 214 | { name = "error", color = "\27[31m" }, 215 | { name = "fatal", color = "\27[35m" }, 216 | } 217 | 218 | local levels = {} 219 | for i, v in ipairs(modes) do 220 | levels[v.name] = i 221 | end 222 | 223 | local round = function(x, increment) 224 | increment = increment or 1 225 | x = x / increment 226 | return (x > 0 and floor(x + 0.5) or ceil(x - 0.5)) * increment 227 | end 228 | 229 | local _tostring = tostring 230 | 231 | local tostring = function(...) 232 | local t = {} 233 | for i = 1, select("#", ...) do 234 | local x = select(i, ...) 235 | if type(x) == "number" then 236 | x = round(x, 0.01) 237 | end 238 | t[#t + 1] = _tostring(x) 239 | end 240 | return table.concat(t, " ") 241 | end 242 | 243 | for i, x in ipairs(modes) do 244 | local nameupper = x.name:upper() 245 | log[x.name] = function(...) 246 | -- Return early if we're below the log level 247 | if i < levels[log.level] then 248 | return 249 | end 250 | 251 | local msg = tostring(...) 252 | local info = debug.getinfo(2, "Sl") 253 | local lineinfo = info.short_src .. ":" .. info.currentline 254 | 255 | -- Output to console 256 | print( 257 | string.format( 258 | "%s[%-6s%s]%s %s: %s", 259 | log.usecolor and x.color or "", 260 | nameupper, 261 | os.date "%H:%M:%S", 262 | log.usecolor and "\27[0m" or "", 263 | lineinfo, 264 | msg 265 | ) 266 | ) 267 | 268 | -- Output to log file 269 | if log.outfile then 270 | local fp = io.open(log.outfile, "a") 271 | local str = string.format("[%-6s%s] %s: %s\n", nameupper, os.date(), lineinfo, msg) 272 | fp:write(str) 273 | fp:close() 274 | end 275 | end 276 | end 277 | 278 | -- general utilities 279 | 280 | local function id(x) 281 | return x 282 | end 283 | ut.id = id 284 | 285 | function ut.is_array(tbl) 286 | return type(tbl) == "table" and (#tbl > 0 or next(tbl) == nil) 287 | end 288 | 289 | ---return size of hash table 290 | ---@param t table 291 | local tsize = function(t) 292 | local size = 0 293 | for _ in pairs(t) do 294 | size = size + 1 295 | end 296 | return size 297 | end 298 | 299 | ---structually compare two table, TODO: needed? 300 | ---@param rhs table 301 | ---@param lhs table 302 | ---@return boolean 303 | function ut.compare(rhs, lhs) 304 | if type(lhs) ~= type(rhs) then 305 | return false 306 | end 307 | if type(lhs) == "table" then 308 | if tsize(lhs) ~= tsize(rhs) then 309 | return false 310 | end 311 | for k, v in pairs(lhs) do 312 | local equal = ut.compare(v, rhs[k]) 313 | if not equal then 314 | return false 315 | end 316 | end 317 | else 318 | return rhs == lhs 319 | end 320 | return true 321 | end 322 | 323 | ---@param value any 324 | ---@return string 325 | function ut.T(value) 326 | local base_type = type(value) 327 | if base_type == "table" then 328 | local cls = value.__class 329 | if cls then 330 | return cls 331 | end 332 | end 333 | return base_type 334 | end 335 | 336 | function ut.flatten(t) 337 | local flat = {} 338 | for i = 1, #t do 339 | local value = t[i] 340 | if ut.T(value) == "table" then 341 | local list = ut.flatten(value) 342 | for j = 1, #list do 343 | flat[#flat + 1] = list[j] 344 | end 345 | else 346 | flat[#flat + 1] = value 347 | end 348 | end 349 | return flat 350 | end 351 | 352 | ---list filter 353 | ---@param f function 354 | ---@param list table 355 | ---@return table 356 | function ut.filter(f, list) 357 | local res = {} 358 | for i = 1, #list do 359 | if f(list[i]) then 360 | res[#res + 1] = list[i] 361 | end 362 | end 363 | return res 364 | end 365 | 366 | local function reduce(f, acc, list) 367 | -- local acc = list[1] 368 | for i = 1, #list do 369 | acc = f(acc, list[i]) 370 | end 371 | return acc 372 | end 373 | ut.reduce = reduce 374 | 375 | ---list map 376 | ---@param f function 377 | ---@param list table 378 | ---@return table 379 | function ut.map(f, list) 380 | for i = 1, #list do 381 | list[i] = f(list[i], i) 382 | end 383 | return list 384 | end 385 | 386 | function ut.dumpf(f) 387 | local fmt = "fun(%s)" 388 | local args = ut.get_args(f) 389 | local argstr = tconcat(args, ", ") 390 | return fmt:format(argstr) 391 | end 392 | 393 | ---dump table as key value pairs 394 | ---@param o table 395 | ---@return string 396 | function ut.tdump(o) 397 | if ut.T(o) == "table" then 398 | local s = {} 399 | for k, v in pairs(o) do 400 | s[#s + 1] = k 401 | s[#s + 1] = ": " 402 | s[#s + 1] = ut.tdump(v) 403 | s[#s + 1] = " " 404 | end 405 | return tconcat(s) 406 | elseif ut.T(o) == "string" then 407 | local str = '"' .. o .. '"' 408 | return ut.Usecolor and ut.colors.green(str) or str 409 | elseif ut.T(o) == "number" then 410 | return tostring(ut.Usecolor and ut.colors.yellow(o) or o) 411 | elseif ut.T(o) == "function" then 412 | return ut.Usecolor and ut.colors.blue(ut.dumpf(o)) or ut.dumpf(o) 413 | else 414 | return tostring(ut.Usecolor and ut.colors.red(o) or o) 415 | end 416 | end 417 | 418 | ---dump table of events the tidal way 419 | ---@param o table 420 | ---@return string 421 | function ut.dump(o) 422 | if ut.T(o) == "table" then 423 | local s = {} 424 | for k, v in pairs(o) do 425 | s[#s + 1] = ut.Usecolor and ut.colors.cyan(k) or k 426 | s[#s + 1] = ": " 427 | s[#s + 1] = ut.dump(v) 428 | s[#s + 1] = (k ~= #o) and "\n" or "" 429 | end 430 | return tconcat(s) 431 | elseif ut.T(o) == "string" then 432 | local str = '"' .. o .. '"' 433 | return ut.Usecolor and ut.colors.green(str) or str 434 | elseif ut.T(o) == "number" then 435 | return tostring(ut.Usecolor and ut.colors.yellow(o) or o) 436 | elseif ut.T(o) == "function" then 437 | return ut.Usecolor and ut.colors.blue(ut.dumpf(o)) or ut.dumpf(o) 438 | else 439 | return tostring(o) 440 | end 441 | end 442 | 443 | ---zip two list (xs, ys) with f(xs, ys) 444 | ---@param f function 445 | ---@param xs table 446 | ---@param ys table 447 | ---@return table 448 | function ut.zipWith(f, xs, ys) 449 | local acc = {} 450 | for i = 1, #xs do 451 | acc[i] = f(xs[i], ys[i]) 452 | end 453 | return acc 454 | end 455 | 456 | ---concat two lists 457 | ---@param a table 458 | ---@param b table 459 | ---@return table 460 | local function concat(a, b) 461 | for i = 1, #b do 462 | a[#a + 1] = b[i] 463 | end 464 | -- return chain(a, b):totable() 465 | return a 466 | end 467 | ut.concat = concat 468 | 469 | ---concat two hashmaps 470 | ---@param a table 471 | ---@param b table 472 | ---@return table 473 | function ut.union(a, b) 474 | for k, v in pairs(b) do 475 | a[k] = v 476 | end 477 | return a 478 | end 479 | 480 | ---@param index number 481 | ---@param list table 482 | ---@return table, table 483 | local function splitAt(index, list) 484 | local fst, lst = {}, {} 485 | for k, v in pairs(list) do 486 | if k <= index then 487 | fst[#fst + 1] = v 488 | else 489 | lst[#lst + 1] = v 490 | end 491 | end 492 | return fst, lst 493 | end 494 | ut.splitAt = splitAt 495 | 496 | ---@param step number 497 | ---@param list any[] 498 | ---@return table 499 | function ut.rotate(step, list) 500 | local a, b = splitAt(step, list) 501 | return concat(b, a) 502 | end 503 | 504 | ---pipe fuctions: pipe(f, g, h)(x) -> f(g(h(x))) 505 | ---@param fs (fun(x : any) : any)[] 506 | ---@return any 507 | function ut.pipe(fs) 508 | return reduce(function(f, g) 509 | return function(...) 510 | return f(g(...)) 511 | end 512 | end, id, fs) 513 | end 514 | 515 | local function rev(t) 516 | local reversed = {} 517 | local n = #t 518 | for k, v in ipairs(t) do 519 | reversed[n + 1 - k] = v 520 | end 521 | return reversed 522 | end 523 | 524 | local function rev_unpack(t, n) 525 | return unpack(rev(t), 1, n) 526 | end 527 | 528 | local function curry(func, nparams) 529 | nparams = nparams or ut.nparams(func) 530 | if nparams < 2 then 531 | return func 532 | end 533 | local function aux(argtrace, n) 534 | if n < 1 then 535 | return func(rev_unpack(argtrace, nparams)) 536 | else 537 | return function(...) 538 | local len = select("#", ...) 539 | for i = 1, len do 540 | argtrace[n - i + 1] = select(i, ...) 541 | end 542 | return aux(argtrace, n - len) 543 | end 544 | end 545 | end 546 | return aux({}, nparams) 547 | end 548 | ut.curry = curry 549 | 550 | ---flip two args of f 551 | ---@param f function 552 | ---@return function 553 | function ut.flip(f) 554 | return function(a, b) 555 | return f(b, a) 556 | end 557 | end 558 | 559 | local function xorwise(x) 560 | local a = bxor(lshift(x, 13), x) 561 | local b = bxor(rshift(a, 17), a) 562 | return bxor(lshift(b, 5), b) 563 | end 564 | 565 | local function _frac(x) 566 | return (x - x:floor()):asFloat() 567 | end 568 | 569 | local function timeToIntSeed(x) 570 | return xorwise(floor((_frac(x / 300) * 536870912))) 571 | end 572 | 573 | local function intSeedToRand(x) 574 | return (x % 536870912) / 536870912 575 | end 576 | 577 | function ut.timeToRand(x) 578 | return abs(intSeedToRand(timeToIntSeed(x))) 579 | end 580 | 581 | local nparams 582 | ---returns num_param, is_vararg 583 | ---@param func function 584 | ---@return number, boolean 585 | function nparams(func) 586 | local info = d_getinfo(func) 587 | return info.nparams, info.isvararg 588 | end 589 | 590 | if _VERSION == "Lua 5.1" and not jit then 591 | function nparams(func) 592 | local s = str_dump(func) 593 | assert(s:sub(1, 6) == "\27LuaQ\0", "This code works only in Lua 5.1") 594 | local int_size = s:byte(8) 595 | local ptr_size = s:byte(9) 596 | local pos = 14 + ptr_size + (s:byte(7) > 0 and s:byte(13) or s:byte(12 + ptr_size)) + 2 * int_size 597 | return s:byte(pos), s:byte(pos + 1) > 0 598 | end 599 | end 600 | ut.nparams = nparams 601 | 602 | ---register a f(..., pat) as a method for Pattern.f(self, ...), essentially switch the order of args 603 | ---@param f function 604 | ---@return function 605 | function ut.method_wrap(f) 606 | return function(...) 607 | local args = { ... } 608 | local pat = tremove(args, 1) 609 | args[#args + 1] = pat 610 | return f(unpack(args)) 611 | end 612 | end 613 | 614 | ---for lua5.1 compatibility 615 | ---@param f any 616 | ---@param env any 617 | ---@return any 618 | function ut.setfenv(f, env) 619 | local i = 1 620 | while true do 621 | local name = d_getupvalue(f, i) 622 | if name == "_ENV" then 623 | d_setupvalue(f, i, env) 624 | break 625 | elseif not name then 626 | break 627 | end 628 | i = i + 1 629 | end 630 | return f 631 | end 632 | 633 | local function partition(array, left, right, pivotIndex) 634 | local pivotValue = array[pivotIndex] 635 | array[pivotIndex], array[right] = array[right], array[pivotIndex] 636 | 637 | local storeIndex = left 638 | 639 | for i = left, right - 1 do 640 | if array[i] <= pivotValue then 641 | array[i], array[storeIndex] = array[storeIndex], array[i] 642 | storeIndex = storeIndex + 1 643 | end 644 | array[storeIndex], array[right] = array[right], array[storeIndex] 645 | end 646 | 647 | return storeIndex 648 | end 649 | 650 | local function quicksort(array, left, right) 651 | if right > left then 652 | local pivotNewIndex = partition(array, left, right, left) 653 | quicksort(array, left, pivotNewIndex - 1) 654 | quicksort(array, pivotNewIndex + 1, right) 655 | end 656 | end 657 | ut.quicksort = quicksort 658 | 659 | function ut.get_args(f) 660 | local args = {} 661 | for i = 1, nparams(f) do 662 | args[#args + 1] = d_getlocal(f, i) 663 | end 664 | return args 665 | end 666 | 667 | if _VERSION == "Lua 5.1" and not jit then 668 | ut.get_args = function(f) 669 | local args = {} 670 | local hook = d_gethook() 671 | 672 | local argHook = function() 673 | local info = d_getinfo(3) 674 | if "pcall" ~= info.name then 675 | return 676 | end 677 | 678 | for i = 1, huge do 679 | local name = d_getlocal(2, i) 680 | if "(*temporary)" == name then 681 | d_sethook(hook) 682 | error "" 683 | return 684 | end 685 | args[#args + 1] = name 686 | end 687 | end 688 | 689 | d_sethook(argHook, "c") 690 | pcall(f) 691 | 692 | return args 693 | end 694 | end 695 | 696 | function ut.getlocal(name, level) 697 | local value 698 | local found = false 699 | 700 | level = (level or 1) + 1 701 | 702 | for i = 1, huge do 703 | local n, v = d_getlocal(level, i) 704 | if not n then 705 | break 706 | end 707 | if n == name then 708 | value = v 709 | found = true 710 | end 711 | end 712 | if found then 713 | return value 714 | end 715 | -- try non-local variables 716 | local func = debug.getinfo(level, "f").func 717 | for i = 1, math.huge do 718 | local n, v = debug.getupvalue(func, i) 719 | if not n then 720 | break 721 | end 722 | if n == name then 723 | return v 724 | end 725 | end 726 | end 727 | 728 | return ut 729 | --------------------------------------------------------------------------------