├── .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 |
--------------------------------------------------------------------------------