├── .gitignore ├── CHANGELOG.md ├── COPYING ├── Cargo.toml ├── README.md ├── examples └── graphical_ggez_editor.rs ├── res └── wdemtracker_sc_20190907.png ├── src ├── audio.rs ├── audio_dev_thread.rs ├── color_opin.rs ├── ggez_gui_painter.rs ├── gui_painter.rs ├── key_shortcut_help.rs ├── lib.rs ├── main.rs ├── operator_gui.rs ├── scopes.rs ├── track.rs ├── tracker.rs ├── tracker_editor.rs ├── tracker_thread.rs └── vval_opin.rs ├── tracker.json └── tracker.wl /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.1.0 (2019-05-19) 2 | ================== 3 | 4 | * Initial version. 5 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wdem-tracker" 3 | version = "0.1.0" 4 | authors = ["Weird Constructor "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | serde = { version = "1.0", features = [ "derive" ] } 9 | serde_json = "1.0" 10 | ggez = "0.5.1" 11 | wlambda = { path = "../wlambda" } 12 | wctr_signal_ops = { path = "../wctr-signal-ops" } 13 | wave_sickle = { path = "../wave-sickle" } 14 | palette = "0.2" 15 | zip = "0.5.3" 16 | cpal = "0.10.0" 17 | 18 | [dev-dependencies] 19 | ggez = "0.5.1" 20 | # vecmath = "1.0.0" 21 | # nalgebra = "0.18.0" 22 | # mint = "0.5.1" 23 | 24 | [profile.dev] 25 | opt-level = 1 26 | 27 | #[dependencies.sdl2] 28 | #version="0.32" 29 | #default-features=false 30 | #features=["ttf","bundled","gfx"] 31 | ##,"image","gfx","ttf"] 32 | ##,"mixer"] 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Please note, this project has been deprecated and won't be continued. It has been superseded by [HexoSynth - A modular synthesizer in Rust](https://github.com/WeirdConstructor/HexoSynth) 2 | 3 | ------ 4 | 5 | WDemTracker - A music tracker which uses wave-sickle for sound generation 6 | ========================================================================= 7 | 8 | 9 | 10 | 11 | 12 | This is a crate that provides an editor and tracker backend for use in other 13 | Rust applications. The editor currently uses ggez for graphics and input 14 | handling, but could be rather easily ported to SDL or other graphics libraries. 15 | 16 | The Rust port of @logicomacorp Logicoma's WaveSabre synthesizer for 64k demos 17 | which I called [wave-sickle](https://github.com/WeirdConstructor/wave-sickle) 18 | is responsible for sound synthesis and sample playing. 19 | 20 | To goal is to include this tracker into a graphics demo engine (or the 21 | other way around) for writing realtime graphics demos with realtime 22 | music/sound generation. 23 | 24 | The WLambda scripting language provides means to setup signal flow 25 | graphs and setting up the wave-sickle synthesizer modules. 26 | 27 | The main user is currently my wctr-demo-engine project. 28 | 29 | # Status 30 | 31 | ## 2019-09-07 32 | 33 | This is not even really released yet. And not even alpha stage, 34 | also the [wave-sickle](https://github.com/WeirdConstructor/wave-sickle) 35 | port is only boiler plate and basic setup code currently. 36 | 37 | I am currently very much implementing basic things like the first 38 | synthesizer port and finally sending note triggers to it. 39 | 40 | Just recently I added the audio buffers and audio device thread. 41 | 42 | # How to run? 43 | 44 | Currently it has not been published to crates.io and you have to clone 45 | the git repositories yourself, but from that it should be just a call 46 | to `cargo run --release` to execute it: 47 | 48 | # git clone https://github.com/WeirdConstructor/wlambda.git 49 | # git clone https://github.com/WeirdConstructor/wctr-signal-ops.git 50 | # git clone https://github.com/WeirdConstructor/wdem-tracker.git 51 | 52 | # cd wdem-tracker 53 | # cargo run --release 54 | 55 | Then hit F1 for a short command overview. 56 | 57 | # Usage 58 | 59 | The tracker usage is different from most trackers and it's input interface 60 | is similar to vi(m) in concept. You have different modes to do different 61 | things and you always get back to the default `[Normal]` mode by hitting the 62 | ESC key. *You Quit by hitting the `q` key!*. 63 | 64 | For Help hit *F1*. 65 | 66 | # WLambda Tracker API 67 | 68 | The whole tracker is configured and signal graph is setup by an WLambda script. 69 | The script is read from the `tracker.wl` file in the current working directory. 70 | This is an example of how such a `tracker.wl` script might look like: 71 | 72 | displayln "audio thread setting start!"; 73 | 74 | !g_main = audio_call :signal_group "Main"; 75 | audio_call :track_proxy 5 g_main; 76 | 77 | !g_sub = audio_call :signal_group "Sub"; 78 | !os = audio_call :op :sin "Sin1" g_sub; 79 | !os2 = audio_call :op :sin "Sin2" g_sub; 80 | 81 | range 1 100 1 { 82 | !i = _; 83 | audio_call :op :sin [str:cat "Sin" i] g_sub; 84 | }; 85 | 86 | !g_inst1 = audio_call :signal_group :Inst1; 87 | audio_call :op :slaughter "Sl1" g_inst1; 88 | audio_call :op :audio_send "AS1" g_inst1; 89 | 90 | !r = $[:addmul, 0, 1.0, 0.01]; 91 | audio_call :input "AS1" :vol_l r; 92 | audio_call :input "AS1" :vol_r r; 93 | 94 | audio_call :thread:quit; 95 | displayln "audio thread setting end!"; 96 | 97 | Aside from the [WLambda Reference](https://docs.rs/wlambda/latest/wlambda/prelude/index.html#wlambda-reference) 98 | following functions are available: 99 | 100 | ## The Modulation Signal Graph 101 | 102 | There is a huge vector of floats called the _register_, which holds 103 | the current modulation values. The signal graph is executed every 104 | tick and the operators in that graph can read/write/modify 105 | the _register_ and optionally generate an audio signal. The audio signal 106 | of a signal group (see below) can be completely replaced/altered/overwritten 107 | by the operator. 108 | 109 | ## Global Functions 110 | 111 | ### audio\_call _audio-thread-function-name_ {_args_} 112 | 113 | ## Audio Thread Functions 114 | 115 | This section holds all the functions available in the 116 | audio thread, where the setup of the signal network happens. 117 | You can call audio thread functions with `audio_call` and `audio_send`. 118 | 119 | ### _group-id_ = signal\_group _name_ 120 | 121 | Creates a new signal group, which is loosely coupling multiple 122 | modulator and/or audio operators. Each signal group also has an 123 | associated audio buffer. The signal groups audio is rendered in the 124 | order the groups were created. 125 | 126 | ### track\_proxy _track-count_ _group-id_ 127 | 128 | This function creates a proxy to forward track modulation value signals 129 | into the signal graph. The first _track-count_ tracks are mapped 130 | into the first _track-count_ register indexes. 131 | 132 | ### _output-register-index_ = op _type_ _name-id_ _group-id_ 133 | 134 | This command generates a new operator called and identified by _name-id_. 135 | The operator is put into the signal group designated by _group-id_. 136 | The return value is the index in the _register_. 137 | 138 | There are currently these types available for _type_: 139 | 140 | sin - A sinus LFO 141 | * Available inputs: 142 | amp - Sine wave amplitude 143 | phase - Sine wave phase 144 | vert - Vertical offset of the sine wave 145 | freq - Frequency of the sine 146 | 147 | audio_send - An operator that sends the audio of the current 148 | signal group and adds it to another signal group. 149 | * Available inputs: 150 | vol_l - Linear factor for left channel audio signal 151 | before it's added to the destination bus. 152 | vol_r - Linear factor for right channel audio signal 153 | before it's added to the destination bus. 154 | 155 | slaughter - The slaughter synthesizer of wave-sickle. 156 | 157 | ### input _name-id_ _input-name_ _register-operator_ 158 | 159 | This operation sets the input _input-name_ of an operator identified by 160 | _name-id_ to the given _register-operator_. The register operator is 161 | calculating and returning the actual value that is used for the input of the 162 | operator. The available inputs are listed above in the documentation of the 163 | `op` function. 164 | 165 | Following _register-operator_ definitions are possible: 166 | 167 | _float_ - Fixed non changing value. 168 | Example: `0.123` 169 | $[:reg, _reg-idx_] - Value of register index _reg-idx_. 170 | Example: `$[:reg, 1]` 171 | $[:mix2, _reg-a-idx_, _reg-b-idx_, _x_] 172 | - _x_ is between 0.0 and 1.0. If 0.0 then 173 | the value of _reg-a-idx_ is taken, if 1.0 174 | value of _reg-b-idx_ is taken. Anything 175 | inbetween is a linear mix of the two registers. 176 | $[:add, _reg-idx_, _value_] 177 | - Adds _value_ to the value of _reg-idx_. 178 | $[:mul, _reg-idx_, _value_] 179 | - Multiplies value of _reg-idx_ with _value_. 180 | $[:addmul, _reg-idx_, _add-value_, _factor-value_] 181 | - (reg-value + _add-value_) * _factor-value_ 182 | $[:muladd, _reg-idx_, _factor-value_, _add-value_] 183 | - (reg-value * _factor-value_) + _add-value_ 184 | $[:lerp, _reg-idx_, _a_, _b_] 185 | - Interpolates linearily between _a_ and _b_ 186 | with x being the value of _reg-idx_. 187 | $[:sstep, _reg-idx_, _a_, _b_] 188 | - Interpolates smoothsteppy between _a_ and _b_ 189 | with x being the value of _reg-idx_. 190 | $[:map, _reg-idx_, _from-a_, _from-b_, _to-a_, _to-b_] 191 | - Maps the value of _reg-idx_ from the _from-a_/_from-b_ range 192 | to the _to-a_/_to-b_ range. 193 | 194 | # License 195 | 196 | This project is licensed under the GNU General Public License Version 3 or 197 | later. 198 | 199 | ## Why GPL? 200 | 201 | Picking a license for my code bothered me for a long time. I read many 202 | discussions about this topic. Read the license explanations. And discussed 203 | this matter with other developers. 204 | 205 | First about _why I write code for free_ at all: 206 | 207 | - It's my passion to write computer programs. In my free time I can 208 | write the code I want, when I want and the way I want. I can freely 209 | allocate my time and freely choose the projects I want to work on. 210 | - To help a friend or member of my family. 211 | - To solve a problem I have. 212 | 213 | Those are the reasons why I write code for free. Now the reasons 214 | _why I publish the code_, when I could as well keep it to myself: 215 | 216 | - So that it may bring value to users and the free software community. 217 | - Show my work as an artist. 218 | - To get into contact with other developers. 219 | - And it's a nice change to put some more polish on my private projects. 220 | 221 | Most of those reasons don't yet justify GPL. The main point of the GPL, as far 222 | as I understand: The GPL makes sure the software stays free software until 223 | eternity. That the user of the software always stays in control. That the users 224 | have _at least the means_ to adapt the software to new platforms or use cases. 225 | Even if the original authors don't maintain the software anymore. 226 | It ultimately prevents _"vendor lock in"_. I really dislike vendor lock in, 227 | especially as developer. Especially as developer I want and need to stay 228 | in control of the computers I use. 229 | 230 | Another point is, that my work has a value. If I give away my work without 231 | _any_ strings attached, I effectively work for free. Work for free for 232 | companies. I would compromise the price I can demand for my skill, workforce 233 | and time. 234 | 235 | This makes two reasons for me to choose the GPL: 236 | 237 | 1. I do not want to support vendor lock in scenarios. At least not for free. 238 | I want to prevent those when I have a choice. 239 | And before you ask, yes I work for a company that sells closed source 240 | software. I am not happy about the closed source fact. 241 | But it pays my bills and gives me the freedom to write free software 242 | in my free time. 243 | 2. I don't want to low ball my own wage and prices by giving away free software 244 | with no strings attached (for companies). 245 | 246 | ## If you need a permissive or private license (MIT) 247 | 248 | Please contact me if you need a different license and really want to use 249 | my code. As long as I am the only author, I can change the license. 250 | We might find an agreement. 251 | 252 | # Contribution 253 | 254 | Unless you explicitly state otherwise, any contribution intentionally submitted 255 | for inclusion in WLambda by you, shall be licensed as GPLv3 or later, 256 | without any additional terms or conditions. 257 | 258 | # Authors 259 | 260 | * Weird Constructor 261 | (You may find me as `WeirdConstructor` on the Rust Discord.) 262 | -------------------------------------------------------------------------------- /examples/graphical_ggez_editor.rs: -------------------------------------------------------------------------------- 1 | extern crate serde_json; 2 | use wdem_tracker::track::*; 3 | use wdem_tracker::tracker::*; 4 | use wdem_tracker::tracker_editor::*; 5 | 6 | use std::rc::Rc; 7 | use std::cell::RefCell; 8 | 9 | use ggez::{Context, ContextBuilder, GameResult}; 10 | use ggez::event::{self, EventHandler, quit}; 11 | use ggez::graphics; 12 | use ggez::input::keyboard::{KeyCode, KeyMods}; 13 | 14 | struct Painter { 15 | reg_view_font: graphics::Font, 16 | text_cache: std::collections::HashMap, 17 | play_pos_row: i32, 18 | } 19 | 20 | impl Painter { 21 | fn draw_rect(&mut self, ctx: &mut Context, color: [f32; 4], pos: [f32; 2], size: [f32; 2], filled: bool, thickness: f32) { 22 | let r = 23 | graphics::Mesh::new_rectangle( 24 | ctx, 25 | if filled { 26 | graphics::DrawMode::fill() 27 | } else { 28 | graphics::DrawMode::stroke(thickness) 29 | }, 30 | graphics::Rect::new(0.0, 0.0, size[0], size[1]), 31 | graphics::Color::from(color)).unwrap(); 32 | graphics::draw( 33 | ctx, 34 | &r, 35 | ([pos[0], pos[1]], 36 | 0.0, 37 | [0.0, 0.0], 38 | graphics::WHITE)).unwrap(); 39 | } 40 | 41 | fn draw_text(&mut self, ctx: &mut Context, color: [f32; 4], pos: [f32; 2], size: f32, text: String) { 42 | let txt = self.text_cache.get(&text); 43 | let txt_elem = if let Some(t) = txt { 44 | t 45 | } else { 46 | let t = graphics::Text::new((text.clone(), self.reg_view_font, size)); 47 | self.text_cache.insert(text.clone(), t); 48 | self.text_cache.get(&text).unwrap() 49 | }; 50 | 51 | graphics::queue_text( 52 | ctx, txt_elem, pos, Some(color.into())); 53 | } 54 | 55 | fn finish_draw_text(&mut self, ctx: &mut Context) { 56 | graphics::draw_queued_text( 57 | ctx, 58 | graphics::DrawParam::default(), 59 | None, 60 | graphics::FilterMode::Linear).unwrap(); 61 | } 62 | } 63 | 64 | const TPOS_PAD : f32 = 50.0; 65 | const TRACK_WIDTH : f32 = 46.0; 66 | const TRACK_PAD : f32 = 0.0; 67 | const TRACK_VAL_PAD : f32 = 4.0; 68 | const ROW_HEIGHT : f32 = 18.0; 69 | 70 | impl TrackerEditorView for Painter { 71 | fn start_drawing(&mut self, _ctx: &mut Context) { 72 | } 73 | 74 | fn end_track(&mut self, _ctx: &mut Context) { 75 | } 76 | 77 | fn start_track(&mut self, ctx: &mut Context, track_idx: usize, _name: &str, cursor: bool) { 78 | let mut clr = [0.8, 0.8, 0.8, 1.0]; 79 | if cursor { 80 | clr = [1.0, 0.7, 0.7, 1.0]; 81 | } 82 | 83 | self.draw_rect( 84 | ctx, 85 | clr, 86 | [TPOS_PAD + track_idx as f32 * (TRACK_WIDTH + TRACK_PAD), 0.0], 87 | [TRACK_WIDTH, (10.0 + 1.0) * ROW_HEIGHT], 88 | false, 89 | 0.5); 90 | } 91 | 92 | fn draw_track_cell(&mut self, ctx: &mut Context, 93 | line_pos: usize, 94 | line_idx: usize, 95 | track_idx: usize, 96 | cursor: bool, 97 | beat: bool, 98 | value: Option, 99 | _interp: Interpolation) { 100 | 101 | let s = if let Some(v) = value { 102 | format!("{:>03.2}", v) 103 | } else { 104 | String::from(" ---") 105 | }; 106 | 107 | let txt_x = 108 | TRACK_VAL_PAD 109 | + TPOS_PAD 110 | + track_idx as f32 * (TRACK_WIDTH + TRACK_PAD); 111 | 112 | let txt_y = line_pos as f32 * ROW_HEIGHT; 113 | 114 | if track_idx == 0 { 115 | if line_idx as i32 == self.play_pos_row { 116 | self.draw_rect( 117 | ctx, 118 | [0.4, 0.0, 0.0, 1.0], 119 | [0.0, txt_y], 120 | [800.0, ROW_HEIGHT], 121 | true, 122 | 0.0); 123 | } 124 | 125 | self.draw_text( 126 | ctx, 127 | if beat { [0.5, 8.0, 0.5, 1.0] } 128 | else { [0.6, 0.6, 0.6, 1.0] }, 129 | [TRACK_PAD / 2.0, txt_y], 130 | ROW_HEIGHT * 0.6, 131 | format!("[{:0>4}]", line_idx)); 132 | } 133 | 134 | if cursor { 135 | self.draw_rect( 136 | ctx, 137 | [0.4, 0.8, 0.4, 1.0], 138 | [txt_x - TRACK_VAL_PAD + 1.0, txt_y + 1.0], 139 | [TRACK_WIDTH - 2.0, ROW_HEIGHT - 2.0], 140 | true, 141 | 0.5); 142 | 143 | self.draw_text( 144 | ctx, 145 | [0.0, 0.0, 0.0, 1.0], 146 | [txt_x, txt_y], 147 | ROW_HEIGHT * 0.9, 148 | s); 149 | } else { 150 | if beat { 151 | self.draw_text( 152 | ctx, 153 | [0.6, 1.0, 0.6, 1.0], 154 | [txt_x, txt_y], 155 | ROW_HEIGHT * 0.9, 156 | s); 157 | } else { 158 | self.draw_text( 159 | ctx, 160 | [0.8, 0.8, 0.8, 1.0], 161 | [txt_x, txt_y], 162 | ROW_HEIGHT * 0.9, 163 | s); 164 | } 165 | } 166 | } 167 | 168 | fn end_drawing(&mut self, _ctx: &mut Context) { 169 | } 170 | } 171 | 172 | struct Output { 173 | values: Vec, 174 | pos: i32, 175 | } 176 | 177 | impl OutputHandler for Output { 178 | fn emit_event(&mut self, track_idx: usize, val: f32) { 179 | //d// println!("EMIT: {}: {}", track_idx, val); 180 | } 181 | 182 | fn emit_play_line(&mut self, play_line: i32) { 183 | //d// println!("EMIT PLAYLINE OUT {}", play_line); 184 | self.pos = play_line; 185 | } 186 | 187 | fn value_buffer(&mut self) -> &mut Vec { 188 | return &mut self.values; 189 | } 190 | } 191 | 192 | fn start_tracker_thread(ext_out: std::sync::Arc>, rcv: std::sync::mpsc::Receiver) { 193 | std::thread::spawn(move || { 194 | let mut o = Output { values: Vec::new(), pos: 0 }; 195 | let mut t = Tracker::new(TrackerNopSync { }); 196 | 197 | let mut is_playing = true; 198 | let mut out_updated = false; 199 | loop { 200 | let r = rcv.try_recv(); 201 | match r { 202 | Ok(TrackerSyncMsg::AddTrack(track)) => { 203 | t.add_track(Track::new(&track.name, track.data)); 204 | println!("THRD: TRACK ADD TRACK"); 205 | }, 206 | Ok(TrackerSyncMsg::SetInt(track_idx, line, int)) => { 207 | t.set_int(track_idx, line, int); 208 | println!("THRD: SET VAL"); 209 | }, 210 | Ok(TrackerSyncMsg::SetValue(track_idx, line, v)) => { 211 | t.set_value(track_idx, line, v); 212 | println!("THRD: SET VAL"); 213 | }, 214 | Ok(TrackerSyncMsg::RemoveValue(track_idx, line)) => { 215 | t.remove_value(track_idx, line); 216 | println!("THRD: REMO VAL"); 217 | }, 218 | Ok(TrackerSyncMsg::PlayHead(a)) => { 219 | match a { 220 | PlayHeadAction::TogglePause => { 221 | is_playing = !is_playing; 222 | }, 223 | PlayHeadAction::Pause => { is_playing = false; }, 224 | PlayHeadAction::Play => { is_playing = true; }, 225 | PlayHeadAction::NextLine => { 226 | println!("NEXT LINE"); 227 | t.tick_to_next_line(&mut o); 228 | out_updated = true; 229 | is_playing = false; 230 | }, 231 | PlayHeadAction::PrevLine => { 232 | println!("PREV LINE"); 233 | t.tick_to_prev_line(&mut o); 234 | out_updated = true; 235 | is_playing = false; 236 | }, 237 | PlayHeadAction::Restart => { 238 | t.reset_pos(); 239 | is_playing = true; 240 | }, 241 | _ => (), 242 | } 243 | }, 244 | Err(std::sync::mpsc::TryRecvError::Empty) => (), 245 | Err(std::sync::mpsc::TryRecvError::Disconnected) => return (), 246 | } 247 | 248 | if is_playing { 249 | t.tick(&mut o); 250 | out_updated = true; 251 | //d// println!("THRD: TICK {}", o.pos); 252 | } 253 | 254 | if out_updated { 255 | out_updated = false; 256 | 257 | if let Ok(ref mut m) = ext_out.try_lock() { 258 | m.pos = o.pos; 259 | o.values = std::mem::replace(&mut m.values, o.values); 260 | } 261 | } 262 | 263 | std::thread::sleep(std::time::Duration::from_millis(20)); 264 | } 265 | }); 266 | } 267 | 268 | #[derive(Debug, Clone)] 269 | enum TrackerSyncMsg { 270 | AddTrack(Track), 271 | SetValue(usize, usize, f32), 272 | SetInt(usize, usize, Interpolation), 273 | RemoveValue(usize, usize), 274 | PlayHead(PlayHeadAction), 275 | } 276 | 277 | struct ThreadTrackSync { 278 | send: std::sync::mpsc::Sender, 279 | } 280 | 281 | impl ThreadTrackSync { 282 | fn new(send: std::sync::mpsc::Sender) -> Self { 283 | ThreadTrackSync { send } 284 | } 285 | } 286 | 287 | impl TrackerSync for ThreadTrackSync { 288 | fn add_track(&mut self, t: Track) { 289 | self.send.send(TrackerSyncMsg::AddTrack(t)); 290 | } 291 | fn set_int(&mut self, track_idx: usize, line: usize, int: Interpolation) { 292 | self.send.send(TrackerSyncMsg::SetInt(track_idx, line, int)); 293 | } 294 | fn set_value(&mut self, track_idx: usize, line: usize, value: f32) { 295 | self.send.send(TrackerSyncMsg::SetValue(track_idx, line, value)); 296 | } 297 | fn remove_value(&mut self, track_idx: usize, line: usize) { 298 | self.send.send(TrackerSyncMsg::RemoveValue(track_idx, line)); 299 | } 300 | fn play_head(&mut self, act: PlayHeadAction) { 301 | self.send.send(TrackerSyncMsg::PlayHead(act)); 302 | } 303 | } 304 | 305 | struct OutputValues { 306 | values: Vec, 307 | } 308 | 309 | struct WDemTrackerGUI { 310 | tracker: Rc>>, 311 | editor: TrackerEditor, 312 | painter: Rc>, 313 | force_redraw: bool, 314 | tracker_thread_out: std::sync::Arc>, 315 | i: i32, 316 | } 317 | 318 | impl WDemTrackerGUI { 319 | pub fn new(ctx: &mut Context) -> WDemTrackerGUI { 320 | let (sync_tx, sync_rx) = std::sync::mpsc::channel::(); 321 | 322 | let sync = ThreadTrackSync::new(sync_tx); 323 | let out = std::sync::Arc::new(std::sync::Mutex::new(Output { values: Vec::new(), pos: 0 })); 324 | 325 | start_tracker_thread(out.clone(), sync_rx); 326 | 327 | let font = graphics::Font::new(ctx, "/DejaVuSansMono.ttf").unwrap(); 328 | let trk = Rc::new(RefCell::new(Tracker::new(sync))); 329 | WDemTrackerGUI { 330 | tracker: trk.clone(), 331 | editor: TrackerEditor::new(trk), 332 | tracker_thread_out: out, 333 | painter: Rc::new(RefCell::new(Painter { 334 | text_cache: std::collections::HashMap::new(), 335 | reg_view_font: font, 336 | play_pos_row: 0, 337 | })), 338 | force_redraw: true, 339 | i: 0, 340 | } 341 | } 342 | 343 | pub fn init(&mut self) { 344 | for i in 0..1 { 345 | self.tracker.borrow_mut().add_track( 346 | Track::new( 347 | &format!("xxx{}", i), 348 | vec![ 349 | (0, 1.0, Interpolation::Step), 350 | (4, 4.0, Interpolation::Step), 351 | (5, 0.2, Interpolation::Step), 352 | ])); 353 | } 354 | } 355 | } 356 | 357 | impl OutputHandler for OutputValues { 358 | fn emit_event(&mut self, track_idx: usize, val: f32) { 359 | println!("EMIT: {}: {}", track_idx, val); 360 | } 361 | 362 | fn emit_play_line(&mut self, play_line: i32) { 363 | println!("EMIT PP: {}", play_line); 364 | } 365 | 366 | fn value_buffer(&mut self) -> &mut Vec { 367 | return &mut self.values; 368 | } 369 | } 370 | 371 | impl EventHandler for WDemTrackerGUI { 372 | fn update(&mut self, _ctx: &mut Context) -> GameResult<()> { 373 | Ok(()) 374 | } 375 | 376 | fn key_down_event(&mut self, ctx: &mut Context, keycode: KeyCode, _keymods: KeyMods, _repeat: bool) { 377 | if keycode == KeyCode::Q { 378 | quit(ctx); 379 | } else { 380 | match keycode { 381 | KeyCode::X => { 382 | self.editor.process_input(TrackerInput::Delete); 383 | }, 384 | KeyCode::H => { 385 | self.editor.process_input(TrackerInput::TrackLeft); 386 | }, 387 | KeyCode::J => { 388 | self.editor.process_input(TrackerInput::RowDown); 389 | }, 390 | KeyCode::K => { 391 | self.editor.process_input(TrackerInput::RowUp); 392 | }, 393 | KeyCode::L => { 394 | self.editor.process_input(TrackerInput::TrackRight); 395 | }, 396 | KeyCode::Space => { 397 | self.editor.process_input( 398 | TrackerInput::PlayHead(PlayHeadAction::TogglePause)); 399 | }, 400 | KeyCode::N => { 401 | self.editor.process_input( 402 | TrackerInput::PlayHead(PlayHeadAction::PrevLine)); 403 | }, 404 | KeyCode::M => { 405 | self.editor.process_input( 406 | TrackerInput::PlayHead(PlayHeadAction::NextLine)); 407 | }, 408 | _ => { 409 | println!("KEY: {:?}", keycode); 410 | } 411 | } 412 | } 413 | } 414 | 415 | fn text_input_event(&mut self, _ctx: &mut Context, character: char) { 416 | //d// println!("CHR: {:?}", character); 417 | self.editor.process_input(TrackerInput::Character(character)); 418 | } 419 | 420 | fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { 421 | 422 | self.i += 1; 423 | if self.i > 100 { 424 | println!("FPS: {}", ggez::timer::fps(ctx)); 425 | self.i = 0; 426 | } 427 | 428 | let _sz = graphics::drawable_size(ctx); 429 | // let param = 430 | // graphics::DrawParam::from( 431 | // ([sz.0 / 2.0, sz.1 / 2.0],)); 432 | // graphics::push_transform(ctx, Some(param.to_matrix())); 433 | // graphics::apply_transformations(ctx)?; 434 | 435 | let _now_time = ggez::timer::time_since_start(ctx).as_millis(); 436 | 437 | //d// let mut ov = OutputValues { values: Vec::new() }; 438 | 439 | //d// self.editor.tracker.borrow_mut().tick(&mut ov); 440 | // if !ov.values.is_empty() { 441 | // println!("OUT: {:?}", ov.values[0]); 442 | // } 443 | 444 | // println!("THREAD POS: {}", self.tracker_thread_out.lock().unwrap().pos); 445 | 446 | self.force_redraw = true; 447 | if self.force_redraw || self.editor.need_redraw() { 448 | graphics::clear(ctx, graphics::BLACK); 449 | self.painter.borrow_mut().play_pos_row = self.tracker_thread_out.lock().unwrap().pos; 450 | self.force_redraw = false; 451 | self.editor.show_state(40, &mut *self.painter.borrow_mut(), ctx); 452 | self.painter.borrow_mut().finish_draw_text(ctx); 453 | } 454 | 455 | //d// println!("O: {:?}", self.tracker_thread_out.lock().unwrap().values); 456 | //d// println!("POS: {:?}", self.tracker_thread_out.lock().unwrap().pos); 457 | 458 | graphics::present(ctx) 459 | } 460 | 461 | fn resize_event(&mut self, ctx: &mut Context, width: f32, height: f32) { 462 | graphics::set_screen_coordinates(ctx, 463 | graphics::Rect::new(0.0, 0.0, width, height)).unwrap(); 464 | self.force_redraw = true; 465 | } 466 | } 467 | 468 | 469 | fn main() { 470 | // Make a Context and an EventLoop. 471 | let (mut ctx, mut event_loop) = 472 | ContextBuilder::new("wdem_tracker", "Weird Constructor") 473 | .window_setup(ggez::conf::WindowSetup { 474 | title: "wdem_tracker".to_owned(), 475 | samples: ggez::conf::NumSamples::Four, 476 | ..Default::default() 477 | }) 478 | .window_mode(ggez::conf::WindowMode { 479 | width: 640.0, 480 | height: 480.0, 481 | maximized: false, 482 | fullscreen_type: ggez::conf::FullscreenType::Windowed, 483 | borderless: false, 484 | min_width: 0.0, 485 | max_width: 0.0, 486 | min_height: 0.0, 487 | max_height: 0.0, 488 | resizable: true, 489 | }) 490 | .build() 491 | .unwrap(); 492 | 493 | let mut engine = WDemTrackerGUI::new(&mut ctx); 494 | engine.init(); 495 | 496 | match event::run(&mut ctx, &mut event_loop, &mut engine) { 497 | Ok(_) => println!("Exited cleanly."), 498 | Err(e) => println!("Error occured: {}", e) 499 | } 500 | } 501 | -------------------------------------------------------------------------------- /res/wdemtracker_sc_20190907.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeirdConstructor/wdem-tracker/676c94c6e27d1b83197c06e4346eb89fc0c4eeb9/res/wdemtracker_sc_20190907.png -------------------------------------------------------------------------------- /src/audio.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex, Condvar}; 2 | 3 | struct AudioQueue { 4 | backend_ready: bool, 5 | sample_rate: usize, 6 | ringbuf_len: usize, 7 | samples: std::collections::VecDeque, 8 | } 9 | 10 | pub struct AudioDev { 11 | mx: Mutex, 12 | cv: Condvar, 13 | cv_put: Condvar, 14 | } 15 | 16 | impl AudioDev { 17 | pub fn new() -> Self { 18 | AudioDev { 19 | mx: Mutex::new(AudioQueue { 20 | backend_ready: false, 21 | sample_rate: 0, 22 | ringbuf_len: 0, 23 | samples: std::collections::VecDeque::new(), 24 | }), 25 | cv: Condvar::new(), 26 | cv_put: Condvar::new(), 27 | } 28 | } 29 | 30 | pub fn backend_ready(&self, sample_rate: usize, ringbuf_len: usize) { 31 | let mut aq = self.mx.lock().unwrap(); 32 | if aq.backend_ready { return; } 33 | 34 | aq.sample_rate = sample_rate; 35 | aq.ringbuf_len = ringbuf_len; 36 | aq.backend_ready = true; 37 | self.cv.notify_one(); 38 | } 39 | 40 | pub fn get_stereo_samples(&self, stereo_out: &mut [f32]) { 41 | let mut aq = self.mx.lock().unwrap(); 42 | let len = 43 | if stereo_out.len() < aq.ringbuf_len { 44 | stereo_out.len() 45 | } else { 46 | aq.ringbuf_len 47 | }; 48 | 49 | while aq.samples.len() < len { 50 | aq = self.cv_put.wait(aq).unwrap(); 51 | } 52 | 53 | for (i, s) in aq.samples.drain(0..len).enumerate() { 54 | stereo_out[i] = s; 55 | } 56 | if stereo_out.len() > len { 57 | for i in len..stereo_out.len() { 58 | stereo_out[i] = 0.0; 59 | } 60 | } 61 | self.cv_put.notify_one(); 62 | } 63 | } 64 | 65 | pub struct AudioFrontend { 66 | dev: Arc, 67 | sample_rate: usize, 68 | ringbuf_len: usize, 69 | } 70 | 71 | impl AudioFrontend { 72 | pub fn new() -> Self { 73 | AudioFrontend { 74 | dev: Arc::new(AudioDev::new()), 75 | sample_rate: 0, 76 | ringbuf_len: 0, 77 | } 78 | } 79 | 80 | pub fn get_dev(&self) -> Arc { self.dev.clone() } 81 | 82 | pub fn wait_backend_ready(&mut self) { 83 | let mut ad = self.dev.mx.lock().unwrap(); 84 | while !ad.backend_ready { 85 | ad = self.dev.cv.wait(ad).unwrap(); 86 | } 87 | 88 | self.sample_rate = ad.sample_rate; 89 | self.ringbuf_len = ad.ringbuf_len; 90 | } 91 | 92 | pub fn get_sample_rate(&self) -> usize { self.sample_rate } 93 | 94 | pub fn get_latency_in_samples(&self) -> usize { 95 | // div by 2 cause of stereo out 96 | self.ringbuf_len / 2 97 | } 98 | 99 | pub fn put_samples_blocking(&mut self, buf: &[f32]) { 100 | let mut ad = self.dev.mx.lock().unwrap(); 101 | while ad.samples.len() >= self.ringbuf_len { 102 | ad = self.dev.cv_put.wait(ad).unwrap(); 103 | } 104 | 105 | for s in buf.iter() { ad.samples.push_back(*s); } 106 | self.dev.cv_put.notify_one(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/audio_dev_thread.rs: -------------------------------------------------------------------------------- 1 | use crate::audio::*; 2 | use std::sync::Arc; 3 | 4 | pub fn start_audio_thread(audio_dev: Arc) { 5 | 6 | let ad = audio_dev.clone(); 7 | std::thread::spawn(move || { 8 | use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait}; 9 | let host = cpal::default_host(); 10 | let event_loop = host.event_loop(); 11 | let device = host.default_output_device().expect("no output device available"); 12 | let format = device.default_output_format().expect("proper default format"); 13 | println!("FORMAT: {:?}", format); 14 | let stream_id = event_loop.build_output_stream(&device, &format).unwrap(); 15 | event_loop.play_stream(stream_id).expect("failed to play_stream"); 16 | 17 | let sample_rate = if let cpal::SampleRate(r) = format.sample_rate { 18 | r 19 | } else { 20 | 44100 21 | }; 22 | 23 | let channels = format.channels as usize; 24 | 25 | let mut avg_buf_len = 0; 26 | let mut avg_buf_cnt = 0; 27 | let avg_buf_len_samples = 10; 28 | let mut startup = true; 29 | 30 | let mut last_call_instant = std::time::Instant::now(); 31 | let mut cnt = 0; 32 | 33 | use cpal::{StreamData, UnknownTypeOutputBuffer}; 34 | event_loop.run(move |stream_id, stream_result| { 35 | let stream_data = match stream_result { 36 | Ok(data) => data, 37 | Err(err) => { 38 | eprintln!("an error occurred on stream {:?}: {}", stream_id, err); 39 | return; 40 | } 41 | }; 42 | 43 | match stream_data { 44 | StreamData::Output { buffer: UnknownTypeOutputBuffer::U16(mut buffer) } => { 45 | println!("FOFOE3"); 46 | for elem in buffer.iter_mut() { 47 | *elem = u16::max_value() / 2; 48 | } 49 | }, 50 | StreamData::Output { buffer: UnknownTypeOutputBuffer::I16(mut buffer) } => { 51 | println!("FOFOE2"); 52 | for elem in buffer.iter_mut() { 53 | *elem = 0; 54 | } 55 | }, 56 | StreamData::Output { buffer: UnknownTypeOutputBuffer::F32(mut buffer) } => { 57 | if startup { 58 | if avg_buf_cnt < avg_buf_len_samples { 59 | avg_buf_len += buffer.len(); 60 | avg_buf_cnt += 1; 61 | 62 | for elem in buffer.iter_mut() { 63 | *elem = 0.0; 64 | } 65 | 66 | return; 67 | } else { 68 | audio_dev.backend_ready( 69 | sample_rate as usize, 70 | ((avg_buf_len / avg_buf_cnt) as f64 * 1.5).ceil() as usize); 71 | println!("AVG BUF SIZE: {}", avg_buf_len / avg_buf_cnt); 72 | startup = false; 73 | } 74 | } 75 | let m = std::time::Instant::now(); 76 | 77 | audio_dev.get_stereo_samples(&mut buffer); 78 | 79 | cnt += 1; 80 | if cnt % 200 == 0 { 81 | println!("Audio time ms: cycle={}us, wait={}us ", 82 | last_call_instant.elapsed().as_micros(), 83 | m.elapsed().as_micros()); 84 | } 85 | last_call_instant = std::time::Instant::now(); 86 | 87 | // for elem in buffer.iter_mut() { 88 | // *elem = 0.0; 89 | // } 90 | }, 91 | _ => (), 92 | } 93 | }); 94 | }); 95 | } 96 | 97 | -------------------------------------------------------------------------------- /src/color_opin.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use serde::Deserialize; 3 | use wctr_signal_ops::signals::OpIn; 4 | 5 | #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)] 6 | pub struct ColorIn { 7 | pub h: OpIn, 8 | pub s: OpIn, 9 | pub v: OpIn, 10 | pub a: OpIn, 11 | } 12 | 13 | impl ColorIn { 14 | pub fn calc(&self, regs: &[f32]) -> [f32; 4] { 15 | extern crate palette; 16 | use palette::{Rgb}; 17 | let hue : palette::Hsv = 18 | palette::Hsv::new( 19 | self.h.calc(regs).into(), 20 | self.s.calc(regs), 21 | self.v.calc(regs)); 22 | let rc : Rgb = hue.into(); 23 | 24 | [ 25 | rc.red, 26 | rc.green, 27 | rc.blue, 28 | self.a.calc(regs) 29 | ] 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/ggez_gui_painter.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | use std::cell::RefCell; 3 | use ggez::{Context, ContextBuilder, GameResult}; 4 | use ggez::graphics; 5 | use crate::gui_painter; 6 | 7 | pub struct GGEZPainter { 8 | pub reg_view_font: graphics::Font, 9 | pub text_cache: std::collections::HashMap<(usize, String), graphics::Text>, 10 | } 11 | 12 | impl GGEZPainter { 13 | fn draw_lines(&mut self, ctx: &mut Context, color: [f32; 4], pos: [f32; 2], 14 | points: &[[f32; 2]], filled: bool, thickness: f32) { 15 | let pl = 16 | graphics::Mesh::new_polyline( 17 | ctx, 18 | if filled { 19 | graphics::DrawMode::fill() 20 | } else { 21 | graphics::DrawMode::stroke(thickness) 22 | }, 23 | points, 24 | graphics::Color::from(color)).unwrap(); 25 | graphics::draw( 26 | ctx, &pl, ([pos[0], pos[1]], 0.0, [0.0, 0.0], graphics::WHITE)).unwrap(); 27 | } 28 | 29 | fn draw_rect(&mut self, ctx: &mut Context, color: [f32; 4], pos: [f32; 2], 30 | size: [f32; 2], filled: bool, thickness: f32) { 31 | let r = 32 | graphics::Mesh::new_rectangle( 33 | ctx, 34 | if filled { 35 | graphics::DrawMode::fill() 36 | } else { 37 | graphics::DrawMode::stroke(thickness) 38 | }, 39 | graphics::Rect::new(0.0, 0.0, size[0], size[1]), 40 | graphics::Color::from(color)).unwrap(); 41 | graphics::draw( 42 | ctx, &r, ([pos[0], pos[1]], 0.0, [0.0, 0.0], graphics::WHITE)).unwrap(); 43 | } 44 | 45 | fn draw_text(&mut self, ctx: &mut Context, color: [f32; 4], pos: [f32; 2], size: f32, text: String) { 46 | let us = (size * 1000.0) as usize; 47 | let key = (us, text.clone()); 48 | let txt = self.text_cache.get(&key); 49 | let txt_elem = if let Some(t) = txt { 50 | t 51 | } else { 52 | let t = graphics::Text::new((text, self.reg_view_font, size)); 53 | self.text_cache.insert(key.clone(), t); 54 | self.text_cache.get(&key).unwrap() 55 | }; 56 | 57 | graphics::queue_text( 58 | ctx, txt_elem, pos, Some(color.into())); 59 | } 60 | 61 | fn finish_draw_text(&mut self, ctx: &mut Context) { 62 | graphics::draw_queued_text( 63 | ctx, 64 | graphics::DrawParam::default(), 65 | None, 66 | graphics::FilterMode::Linear).unwrap(); 67 | } 68 | } 69 | 70 | pub struct GGEZGUIPainter<'b> { 71 | pub p: Rc>, 72 | pub c: &'b mut ggez::Context, 73 | pub offs: (f32, f32), 74 | pub area: (f32, f32), 75 | } 76 | 77 | impl<'b> gui_painter::GUIPainter for GGEZGUIPainter<'b> { 78 | fn start(&mut self) { } 79 | fn draw_lines(&mut self, color: [f32; 4], mut pos: [f32; 2], points: &[[f32; 2]], filled: bool, thickness: f32) { 80 | pos[0] += self.offs.0; 81 | pos[1] += self.offs.1; 82 | self.p.borrow_mut().draw_lines(&mut self.c, color, pos, points, filled, thickness); 83 | } 84 | fn draw_rect(&mut self, color: [f32; 4], mut pos: [f32; 2], size: [f32; 2], filled: bool, thickness: f32) { 85 | pos[0] += self.offs.0; 86 | pos[1] += self.offs.1; 87 | self.p.borrow_mut().draw_rect(&mut self.c, color, pos, size, filled, thickness); 88 | } 89 | fn draw_text(&mut self, color: [f32; 4], mut pos: [f32; 2], size: f32, text: String) { 90 | pos[0] += self.offs.0 - 0.5; 91 | pos[1] += self.offs.1 - 0.5; 92 | self.p.borrow_mut().draw_text(&mut self.c, color, pos, size, text); 93 | } 94 | fn show(&mut self) { 95 | self.p.borrow_mut().finish_draw_text(&mut self.c); 96 | } 97 | 98 | fn set_offs(&mut self, offs: (f32, f32)) { self.offs = offs; } 99 | fn get_offs(&mut self) -> (f32, f32) { self.offs } 100 | fn set_area_size(&mut self, area: (f32, f32)) { self.area = area; } 101 | fn get_area_size(&mut self) -> (f32, f32) { self.area } 102 | } 103 | 104 | -------------------------------------------------------------------------------- /src/gui_painter.rs: -------------------------------------------------------------------------------- 1 | pub trait GUIPainter { 2 | fn start(&mut self); 3 | fn draw_rect(&mut self, color: [f32; 4], pos: [f32; 2], size: [f32; 2], filled: bool, thickness: f32); 4 | fn draw_text(&mut self, color: [f32; 4], pos: [f32; 2], size: f32, text: String); 5 | fn draw_lines(&mut self, color: [f32; 4], pos: [f32; 2], points: &[[f32; 2]], filled: bool, thickness: f32); 6 | fn show(&mut self); 7 | 8 | fn add_offs(&mut self, x: f32, y: f32) { 9 | let o = self.get_offs(); 10 | self.set_offs((o.0 + x, o.1 + y)); 11 | } 12 | fn set_offs(&mut self, offs: (f32, f32)); 13 | fn get_offs(&mut self) -> (f32, f32); 14 | fn set_area_size(&mut self, area: (f32, f32)); 15 | fn get_area_size(&mut self) -> (f32, f32); 16 | } 17 | -------------------------------------------------------------------------------- /src/key_shortcut_help.rs: -------------------------------------------------------------------------------- 1 | pub fn get_shortcut_help_page(page: usize) -> String { 2 | match page { 3 | 1 => String::from(r#" 4 | [Step] Mode: 5 | 6 | When entering the mode the step is reset to 1, from that 7 | you can change it with these keys: 8 | 9 | 0 - Multiply by 10 10 | 1 - 9 - Add a value (1 to 9). 11 | any other key - Go back to [Normal] mode. 12 | 13 | [File] Mode: 14 | w - Write contents of trackers and input values of 15 | signal ops to `tracker.json` file. 16 | r - Read contents of trackers and input values from 17 | `tracker.json` again. 18 | 19 | [Interpolation] Mode: 20 | s - Step (no interpolation) 21 | l - Linear interpolation 22 | e - Exponential interpolation 23 | t - Smoothstep interpolation 24 | "#), 25 | 2 => String::from(r#" 26 | [Note] Mode: 27 | 28 | Remember: In [Normal] mode you can always press the Alt key 29 | and a key from the [Note] mode to enter a note on the fly. 30 | 31 | + / - - Go an octave up/down 32 | yxcvbnm - Octave+0 White keys from C to B 33 | sdghj - Octave+0 Black keys from C# to A# 34 | 35 | qwertzu - Octave+1 White keys from C to B 36 | 23567 - Octave+1 Black keys from C# to A# 37 | 38 | iop - Octave+2 White keys from C to E 39 | 90 - Octave+2 Black keys from C# to D# 40 | 41 | [ScrollOps] Mode: 42 | h / j / k / l - Scroll the signal groups / operators 43 | 44 | [A] / [B] Mode: 45 | 0-9 / A-F / a-f - Enter 2 hex digits 46 | "#), 47 | _ => String::from( 48 | r#" 49 | WDem Tracker - Keyboard Reference 50 | ================================= 51 | - Hit ESC to get back. 52 | - Space/PageDown for next page. 53 | - Backspace/PageUp for previous page. 54 | 55 | [Normal] Mode: 56 | h / l - Move cursor to left/right track. 57 | j / k - Step cursor down/up a row. 58 | Shift + j / k - Move cursor down/up exactly 1 row (regardless of the 59 | step size). 60 | s - Go to `Step` mode for setting the step size. 61 | x - Delete contents of cursor cell. 62 | f - Go to `File` mode, for writing/reading the 63 | current contents of the tracks and input signals. 64 | y - Refresh signal operator from background thread. 65 | i - Go to `Interpolation` mode for setting the interpolation 66 | of the current track. 67 | ' ' (space) - Pause/Unpause the tracker. 68 | '#' - Go to `Note` mode for entering notes by keyboard. 69 | For quickly entering notes hit the Alt key and the 70 | notes on the keyboard according to `Note` mode. 71 | 'o' - Go to `ScrollOps` mode for scrolling the displayed 72 | signal groups and operators using the h/j/k/l keys. 73 | n / m - Stop the tracker and move the play cursor up/down a row. 74 | a - Go to `A` mode for entering the A 8-bit hex value. 75 | b - Go to `B` mode for entering the B 8-bit hex value. 76 | - / . / 0-9 - For entering a value, just start typing the value 77 | and hit Return or some other key. 78 | "#), 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod gui_painter; 2 | pub mod ggez_gui_painter; 3 | pub mod tracker; 4 | pub mod track; 5 | pub mod tracker_editor; 6 | pub mod scopes; 7 | pub mod vval_opin; 8 | pub mod color_opin; 9 | pub mod audio; 10 | pub mod key_shortcut_help; 11 | pub mod audio_dev_thread; 12 | pub mod operator_gui; 13 | pub mod tracker_thread; 14 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate serde_json; 2 | extern crate ggez; 3 | 4 | use std::io::prelude::*; 5 | use wdem_tracker::track::*; 6 | use wdem_tracker::tracker::*; 7 | use wdem_tracker::tracker_editor::*; 8 | use wdem_tracker::scopes::{Scopes, SCOPE_SAMPLES, SCOPE_WIDTH}; 9 | use wctr_signal_ops::*; 10 | use wdem_tracker::audio::*; 11 | use wdem_tracker::key_shortcut_help::*; 12 | use wdem_tracker::ggez_gui_painter::{GGEZPainter, GGEZGUIPainter}; 13 | use wdem_tracker::operator_gui::*; 14 | use wdem_tracker::tracker_thread::*; 15 | 16 | use wlambda; 17 | use wlambda::{VVal, GlobalEnv, EvalContext, Env}; 18 | 19 | use std::rc::Rc; 20 | use std::sync::Arc; 21 | use std::sync::Mutex; 22 | use std::cell::RefCell; 23 | 24 | use ggez::{Context, ContextBuilder, GameResult}; 25 | use ggez::event::{self, EventHandler, quit, MouseButton}; 26 | use ggez::graphics; 27 | use ggez::input::keyboard::{KeyCode, KeyMods, is_mod_active}; 28 | use ggez::input::mouse::button_pressed; 29 | use ggez::input::mouse::set_cursor_grabbed; 30 | use ggez::input::mouse::set_cursor_hidden; 31 | use ggez::input::mouse::set_position; 32 | 33 | /* Synth 34 | 35 | - Add DemOp I/O names 36 | - Make a Track an DemOp 37 | - Have 4 outputs: Note, Value, A, B. Note/A/B is 0-256, Value is any. 38 | - Make an DemOpUI, which takes an op index and a name (from wlambda for instance) 39 | the DemOpUI queries the backend Simulator for details about the OP I/O count 40 | and names. 41 | - The UI communicates to the Signal thread via DemOpUIMessage enum. 42 | - The Simulator can send it's config out via a mpsc channel or some 43 | other kind of way. It's triggered by a DemOpUIMessage::GetConfig. 44 | 45 | 46 | - Parameters are just one large array of f32 47 | - Indexes are per device (each device has a index <-> name mapping for access) 48 | - values are calc'ed from the signal regs and inserted at their index. 49 | - also static values are calced that way 50 | - make a configurable link of static values and a GUI element somehow 51 | - should also be able to set values of static registers?! 52 | (maybe some static input Op, that acts as device with inputs?) 53 | => have an array of OpIn's for the device, device communicates the mapping, 54 | have one global signal device with customizable mapping 55 | - configuration by wlambda 56 | 57 | 58 | 59 | */ 60 | #[derive(Debug, PartialEq, Copy, Clone)] 61 | enum InputMode { 62 | Normal, 63 | Interpolation, 64 | Step, 65 | Value, 66 | A, 67 | B, 68 | Note, 69 | OpInValue(usize, usize), 70 | FileActions, 71 | ScrollOps, 72 | HelpScreen(usize), 73 | } 74 | 75 | struct WDemTrackerGUI { 76 | tracker: Rc>>, 77 | editor: TrackerEditor, 78 | painter: Rc>, 79 | force_redraw: bool, 80 | tracker_thread_out: std::sync::Arc>, 81 | i: i32, 82 | mode: InputMode, 83 | step: i32, 84 | scopes: Scopes, 85 | audio_scopes: Scopes, 86 | num_txt: String, 87 | octave: u8, 88 | status_line: String, 89 | grabbed_mpos: Option<[f32; 2]>, 90 | ref_mpos: [f32; 2], 91 | op_inp_set: OperatorInputSettings, 92 | evctx: wlambda::compiler::EvalContext, 93 | } 94 | 95 | impl WDemTrackerGUI { 96 | pub fn new(ctx: &mut Context) -> WDemTrackerGUI { 97 | let (sync_tx, sync_rx) = std::sync::mpsc::channel::(); 98 | 99 | let mut simcom = SimulatorCommunicator::new(); 100 | 101 | let sync = ThreadTrackSync::new(sync_tx); 102 | let out = std::sync::Arc::new(std::sync::Mutex::new(TrackerThreadOutput::new())); 103 | 104 | let genv = GlobalEnv::new_default(); 105 | let mut wl_eval_ctx = 106 | wlambda::compiler::EvalContext::new(genv); 107 | 108 | let msgh = wlambda::threads::MsgHandle::new(); 109 | let snd = msgh.sender(); 110 | 111 | let scopes = 112 | start_tracker_thread( 113 | msgh, 114 | out.clone(), 115 | sync_rx, 116 | simcom.get_endpoint()); 117 | 118 | let audio_scopes = Scopes::new(0); 119 | 120 | snd.register_on_as(&mut wl_eval_ctx, "audio"); 121 | 122 | match wl_eval_ctx.eval_file("tracker.wl") { 123 | Ok(_) => (), 124 | Err(e) => { panic!(format!("SCRIPT ERROR: {}", e)); } 125 | } 126 | 127 | let font = graphics::Font::new(ctx, "/DejaVuSansMono.ttf").unwrap(); 128 | let trk = Rc::new(RefCell::new(Tracker::new(sync))); 129 | let mut ctx = WDemTrackerGUI { 130 | tracker: trk.clone(), 131 | editor: TrackerEditor::new(trk), 132 | tracker_thread_out: out, 133 | force_redraw: true, 134 | mode: InputMode::Normal, 135 | step: 0, 136 | i: 0, 137 | ref_mpos: [0.0, 0.0], 138 | num_txt: String::from(""), 139 | octave: 4, 140 | grabbed_mpos: None, 141 | status_line: String::from("(F1 - Help, q - Quit)"), 142 | op_inp_set: OperatorInputSettings::new(simcom), 143 | evctx: wl_eval_ctx, 144 | scopes, 145 | audio_scopes, 146 | painter: Rc::new(RefCell::new(GGEZPainter { 147 | text_cache: std::collections::HashMap::new(), 148 | reg_view_font: font, 149 | })), 150 | }; 151 | 152 | ctx.op_inp_set.update(); 153 | 154 | ctx 155 | } 156 | 157 | pub fn get_status_text(&self) -> String { 158 | format!("[{:?}] {}", self.mode, self.status_line) 159 | } 160 | 161 | pub fn set_status_text(&mut self, txt: String) { 162 | self.status_line = txt; 163 | } 164 | 165 | pub fn init(&mut self) { 166 | for i in 0..6 { 167 | let lpp = self.tracker.borrow().lpp; 168 | let mut t = Track::new(&format!("xxx{}", i), lpp); 169 | t.touch_pattern_idx(1); 170 | t.touch_pattern_idx(2); 171 | t.set_arrangement_pattern(lpp, 2); 172 | t.set_arrangement_pattern(lpp * 2, 1); 173 | t.set_arrangement_pattern(lpp * 3, 0); 174 | self.tracker.borrow_mut().add_track(t); 175 | } 176 | } 177 | 178 | pub fn inp(&mut self, ti: TrackerInput) { 179 | self.editor.process_input(ti); 180 | } 181 | } 182 | 183 | fn write_file_safely(filename: &str, s: &str) -> std::io::Result<()> { 184 | let tmpfile = format!("{}~", filename); 185 | let mut file = std::fs::File::create(tmpfile.clone())?; 186 | file.write_all(s.as_bytes())?; 187 | std::fs::rename(tmpfile, filename)?; 188 | Ok(()) 189 | } 190 | 191 | impl EventHandler for WDemTrackerGUI { 192 | fn update(&mut self, _ctx: &mut Context) -> GameResult<()> { 193 | Ok(()) 194 | } 195 | 196 | fn key_down_event(&mut self, ctx: &mut Context, keycode: KeyCode, _keymods: KeyMods, _repeat: bool) { 197 | if keycode == KeyCode::Q { 198 | quit(ctx); 199 | } else if keycode == KeyCode::F1 { 200 | self.mode = InputMode::HelpScreen(0); 201 | } 202 | 203 | println!("KEY {:?}", keycode); 204 | 205 | match self.mode { 206 | InputMode::HelpScreen(p) => { 207 | match keycode { 208 | KeyCode::Space | KeyCode::PageDown => { 209 | let mut p = p + 1; 210 | if p > 2 { p = 0; } 211 | self.mode = InputMode::HelpScreen(p); 212 | }, 213 | KeyCode::Back | KeyCode::PageUp => { 214 | if p > 0 { 215 | let mut p = p - 1; 216 | self.mode = InputMode::HelpScreen(p); 217 | } 218 | }, 219 | _ => (), 220 | } 221 | }, 222 | _ => (), 223 | } 224 | } 225 | 226 | fn mouse_button_down_event(&mut self, _ctx: &mut Context, button: MouseButton, x: f32, y: f32) { 227 | if button == MouseButton::Right { 228 | if let Some((op_idx, in_idx)) = self.op_inp_set.hit_zone(x, y) { 229 | self.mode = InputMode::OpInValue(op_idx, in_idx); 230 | self.num_txt = String::from(""); 231 | self.set_status_text(format!("input value[]")); 232 | } 233 | 234 | } else if button == MouseButton::Middle { 235 | if let Some((op_idx, in_idx)) = self.op_inp_set.hit_zone(x, y) { 236 | self.op_inp_set.set_input_default(op_idx, in_idx); 237 | } 238 | } 239 | } 240 | 241 | fn mouse_motion_event(&mut self, ctx: &mut Context, x: f32, y: f32, 242 | mut xr: f32, mut yr: f32) { 243 | 244 | let sz = graphics::drawable_size(ctx); 245 | // XXX: Workaround for bug in winit, where on windows WM_MOUSEMOTION 246 | // is kept being sent to the application. And ggez does 247 | xr = x - self.ref_mpos[0]; 248 | yr = y - self.ref_mpos[1]; 249 | 250 | let mouse_is_grabbed = 251 | self.op_inp_set.handle_mouse_move( 252 | x, y, xr, yr, button_pressed(ctx, MouseButton::Left)); 253 | 254 | if mouse_is_grabbed { 255 | if self.grabbed_mpos.is_none() { 256 | self.grabbed_mpos = Some([x, y]); 257 | set_position(ctx, [sz.0 / 2.0, sz.1 / 2.0]); 258 | self.ref_mpos = [sz.0 / 2.0, sz.1 / 2.0]; 259 | } 260 | 261 | } else { 262 | if self.grabbed_mpos.is_some() { 263 | set_position(ctx, self.grabbed_mpos.unwrap()); 264 | } 265 | self.grabbed_mpos = None; 266 | } 267 | } 268 | 269 | fn text_input_event(&mut self, ctx: &mut Context, character: char) { 270 | println!("CHR: {:?}", character); 271 | 272 | if character == '\u{1b}' { self.mode = InputMode::Normal; } 273 | 274 | let mode = 275 | if is_mod_active(ctx, KeyMods::ALT) { 276 | InputMode::Note 277 | } else { 278 | self.mode 279 | }; 280 | 281 | match mode { 282 | InputMode::Normal => { 283 | self.set_status_text(String::from("(F1 - Help, q - Quit)")); 284 | match character { 285 | 's' => { 286 | self.mode = InputMode::Step; 287 | self.step = 0; 288 | }, 289 | 'x' => { 290 | self.editor.process_input(TrackerInput::Delete); 291 | }, 292 | 'h' => { 293 | self.editor.process_input(TrackerInput::TrackLeft); 294 | }, 295 | 'f' => { 296 | self.mode = InputMode::FileActions; 297 | self.set_status_text(format!("'w' write, 'r' read")); 298 | }, 299 | 'y' => { 300 | self.op_inp_set.update(); 301 | }, 302 | 'j' | 'J' => { 303 | if is_mod_active(ctx, KeyMods::SHIFT) { 304 | self.editor.process_input(TrackerInput::RowDown); 305 | } else { 306 | self.editor.process_input(TrackerInput::StepDown); 307 | } 308 | }, 309 | 'k' | 'K' => { 310 | if is_mod_active(ctx, KeyMods::SHIFT) { 311 | self.editor.process_input(TrackerInput::RowUp); 312 | } else { 313 | self.editor.process_input(TrackerInput::StepUp); 314 | } 315 | }, 316 | 'l' => { 317 | self.editor.process_input(TrackerInput::TrackRight); 318 | }, 319 | 'i' => { 320 | self.mode = InputMode::Interpolation; 321 | }, 322 | ' ' => { 323 | self.editor.process_input( 324 | TrackerInput::PlayHead(PlayHeadAction::TogglePause)); 325 | }, 326 | '#' => { 327 | self.mode = InputMode::Note; 328 | }, 329 | 'o' => { 330 | self.mode = InputMode::ScrollOps; 331 | 332 | }, 333 | 'n' => { 334 | self.editor.process_input( 335 | TrackerInput::PlayHead(PlayHeadAction::PrevLine)); 336 | }, 337 | 'm' => { 338 | self.editor.process_input( 339 | TrackerInput::PlayHead(PlayHeadAction::NextLine)); 340 | }, 341 | 'a' => { 342 | self.num_txt = String::from(""); 343 | self.mode = InputMode::A; 344 | }, 345 | 'b' => { 346 | self.num_txt = String::from(""); 347 | self.mode = InputMode::B; 348 | }, 349 | '-' | '.' | '0'..='9' => { 350 | self.num_txt = String::from(""); 351 | self.num_txt.push(character); 352 | self.mode = InputMode::Value; 353 | self.set_status_text(format!("value[{}]", self.num_txt)); 354 | }, 355 | _ => { }, 356 | } 357 | }, 358 | InputMode::Note => { 359 | let mut note = 0; 360 | // XXX: This is just german layout :-/ 361 | match character { 362 | '+' => { if self.octave < 9 { self.octave += 1; } }, 363 | '-' => { if self.octave > 0 { self.octave -= 1; } }, 364 | 'y' => { note = (self.octave + 1) * 12 + 0; }, // C 365 | 's' => { note = (self.octave + 1) * 12 + 1; }, // C# 366 | 'x' => { note = (self.octave + 1) * 12 + 2; }, // D 367 | 'd' => { note = (self.octave + 1) * 12 + 3; }, // D# 368 | 'c' => { note = (self.octave + 1) * 12 + 4; }, // E 369 | 'v' => { note = (self.octave + 1) * 12 + 5; }, // F 370 | 'g' => { note = (self.octave + 1) * 12 + 6; }, // F# 371 | 'b' => { note = (self.octave + 1) * 12 + 7; }, // G 372 | 'h' => { note = (self.octave + 1) * 12 + 8; }, // G# 373 | 'n' => { note = (self.octave + 1) * 12 + 9; }, // A 374 | 'j' => { note = (self.octave + 1) * 12 + 10; }, // A# 375 | 'm' => { note = (self.octave + 1) * 12 + 11; }, // B 376 | 377 | 'q' => { note = (self.octave + 2) * 12 + 0; }, // C 378 | '2' => { note = (self.octave + 2) * 12 + 1; }, // C# 379 | 'w' => { note = (self.octave + 2) * 12 + 2; }, // D 380 | '3' => { note = (self.octave + 2) * 12 + 3; }, // D# 381 | 'e' => { note = (self.octave + 2) * 12 + 4; }, // E 382 | 'r' => { note = (self.octave + 2) * 12 + 5; }, // F 383 | '5' => { note = (self.octave + 2) * 12 + 6; }, // F# 384 | 't' => { note = (self.octave + 2) * 12 + 7; }, // G 385 | '6' => { note = (self.octave + 2) * 12 + 8; }, // G# 386 | 'z' => { note = (self.octave + 2) * 12 + 9; }, // A 387 | '7' => { note = (self.octave + 2) * 12 + 10; }, // A# 388 | 'u' => { note = (self.octave + 2) * 12 + 11; }, // B 389 | 390 | 'i' => { note = (self.octave + 3) * 12 + 0; }, // C 391 | '9' => { note = (self.octave + 3) * 12 + 1; }, // C# 392 | 'o' => { note = (self.octave + 3) * 12 + 2; }, // D 393 | '0' => { note = (self.octave + 3) * 12 + 3; }, // D# 394 | 'p' => { note = (self.octave + 3) * 12 + 4; }, // E 395 | _ => { }, 396 | } 397 | 398 | self.set_status_text(format!("octave[{}]", self.octave)); 399 | 400 | if note > 0 { 401 | self.inp(TrackerInput::SetNote(note)); 402 | self.editor.process_input(TrackerInput::StepDown); 403 | } 404 | }, 405 | InputMode::A => { 406 | match character { 407 | '0'..='9' | 'A'..='F' | 'a'..='f' => { 408 | self.num_txt.push(character); 409 | self.set_status_text(format!("a[{}]", self.num_txt)); 410 | }, 411 | _ => { } 412 | } 413 | 414 | if self.num_txt.len() >= 2 { 415 | self.inp(TrackerInput::SetA( 416 | u8::from_str_radix(&self.num_txt, 16).unwrap_or(0))); 417 | self.mode = InputMode::Normal; 418 | } 419 | }, 420 | InputMode::B => { 421 | match character { 422 | '0'..='9' | 'A'..='F' | 'a'..='f' => { 423 | self.num_txt.push(character); 424 | self.set_status_text(format!("a[{}]", self.num_txt)); 425 | }, 426 | _ => { } 427 | } 428 | 429 | if self.num_txt.len() >= 2 { 430 | self.inp(TrackerInput::SetB( 431 | u8::from_str_radix(&self.num_txt, 16).unwrap_or(0))); 432 | self.mode = InputMode::Normal; 433 | } 434 | }, 435 | InputMode::OpInValue(op_idx, in_idx) => { 436 | match character { 437 | '-' | '.' | '0'..='9' => { 438 | self.num_txt.push(character); 439 | }, 440 | '\r' => { 441 | self.op_inp_set.set_input_val( 442 | op_idx, in_idx, 443 | self.num_txt.parse::().unwrap_or(0.0)); 444 | self.mode = InputMode::Normal; 445 | }, 446 | _ => { } 447 | } 448 | 449 | self.set_status_text(format!("input value[{}]", self.num_txt)); 450 | }, 451 | InputMode::Value => { 452 | match character { 453 | '-' | '.' | '0'..='9' => { 454 | self.num_txt.push(character); 455 | }, 456 | '\r' => { 457 | self.inp(TrackerInput::SetValue( 458 | self.num_txt.parse::().unwrap_or(0.0))); 459 | self.mode = InputMode::Normal; 460 | }, 461 | _ => { } 462 | } 463 | 464 | self.set_status_text(format!("value[{}]", self.num_txt)); 465 | }, 466 | InputMode::Interpolation => { 467 | match character { 468 | 'e' => { self.inp(TrackerInput::SetInterpExp); }, 469 | 't' => { self.inp(TrackerInput::SetInterpSStep); }, 470 | 's' => { self.inp(TrackerInput::SetInterpStep); }, 471 | 'l' => { self.inp(TrackerInput::SetInterpLerp); }, 472 | _ => { }, 473 | } 474 | 475 | self.mode = InputMode::Normal; 476 | }, 477 | InputMode::Step => { 478 | match character { 479 | '0' => { self.step *= 10; }, 480 | '1' => { self.step += 1; }, 481 | '2' => { self.step += 2; }, 482 | '3' => { self.step += 3; }, 483 | '4' => { self.step += 4; }, 484 | '5' => { self.step += 5; }, 485 | '6' => { self.step += 6; }, 486 | '7' => { self.step += 7; }, 487 | '8' => { self.step += 8; }, 488 | '9' => { self.step += 9; }, 489 | _ => { self.mode = InputMode::Normal; }, 490 | } 491 | 492 | self.set_status_text(format!("step[{}]", self.step)); 493 | 494 | self.editor.process_input( 495 | TrackerInput::SetStep(self.step as usize)); 496 | }, 497 | InputMode::FileActions => { 498 | match character { 499 | 'w' => { 500 | let s = self.op_inp_set.save_input_values(); 501 | let st = self.editor.tracker.borrow().serialize_tracks(); 502 | 503 | match serde_json::to_string_pretty(&(s, st)) { 504 | Ok(s) => { 505 | match write_file_safely("tracker.json", &s) { 506 | Ok(()) => { 507 | self.set_status_text( 508 | format!("everything written ok")); 509 | }, 510 | Err(e) => { 511 | self.set_status_text( 512 | format!("write error 'tracker.json': {}", e)); 513 | println!("tracker.json WRITE ERROR: {}", e); 514 | } 515 | } 516 | }, 517 | Err(e) => { 518 | self.set_status_text(format!("serialize error: {}", e)); 519 | println!("SERIALIZE ERROR: {}", e); 520 | } 521 | }; 522 | }, 523 | 'r' => { 524 | match std::fs::File::open("tracker.json") { 525 | Ok(mut file) => { 526 | let mut c = String::new(); 527 | match file.read_to_string(&mut c) { 528 | Ok(_) => { 529 | match serde_json::from_str(&c) { 530 | Ok(v) => { 531 | let v : (Vec<(String, Vec<(String, OpIn)>)>, Vec) = v; 532 | self.op_inp_set.load_input_values(&v.0); 533 | self.editor.tracker.borrow_mut().deserialize_tracks(v.1); 534 | self.op_inp_set.update(); 535 | }, 536 | Err(e) => { 537 | self.set_status_text( 538 | format!("deserialize error 'tracker.json': {}", e)); 539 | println!("tracker.json DESERIALIZE ERROR: {}", e); 540 | }, 541 | } 542 | }, 543 | Err(e) => { 544 | self.set_status_text( 545 | format!("read error 'tracker.json': {}", e)); 546 | println!("tracker.json READ ERROR: {}", e); 547 | } 548 | } 549 | }, 550 | Err(e) => { 551 | self.set_status_text( 552 | format!("open error 'tracker.json': {}", e)); 553 | println!("tracker.json OPEN ERROR: {}", e); 554 | } 555 | } 556 | // valmap = serde_json::from_str(s).unwrap_or(valmap); 557 | }, 558 | _ => (), 559 | } 560 | 561 | self.mode = InputMode::Normal; 562 | }, 563 | InputMode::ScrollOps => { 564 | match character { 565 | 'h' => { 566 | if self.op_inp_set.scroll_offs.0 > 0 { 567 | self.op_inp_set.scroll_offs.0 -= 1; 568 | } 569 | }, 570 | 'l' => { 571 | self.op_inp_set.scroll_offs.0 += 1; 572 | }, 573 | 'j' => { 574 | self.op_inp_set.scroll_offs.1 += 1; 575 | }, 576 | 'k' => { 577 | if self.op_inp_set.scroll_offs.1 > 0 { 578 | self.op_inp_set.scroll_offs.1 -= 1; 579 | } 580 | }, 581 | _ => { self.mode = InputMode::Normal; }, 582 | } 583 | 584 | self.set_status_text( 585 | format!("offset[{}, {}]", 586 | self.op_inp_set.scroll_offs.0, 587 | self.op_inp_set.scroll_offs.1)); 588 | }, 589 | InputMode::HelpScreen(_) => { 590 | }, 591 | } 592 | } 593 | 594 | fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { 595 | 596 | self.i += 1; 597 | if self.i > 100 { 598 | println!("FPS: {}", ggez::timer::fps(ctx)); 599 | self.i = 0; 600 | } 601 | 602 | let sz = graphics::drawable_size(ctx); 603 | 604 | self.force_redraw = true; 605 | if self.force_redraw || self.editor.need_redraw() { 606 | use wdem_tracker::gui_painter::GUIPainter; 607 | 608 | graphics::clear(ctx, graphics::BLACK); 609 | let mut play_line = 0; 610 | let mut cpu = (0.0, 0.0, 0.0); 611 | 612 | if let Ok(mut out) = self.tracker_thread_out.lock() { 613 | play_line = out.pos; 614 | cpu = out.cpu; 615 | if out.audio_scope_done { 616 | self.audio_scopes.update_from_audio_bufs( 617 | &out.audio_scope_samples); 618 | 619 | for ass in out.audio_scope_samples.iter_mut() { 620 | ass.clear(); 621 | } 622 | out.audio_scope_done = false; 623 | } 624 | 625 | } 626 | 627 | 628 | self.force_redraw = false; 629 | let mut p : GGEZGUIPainter = 630 | GGEZGUIPainter { p: self.painter.clone(), c: ctx, offs: (0.0, 0.0), area: (0.0, 0.0) }; 631 | 632 | match self.mode { 633 | InputMode::HelpScreen(page) => { 634 | p.set_offs((10.0, 10.0)); 635 | p.draw_rect( 636 | [0.2, 0.2, 0.2, 1.0], [0.0, 0.0], 637 | [sz.0 - 20.0, sz.1 - 20.0], true, 0.0); 638 | p.draw_rect( 639 | [1.0, 1.0, 1.0, 1.0], [0.0, 0.0], 640 | [sz.0 - 20.0, sz.1 - 20.0], false, 2.0); 641 | p.add_offs(5.0, 5.0); 642 | 643 | p.draw_text( 644 | [1.0, 1.0, 1.0, 1.0], [0.0, 0.0], 645 | 15.0, 646 | format!("[page {}/3] (navigation: Space/Backspace or PageUp/PageDown)\n", page + 1) + 647 | &get_shortcut_help_page(page), 648 | ) // p.draw_text 649 | }, 650 | _ => { 651 | p.set_offs(((sz.0 - 126.0).floor() + 0.5, 0.5)); 652 | p.draw_text( 653 | [1.0, 1.0, 1.0, 1.0], 654 | [0.0, 0.0], 655 | 10.0, 656 | format!("CPU {:6.2}/{:6.2}/{:6.2}", cpu.0, cpu.1, cpu.2)); 657 | 658 | p.set_offs((0.5, 0.5)); 659 | p.draw_text([1.0, 0.0, 0.0, 1.0], [0.0, 0.0], 10.0, self.get_status_text()); 660 | 661 | p.set_offs((0.5, 20.5)); 662 | p.set_area_size((sz.0 - 2.0 * SCOPE_WIDTH, sz.1 / 2.0)); 663 | self.editor.draw(&mut p, play_line); 664 | 665 | let y_below_tracker = 40.5 + (sz.1 / 2.0).floor(); 666 | 667 | let areas = p.get_area_size(); 668 | p.set_offs((areas.0 + 0.5, 0.5)); 669 | p.set_area_size((SCOPE_WIDTH, sz.1 / 2.0)); 670 | self.scopes.update_from_sample_row(); 671 | self.scopes.draw_scopes(&mut p); 672 | 673 | p.add_offs(SCOPE_WIDTH, 0.0); 674 | p.set_area_size((SCOPE_WIDTH, sz.1 / 2.0)); 675 | self.audio_scopes.draw_scopes(&mut p); 676 | 677 | p.set_offs((0.5, y_below_tracker)); 678 | p.set_area_size((sz.0, sz.1 / 2.0)); 679 | self.op_inp_set.draw(&mut p); 680 | } 681 | } 682 | 683 | p.show(); 684 | } 685 | 686 | graphics::present(ctx) 687 | } 688 | 689 | fn resize_event(&mut self, ctx: &mut Context, width: f32, height: f32) { 690 | graphics::set_screen_coordinates(ctx, 691 | graphics::Rect::new(0.0, 0.0, width, height)).unwrap(); 692 | self.force_redraw = true; 693 | } 694 | } 695 | 696 | 697 | fn main() { 698 | use wave_sickle::helpers; 699 | wave_sickle::helpers::init_cos_tab(); 700 | 701 | // Make a Context and an EventLoop. 702 | let (mut ctx, mut event_loop) = 703 | ContextBuilder::new("wdem_tracker", "Weird Constructor") 704 | .window_setup(ggez::conf::WindowSetup { 705 | title: "wdem_tracker".to_owned(), 706 | samples: ggez::conf::NumSamples::Four, 707 | ..Default::default() 708 | }) 709 | .window_mode(ggez::conf::WindowMode { 710 | width: 640.0, 711 | height: 480.0, 712 | maximized: false, 713 | fullscreen_type: ggez::conf::FullscreenType::Windowed, 714 | borderless: false, 715 | min_width: 0.0, 716 | max_width: 0.0, 717 | min_height: 0.0, 718 | max_height: 0.0, 719 | resizable: true, 720 | }) 721 | .build() 722 | .unwrap(); 723 | 724 | let mut engine = WDemTrackerGUI::new(&mut ctx); 725 | engine.init(); 726 | 727 | match event::run(&mut ctx, &mut event_loop, &mut engine) { 728 | Ok(_) => println!("Exited cleanly."), 729 | Err(e) => println!("Error occured: {}", e) 730 | } 731 | } 732 | -------------------------------------------------------------------------------- /src/operator_gui.rs: -------------------------------------------------------------------------------- 1 | use wctr_signal_ops::*; 2 | use crate::gui_painter::GUIPainter; 3 | 4 | #[derive(Debug, PartialEq, Clone)] 5 | pub enum OperatorInputMode { 6 | Value(f32), 7 | RegInfo(String), 8 | } 9 | 10 | #[derive(Debug)] 11 | pub struct OperatorInputSettings { 12 | simcom: SimulatorCommunicator, 13 | pub specs: Vec<(OpIOSpec, OpInfo)>, 14 | pub groups: Vec<(String, Vec)>, 15 | // x/y/w/h, op_i, in_idx 16 | active_zones: Vec<([f32; 4], usize, usize)>, 17 | highlight: Option<(usize, usize)>, 18 | selection: Option<(usize, usize)>, 19 | orig_val: f32, 20 | pub scroll_offs: (usize, usize), 21 | } 22 | 23 | fn draw_op

(p: &mut P, op: &(OpIOSpec, OpInfo), highlight: &Option<(usize, usize)>, selection: &Option<(usize, usize)>) -> (f32, f32, Vec<([f32; 4], usize, usize)>) 24 | where P: GUIPainter { 25 | let inp_col_w : f32 = 180.0; 26 | let inp_col_wr: f32 = 70.0; 27 | let padding : f32 = 2.0; 28 | let text_h : f32 = 12.0; 29 | 30 | let mut io_lens = op.0.input_values.len(); 31 | if op.0.output_regs.len() > io_lens { 32 | io_lens = op.0.output_regs.len(); 33 | } 34 | 35 | let op_h = (1 + io_lens) as f32 * text_h + padding * 2.0; 36 | let op_w = padding + inp_col_w + padding + inp_col_wr; 37 | 38 | p.draw_rect( 39 | [0.2, 0.2, 0.2, 1.0], [0.0, 0.0], [op_w, op_h], true, 0.1); 40 | p.draw_rect( 41 | [1.0, 0.0, 1.0, 1.0], [0.0, 0.0], [op_w, op_h], false, 0.5); 42 | 43 | p.add_offs(padding, padding); 44 | 45 | p.draw_text( 46 | [0.3, 1.0, 0.8, 1.0], [0.0, 0.0], text_h, format!("{}", op.1.name)); 47 | p.draw_text( 48 | [1.0, 0.3, 0.3, 1.0], [inp_col_w - (text_h + padding), 0.0], text_h, "IN".to_string()); 49 | p.draw_text( 50 | [0.3, 1.0, 0.3, 1.0], [inp_col_w + padding, 0.0], text_h, "OUT".to_string()); 51 | 52 | let mut y = text_h; 53 | 54 | let mut active_zones : Vec<([f32; 4], usize, usize)> = Vec::new(); 55 | 56 | for (idx, (i, is)) in op.0.input_values.iter().zip(op.0.inputs.iter()).enumerate() { 57 | let text = match i { 58 | OpIn::Constant(v) => { 59 | format!("{:>8.3}", *v) 60 | }, 61 | OpIn::Reg(u) => 62 | format!("r{}", *u), 63 | OpIn::RegMix2(u, u2, f) => 64 | format!("r{}x{:0.2}[{:0.2}]", *u, *u2, *f), 65 | OpIn::RegAdd(u, f) => 66 | format!("r{}+[{:0.2}]", *u, *f), 67 | OpIn::RegMul(u, f) => 68 | format!("r{}*[{:0.2}]", *u, *f), 69 | OpIn::RegAddMul(u, f, f2) => 70 | format!("(r{}+[{:0.2}])*[{:0.2}]", *u, *f, *f2), 71 | OpIn::RegMulAdd(u, f, f2) => 72 | format!("(r{}*[{:0.2}])+[{:0.2}]", *u, *f, *f2), 73 | OpIn::RegLerp(u, f, f2) => 74 | format!("r{}/[{:0.2}][{:0.2}]", *u, *f, *f2), 75 | OpIn::RegSStep(u, f, f2) => 76 | format!("r{}~[{:0.2}][{:0.2}]", *u, *f, *f2), 77 | OpIn::RegMap(u, f, f2, g, g2) => 78 | format!("r{}[{:0.2}-{:0.2}]->[{:0.2}-{:0.2}]", *u, *f, *f2, *g, *g2), 79 | }; 80 | 81 | let o = p.get_offs(); 82 | active_zones.push(([o.0, o.1 + y, inp_col_w, text_h], op.0.index, idx)); 83 | 84 | let mut highlighted = if let Some((op_idx, i_idx)) = highlight { 85 | *op_idx == op.0.index && idx == *i_idx 86 | } else { 87 | false 88 | }; 89 | 90 | // XXX: Because the mouse cursor is repositioned, we would 91 | // get flickering on neighbour elements. 92 | if selection.is_some() { 93 | highlighted = false; 94 | } 95 | 96 | let selected = if let Some((op_idx, i_idx)) = selection { 97 | *op_idx == op.0.index && idx == *i_idx 98 | } else { 99 | false 100 | }; 101 | 102 | p.draw_rect( 103 | if selected { [1.0, 0.2, 0.2, 1.0] } 104 | else { [0.4, 0.4, 0.4, 1.0] }, 105 | [0.0, y], 106 | [inp_col_w - 1.0, text_h - 1.0], 107 | !selected && highlighted, 108 | 0.5); 109 | 110 | p.draw_text( 111 | [1.0, 0.3, 0.8, 1.0], [0.0, y], text_h, 112 | format!("{:<7} {}", is.name, text)); 113 | 114 | y += text_h; 115 | } 116 | 117 | y = text_h; 118 | for (o, os) in op.0.output_regs.iter().zip(op.0.outputs.iter()) { 119 | p.draw_text( 120 | [1.0, 0.3, 0.8, 1.0], [inp_col_w + padding, y], text_h, 121 | format!("{:<7} r{}", os.name, o)); 122 | y += text_h; 123 | } 124 | 125 | p.add_offs(0.0, -padding); 126 | p.draw_lines( 127 | [1.0, 0.0, 1.0, 1.0], 128 | [inp_col_w, 0.0], 129 | &vec![[0.0, 0.0], [0.0, op_h]], 130 | false, 131 | 0.5); 132 | 133 | (op_w, op_h, active_zones) 134 | } 135 | 136 | impl OperatorInputSettings { 137 | pub fn new(simcom: SimulatorCommunicator) -> Self { 138 | OperatorInputSettings { 139 | simcom: simcom, 140 | specs: Vec::new(), 141 | groups: Vec::new(), 142 | active_zones: Vec::new(), 143 | highlight: None, 144 | selection: None, 145 | orig_val: 0.0, 146 | scroll_offs: (0, 0), 147 | } 148 | } 149 | 150 | pub fn save_input_values(&mut self) -> Vec<(String, Vec<(String, OpIn)>)> { 151 | self.simcom.save_input_values() 152 | } 153 | 154 | pub fn load_input_values(&mut self, inputs: &Vec<(String, Vec<(String, OpIn)>)>) { 155 | self.simcom.load_input_values(inputs); 156 | } 157 | 158 | pub fn hit_zone(&mut self, x: f32, y: f32) -> Option<(usize, usize)> { 159 | for az in self.active_zones.iter() { 160 | if x >= (az.0)[0] 161 | && y >= (az.0)[1] 162 | && x <= ((az.0)[0] + (az.0)[2]) 163 | && y <= ((az.0)[1] + (az.0)[3]) { 164 | 165 | return Some((az.1, az.2)); 166 | } 167 | } 168 | 169 | None 170 | } 171 | 172 | pub fn handle_mouse_move(&mut self, x: f32, y: f32, xr: f32, yr: f32, button_is_down: bool) -> bool { 173 | if !button_is_down { self.selection = None; } 174 | 175 | let old_highlight = self.highlight; 176 | 177 | self.highlight = None; 178 | 179 | for az in self.active_zones.iter() { 180 | if x >= (az.0)[0] 181 | && y >= (az.0)[1] 182 | && x <= ((az.0)[0] + (az.0)[2]) 183 | && y <= ((az.0)[1] + (az.0)[3]) { 184 | 185 | self.highlight = Some((az.1, az.2)); 186 | 187 | if button_is_down 188 | && self.selection.is_none() 189 | && old_highlight == self.highlight { 190 | 191 | self.selection = self.highlight; 192 | self.orig_val = self.get_selection_val(); 193 | return true; 194 | } 195 | break; 196 | } 197 | } 198 | 199 | if self.selection.is_some() && button_is_down { 200 | let exp = (10.0 as f64).powf((xr / 200.0).abs() as f64); 201 | let ampli = -((yr as f64 * exp) / 200.0) as f32; 202 | let s = self.selection.unwrap(); 203 | self.set_input_val(s.0, s.1, self.orig_val + ampli); 204 | return true; 205 | } 206 | 207 | false 208 | } 209 | 210 | pub fn set_input_default(&mut self, op_idx: usize, i_idx: usize) { 211 | let iname = self.specs[op_idx].0.inputs[i_idx].name.clone(); 212 | let default = self.specs[op_idx].0.input_defaults[i_idx]; 213 | self.specs[op_idx].0.input_values[i_idx] = default; 214 | self.simcom.set_op_input(op_idx, &iname, default, false); 215 | } 216 | 217 | pub fn set_input_val(&mut self, op_idx: usize, i_idx: usize, val: f32) { 218 | let iname = self.specs[op_idx].0.inputs[i_idx].name.clone(); 219 | self.specs[op_idx].0.input_values[i_idx] = OpIn::Constant(val); 220 | self.simcom.set_op_input(op_idx, &iname, OpIn::Constant(val), false); 221 | } 222 | 223 | pub fn get_selection_val(&self) -> f32 { 224 | if self.selection.is_some() { 225 | let s = self.selection.unwrap(); 226 | self.get_input_val(s.0, s.1) 227 | } else { 228 | 0.0 229 | } 230 | } 231 | 232 | pub fn get_input_val(&self, op_idx: usize, i_idx: usize) -> f32 { 233 | if let OpIn::Constant(v) = self.specs[op_idx].0.input_values[i_idx] { 234 | v 235 | } else { 236 | 0.0 237 | } 238 | } 239 | 240 | pub fn update(&mut self) { 241 | let r = self.simcom.update(|ev| { 242 | if let SimulatorUIEvent::OpSpecUpdate(up) = ev { 243 | Some(up) 244 | } else { None } 245 | }); 246 | 247 | if r.is_some() { 248 | self.update_from_spec(r.unwrap().unwrap()); 249 | } 250 | } 251 | 252 | fn update_from_spec(&mut self, specs: Vec<(OpIOSpec, OpInfo)>) { 253 | //d// println!("Updated: {:?}", specs); 254 | self.specs = specs; 255 | self.groups = Vec::new(); 256 | 257 | for iv in self.specs.iter() { 258 | let group = &iv.1.group; 259 | if group.index <= self.groups.len() { 260 | self.groups.resize(group.index + 1, ("".to_string(), Vec::new())); 261 | } 262 | } 263 | 264 | for i in 0..self.groups.len() { 265 | let ops : Vec = 266 | self.specs 267 | .iter() 268 | .filter(|o| o.1.group.index == i) 269 | .map(|o| o.0.index) 270 | .collect(); 271 | 272 | if ops.is_empty() { continue; } 273 | 274 | let group = self.specs[ops[0]].1.group.clone(); 275 | println!("OP: {:?} => {}", ops, group.name); 276 | 277 | self.groups[i] = (group.name.clone(), ops); 278 | } 279 | } 280 | 281 | pub fn draw

(&mut self, p: &mut P) where P: GUIPainter { 282 | let text_h = 10.0; 283 | 284 | self.active_zones = Vec::new(); 285 | 286 | let oo = p.get_offs(); 287 | 288 | let mut skip_groups_count = self.scroll_offs.1; 289 | for grp in self.groups.iter() { 290 | if skip_groups_count > 0 { 291 | skip_groups_count -= 1; 292 | continue; 293 | } 294 | 295 | p.draw_text([1.0, 1.0, 1.0, 1.0], [0.0, 0.0], text_h, grp.0.clone()); 296 | 297 | let ooo = p.get_offs(); 298 | 299 | let mut skip_ops_count = self.scroll_offs.0; 300 | 301 | let mut max_op_h = 0.0; 302 | for op_i in grp.1.iter() { 303 | if skip_ops_count > 0 { 304 | skip_ops_count -= 1; 305 | continue; 306 | } 307 | 308 | let op = &self.specs[*op_i]; 309 | let o = p.get_offs(); 310 | p.set_offs((o.0, o.1 + text_h)); 311 | 312 | let (w, h, zones) = draw_op(p, op, &self.highlight, &self.selection); 313 | self.active_zones.extend_from_slice(&zones); 314 | if h > max_op_h { max_op_h = h; } 315 | p.set_offs((o.0 + w + 3.0, o.1)); 316 | 317 | if (p.get_offs().0 - oo.0) > p.get_area_size().0 { 318 | break; 319 | } 320 | 321 | } 322 | 323 | p.set_offs(ooo); 324 | 325 | p.add_offs(0.0, max_op_h + text_h); 326 | } 327 | 328 | p.set_offs(oo); 329 | } 330 | } 331 | 332 | -------------------------------------------------------------------------------- /src/scopes.rs: -------------------------------------------------------------------------------- 1 | use wctr_signal_ops::sample_row::SampleRow; 2 | use crate::gui_painter::*; 3 | 4 | pub const SCOPE_SAMPLES : usize = 128; 5 | pub const SCOPE_WIDTH : f32 = 128.0; 6 | pub const SCOPE_HEIGHT : f32 = 48.0; 7 | const SCOPE_FONT_HEIGHT : f32 = 13.0; 8 | 9 | #[derive(Debug, PartialEq, Clone)] 10 | pub struct Scope { 11 | pub samples: Vec, 12 | recent_value: f32, 13 | pub points: Vec<[f32; 2]>, 14 | min: f32, 15 | max: f32, 16 | } 17 | 18 | impl Scope { 19 | fn new(sample_count: usize) -> Self { 20 | let mut v = Vec::new(); 21 | v.resize(sample_count, 0.0); 22 | let mut p = Vec::new(); 23 | p.resize(sample_count, [0.0; 2]); 24 | Scope { 25 | samples: v, 26 | points: p, 27 | min: 99999.0, 28 | max: -99999.0, 29 | recent_value: 0.0, 30 | } 31 | } 32 | 33 | fn draw

(&mut self, painter: &mut P, idx: usize, pos: [f32; 2], size: [f32; 2]) where P: GUIPainter { 34 | let x_offs : f32 = size[0] / self.samples.len() as f32; 35 | 36 | let mut diff = self.max - self.min; 37 | if diff <= std::f32::EPSILON { 38 | diff = 1.0; 39 | } 40 | 41 | for (v, (i, p)) in self.samples.iter().zip(self.points.iter_mut().enumerate()) { 42 | p[0] = x_offs * (i as f32); 43 | p[1] = size[1] - (((v - self.min) / diff) * size[1]); 44 | 45 | if self.min > *v { self.min = *v; } 46 | if self.max < *v { self.max = *v; } 47 | } 48 | 49 | painter.draw_lines( 50 | [0.0, 1.0, 0.0, 1.0], 51 | pos, 52 | &[[0.0, 0.0],[size[0], 0.0]], 53 | false, 54 | 0.5); 55 | painter.draw_lines( 56 | [0.0, 1.0, 0.0, 1.0], 57 | pos, 58 | &[[0.0, size[1]],[size[0], size[1]]], 59 | false, 60 | 0.5); 61 | painter.draw_lines( 62 | [0.0, 1.0, 0.0, 1.0], 63 | pos, 64 | &[[size[0], 0.0],[size[0], size[1]]], 65 | false, 66 | 0.5); 67 | if !self.points.is_empty() { 68 | painter.draw_lines( 69 | [1.0, 1.0, 1.0, 1.0], 70 | pos, 71 | &self.points, 72 | false, 73 | 0.5); 74 | } 75 | painter.draw_text( 76 | [1.0, 0.0, 1.0, 1.0], 77 | [pos[0], pos[1] + size[1]], 78 | SCOPE_FONT_HEIGHT, 79 | format!("r{} {:0.2}", idx, self.recent_value)); 80 | } 81 | } 82 | 83 | pub struct Scopes { 84 | pub sample_count: usize, 85 | pub scopes: Vec, 86 | pub sample_row: std::sync::Arc>, 87 | my_sample_row: SampleRow, 88 | } 89 | 90 | impl Scopes { 91 | pub fn new(sample_count: usize) -> Self { 92 | use std::sync::Arc; 93 | use std::sync::Mutex; 94 | 95 | Scopes { 96 | sample_count, 97 | scopes: Vec::new(), 98 | sample_row: Arc::new(Mutex::new(SampleRow::new())), 99 | my_sample_row: SampleRow::new(), 100 | } 101 | } 102 | 103 | pub fn update_from_audio_bufs(&mut self, bufs: &Vec>) { 104 | if bufs.len() != self.scopes.len() { 105 | self.scopes.resize( 106 | bufs.len() * 2, 107 | Scope::new(self.sample_count)); 108 | } 109 | 110 | for (i, ab) in bufs.iter().enumerate() { 111 | for channel in 0..2 { 112 | let s : &mut Scope = &mut self.scopes[(i * 2) + channel]; 113 | if s.samples.len() != ab.len() { 114 | self.sample_count = ab.len(); 115 | s.samples.resize(ab.len() / 2, 0.0); 116 | s.points.resize(ab.len() / 2, [0.0; 2]); 117 | } 118 | 119 | for (j, s) in s.samples.iter_mut().enumerate() { 120 | *s = ab[(j * 2) + channel]; 121 | } 122 | } 123 | } 124 | } 125 | 126 | pub fn update_from_sample_row(&mut self) { 127 | // use std::ops::DerefMut; 128 | 129 | if !self.sample_row.lock().unwrap().updated { 130 | return; 131 | } 132 | 133 | let old_pos = self.my_sample_row.pos; 134 | 135 | std::mem::swap( 136 | &mut *self.sample_row.lock().unwrap(), 137 | &mut self.my_sample_row); 138 | 139 | self.my_sample_row.updated = false; 140 | 141 | let len = self.my_sample_row.sample_row.len(); 142 | 143 | let pos = self.my_sample_row.pos; 144 | if self.scopes.len() < len { 145 | self.scopes.resize(len, Scope::new(self.sample_count)); 146 | } 147 | 148 | for (i, s) in self.my_sample_row.sample_row.iter().enumerate() { 149 | self.scopes[i].recent_value = *s; 150 | // println!("RECENT {}", *s); 151 | 152 | let mut j = old_pos; 153 | while j != pos { 154 | j = (j + 1) % self.sample_count; 155 | self.scopes[i].samples[j] = *s; 156 | } 157 | } 158 | } 159 | 160 | pub fn draw_scopes

(&mut self, p: &mut P) where P: GUIPainter { 161 | let scope_width = SCOPE_WIDTH; 162 | let scope_height = SCOPE_HEIGHT; 163 | let font_height = SCOPE_FONT_HEIGHT; 164 | let elem_height = scope_height + SCOPE_FONT_HEIGHT; 165 | let per_row = (p.get_area_size().0 / scope_width).ceil() as usize; 166 | let max_rows = (p.get_area_size().1 / elem_height).ceil() as usize; 167 | 168 | if per_row <= 0 { return; } 169 | 170 | let s = p.get_area_size(); 171 | 172 | p.draw_rect( 173 | [0.3, 0.1, 0.1, 1.0], 174 | [0.0, 0.0], 175 | [s.0, s.1], 176 | true, 1.0); 177 | 178 | for (i, s) in self.scopes.iter_mut().enumerate() { 179 | let row_idx = i % per_row; 180 | if (1 + (i / per_row)) >= max_rows { break; } 181 | let y = (scope_height + font_height) * ((i / per_row) as f32); 182 | s.draw( 183 | p, 184 | i, 185 | [(row_idx as f32) * scope_width, y], 186 | [scope_width, scope_height]); 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/track.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use serde::Deserialize; 3 | use crate::gui_painter::GUIPainter; 4 | 5 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 6 | pub enum Interpolation { 7 | Empty, 8 | Step, 9 | Lerp, 10 | SStep, 11 | Exp, 12 | } 13 | 14 | impl std::default::Default for Interpolation { 15 | fn default() -> Self { Interpolation::Empty } 16 | } 17 | 18 | #[derive(Debug, Copy, Clone, PartialEq)] 19 | struct InterpolationState { 20 | line_a: usize, 21 | line_b: usize, 22 | val_a: f32, 23 | val_b: f32, 24 | int: Interpolation, 25 | desync: bool, 26 | } 27 | 28 | impl std::default::Default for InterpolationState { 29 | fn default() -> Self { InterpolationState::new() } 30 | } 31 | 32 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 33 | pub struct Row { 34 | pub value: Option<(f32, Interpolation)>, 35 | pub a: u8, 36 | pub b: u8, 37 | pub note: u8, 38 | } 39 | 40 | impl Row { 41 | fn new() -> Self { 42 | Row { 43 | value: None, 44 | a: 0, 45 | b: 0, 46 | note: 0, 47 | } 48 | } 49 | 50 | pub fn draw

(&self, p: &mut P, state: &mut GUIState, line: usize) where P: GUIPainter { 51 | let val_s = 52 | if let Some((val, int)) = self.value { 53 | format!("{:>6.2}{}", 54 | val, 55 | match int { 56 | Interpolation::Empty => "e", 57 | Interpolation::Step => "_", 58 | Interpolation::Lerp => "/", 59 | Interpolation::SStep => "~", 60 | Interpolation::Exp => "^", 61 | }) 62 | } else { 63 | String::from("------ ") 64 | }; 65 | 66 | let note_s = match self.note { 67 | 0 => String::from("---"), 68 | 1 => String::from("off"), 69 | n => format!("{:<4}", note2name(n)), 70 | }; 71 | 72 | let s = 73 | if state.track_index == 0 { 74 | format!("{:<05} |{:<02}|{:<4}{:>7}|{:02X} {:02X}|", 75 | line, 76 | state.pattern_index, 77 | note_s, val_s, self.a, self.b) 78 | } else { 79 | format!("|{:<02}|{:<4}{:>7}|{:02X} {:02X}|", 80 | state.pattern_index, 81 | note_s, val_s, self.a, self.b) 82 | }; 83 | 84 | let color = 85 | if state.cursor_on_line && state.play_on_line { 86 | [0.8, 0.8, 0.4, 1.0] 87 | } else if state.play_on_line { 88 | [0.8, 0.4, 0.4, 1.0] 89 | } else if state.cursor_on_line { 90 | [0.4, 0.8, 0.4, 1.0] 91 | } else { 92 | [0.0, 0.0, 0.0, 1.0] 93 | }; 94 | 95 | let txt_color = 96 | if state.cursor_on_line || state.play_on_line { 97 | if state.on_beat { [0.0, 0.4, 0.0, 1.0] } 98 | else { [0.0, 0.0, 0.0, 1.0] } 99 | } else { 100 | if state.on_beat { [0.6, 1.0, 0.6, 1.0] } 101 | else { [0.8, 0.8, 0.8, 1.0] } 102 | }; 103 | 104 | let width = 105 | if state.track_index == 0 { 106 | FIRST_TRACK_WIDTH 107 | } else { 108 | TRACK_WIDTH 109 | }; 110 | p.draw_rect( 111 | color, 112 | [0.0, 0.0], 113 | [width, ROW_HEIGHT], true, 0.5); 114 | p.draw_text(txt_color, [0.0, 0.0], ROW_HEIGHT * 0.9, s); 115 | } 116 | } 117 | 118 | impl InterpolationState { 119 | fn new() -> Self { 120 | InterpolationState { 121 | line_a: 0, 122 | line_b: 0, 123 | val_a: 0.0, 124 | val_b: 0.0, 125 | int: Interpolation::Empty, 126 | desync: true, 127 | } 128 | } 129 | 130 | fn clear(&mut self) { 131 | self.int = Interpolation::Empty; 132 | } 133 | 134 | fn to_end(&mut self, l: usize, d: &Row, end_line: usize) { 135 | self.line_a = l; 136 | self.val_a = d.value.unwrap_or((0.0, Interpolation::Step)).0; 137 | self.int = d.value.unwrap_or((0.0, Interpolation::Step)).1; 138 | self.line_b = end_line; 139 | self.val_b = 0.0; 140 | } 141 | 142 | fn to_next(&mut self, l: usize, d: &Row, lb: usize, db: &Row) { 143 | self.line_a = l; 144 | self.val_a = d.value.unwrap_or((0.0, Interpolation::Step)).0; 145 | self.int = d.value.unwrap_or((0.0, Interpolation::Step)).1; 146 | self.line_b = lb; 147 | self.val_b = db.value.unwrap_or((0.0, Interpolation::Step)).0; 148 | } 149 | 150 | fn desync(&mut self) { 151 | self.clear(); 152 | self.desync = true; 153 | } 154 | } 155 | 156 | const NOTE_NAMES : &'static [&str] = &["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; 157 | 158 | fn note2name(note: u8) -> String { 159 | if note == 0 { return String::from(""); } 160 | 161 | let octave : i32 = (note / 12) as i32 - 1; 162 | let name_idx : usize = (note % 12) as usize; 163 | format!("{}{}", NOTE_NAMES[name_idx], octave) 164 | } 165 | 166 | pub const TPOS_PAD : f32 = 50.0; 167 | pub const TRACK_PAD : f32 = 0.0; 168 | pub const TRACK_WIDTH : f32 = 160.0; 169 | pub const FIRST_TRACK_WIDTH : f32 = TRACK_WIDTH + 40.0; 170 | pub const ROW_HEIGHT : f32 = 15.0; 171 | pub const ROW_COMPR_FACT : f32 = 0.8; 172 | pub const CONTEXT_LINES : usize = 6; 173 | 174 | pub struct GUIState { 175 | pub cursor_track_idx: usize, 176 | pub cursor_line: usize, 177 | pub play_line: i32, 178 | pub cursor_on_track: bool, 179 | pub track_index: usize, 180 | pub pattern_index: usize, 181 | pub play_on_line: bool, 182 | pub on_beat: bool, 183 | pub lpb: usize, 184 | pub cursor_on_line: bool, 185 | pub scroll_offs: usize, 186 | } 187 | 188 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 189 | pub struct Track { 190 | pub name: String, 191 | #[serde(skip)] 192 | interpol: InterpolationState, 193 | // if index is at or above desired key, interpolate 194 | // else set index = 0 and restart search for right key 195 | pub lpp: usize, 196 | pub patterns: Vec>, 197 | pub arrangement: Vec, // arrangement of the patterns 198 | } 199 | 200 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 201 | pub struct TrackSerialized { 202 | pub name: String, 203 | pub patterns: Vec>, 204 | pub arrangement: Vec, // arrangement of the patterns 205 | } 206 | 207 | impl Track { 208 | pub fn new(name: &str, lpp: usize) -> Self { 209 | let mut fp = Vec::new(); 210 | fp.resize(lpp, Row::new()); 211 | 212 | Track { 213 | name: String::from(name), 214 | interpol: InterpolationState::new(), 215 | patterns: vec![fp], 216 | arrangement: vec![0], 217 | lpp, 218 | } 219 | } 220 | 221 | pub fn draw

(&self, p: &mut P, state: &mut GUIState) where P: GUIPainter { 222 | let rows = (p.get_area_size().1 / (ROW_HEIGHT * ROW_COMPR_FACT)) as usize; 223 | 224 | let lines = self.line_count(); 225 | let offs = 226 | if rows > 2 * CONTEXT_LINES { 227 | if state.cursor_line < (state.scroll_offs + CONTEXT_LINES) { 228 | let mut cl : i32 = state.cursor_line as i32; 229 | cl -= CONTEXT_LINES as i32; 230 | if cl < 0 { cl = 0; } 231 | cl as usize 232 | } else if state.cursor_line > (state.scroll_offs + (rows - CONTEXT_LINES)) { 233 | let mut cl = state.cursor_line as i32; 234 | cl -= (rows - CONTEXT_LINES) as i32; 235 | if cl < 0 { cl = 0; } 236 | cl as usize 237 | } else { 238 | state.scroll_offs 239 | } 240 | } else { 241 | if state.cursor_line >= (rows / 2) { 242 | state.cursor_line - (rows / 2) 243 | } else { 244 | 0 245 | } 246 | }; 247 | 248 | state.scroll_offs = offs; 249 | 250 | let from = offs; 251 | let to = if from + rows > lines { lines } else { from + rows }; 252 | 253 | // rows is the nums of displayable lines 254 | // the window of viewed rows should be stable and only scroll if 255 | // the cursor is close to the edge of an window. 256 | 257 | let o = p.get_offs(); 258 | 259 | p.draw_text( 260 | [1.0, 1.0, 1.0, 1.0], 261 | [0.0, 0.2 * ROW_HEIGHT], 262 | 0.8 * ROW_HEIGHT, 263 | self.name.clone()); 264 | p.add_offs(0.0, ROW_HEIGHT); 265 | 266 | for l in from..to { 267 | state.play_on_line = state.play_line == l as i32; 268 | state.cursor_on_line = state.cursor_on_track && state.cursor_line == l; 269 | state.on_beat = (l % state.lpb) == 0; 270 | 271 | if let Some((pat_idx, row)) = self.row_checked(l) { 272 | state.pattern_index = pat_idx; 273 | row.draw(p, state, l); 274 | } 275 | 276 | p.add_offs(0.0, ROW_HEIGHT * ROW_COMPR_FACT); 277 | } 278 | 279 | p.set_offs(o); 280 | } 281 | 282 | pub fn deserialize_contents(&mut self, ts: &TrackSerialized) { 283 | self.patterns = ts.patterns.clone(); 284 | self.arrangement = ts.arrangement.clone(); 285 | self.desync(); 286 | } 287 | 288 | pub fn serialize_contents(&self) -> TrackSerialized { 289 | TrackSerialized { 290 | name: self.name.clone(), 291 | patterns: self.patterns.clone(), 292 | arrangement: self.arrangement.clone(), 293 | } 294 | } 295 | 296 | pub fn set_arrangement_pattern(&mut self, line: usize, pat_idx: usize) { 297 | if pat_idx < self.patterns.len() { 298 | let arr_idx = line / self.lpp; 299 | while arr_idx >= self.arrangement.len() { 300 | self.arrangement.push(pat_idx) 301 | } 302 | } 303 | } 304 | 305 | pub fn touch_pattern_idx(&mut self, pat_idx: usize) { 306 | if pat_idx >= self.patterns.len() { 307 | let mut fp = Vec::new(); 308 | fp.resize(self.lpp, Row::new()); 309 | self.patterns.resize(pat_idx + 1, fp); 310 | } 311 | } 312 | 313 | pub fn line_count(&self) -> usize { 314 | self.arrangement.len() * self.lpp 315 | } 316 | 317 | pub fn row_checked(&self, line: usize) -> Option<(usize, Row)> { 318 | if line >= self.line_count() { return None; } 319 | Some(( 320 | self.arrangement[line / self.lpp], 321 | self.patterns[self.arrangement[line / self.lpp]][line % self.lpp].clone() 322 | )) 323 | } 324 | 325 | pub fn row(&mut self, line: usize) -> &mut Row { 326 | &mut self.patterns[self.arrangement[line / self.lpp]][line % self.lpp] 327 | } 328 | 329 | pub fn prev_row_with_value(&mut self, line: usize) -> Option<(usize, Row)> { 330 | let mut ll = line; 331 | while ll > 0 { 332 | let row = &self.patterns[self.arrangement[(ll - 1) / self.lpp]][(ll - 1) % self.lpp]; 333 | if (*row).value.is_some() { 334 | return Some(((ll - 1), row.clone())); 335 | } 336 | ll -= 1; 337 | } 338 | 339 | None 340 | } 341 | 342 | pub fn next_row_with_value(&mut self, line: usize) -> Option<(usize, Row)> { 343 | let mut ll = line; 344 | let lc = self.line_count(); 345 | while ll < lc { 346 | let pat_idx = self.arrangement[ll / self.lpp]; 347 | let row = &self.patterns[pat_idx][ll % self.lpp]; 348 | if (*row).value.is_some() { 349 | return Some((ll, row.clone())); 350 | } 351 | ll += 1; 352 | } 353 | 354 | None 355 | } 356 | 357 | pub fn touch_row(&mut self, line: usize) -> &mut Row { 358 | let a = line / self.lpp; 359 | while a >= self.arrangement.len() { 360 | self.patterns.push(Vec::new()); 361 | let last_idx = self.patterns.len() - 1; 362 | self.patterns[last_idx].resize(self.lpp, Row::new()); 363 | self.arrangement.push(self.patterns.len() - 1); 364 | } 365 | 366 | &mut self.patterns[self.arrangement[a]][line % self.lpp] 367 | } 368 | 369 | pub fn desync(&mut self) { 370 | self.interpol.desync(); 371 | } 372 | 373 | pub fn remove_value(&mut self, line: usize) { 374 | *self.touch_row(line) = Row::new(); 375 | self.desync(); 376 | } 377 | 378 | pub fn set_int(&mut self, line: usize, int: Interpolation) { 379 | if let Some((v, _i)) = (*self.touch_row(line)).value { 380 | (*self.touch_row(line)).value = Some((v, int)); 381 | } else { 382 | (*self.touch_row(line)).value = Some((0.0, int)); 383 | } 384 | self.desync(); 385 | } 386 | 387 | pub fn set_note(&mut self, line: usize, value: u8) { 388 | (*self.touch_row(line)).note = value; 389 | self.desync(); 390 | } 391 | 392 | pub fn set_a(&mut self, line: usize, value: u8) { 393 | (*self.touch_row(line)).a = value; 394 | self.desync(); 395 | } 396 | 397 | pub fn set_b(&mut self, line: usize, value: u8) { 398 | (*self.touch_row(line)).b = value; 399 | self.desync(); 400 | } 401 | 402 | pub fn set_value(&mut self, line: usize, value: f32) { 403 | if let Some((_v, i)) = (*self.touch_row(line)).value { 404 | (*self.touch_row(line)).value = Some((value, i)); 405 | } else { 406 | (*self.touch_row(line)).value = Some((value, Interpolation::Step)); 407 | } 408 | self.desync(); 409 | } 410 | 411 | fn sync_interpol_to_play_line(&mut self, line: usize) -> Option { 412 | if let Some((l_a, row_a)) = self.next_row_with_value(line) { 413 | if let Some((l_b, row_b)) = self.prev_row_with_value(line) { 414 | self.interpol.to_next(l_b, &row_b, l_a, &row_a); 415 | } else { 416 | self.interpol.to_end(l_a, &row_a, self.line_count() - 1); 417 | } 418 | 419 | if l_a == line { Some(row_a) } 420 | else { None } 421 | } else { 422 | if let Some((l_b, row_b)) = self.prev_row_with_value(line) { 423 | self.interpol.to_end(l_b, &row_b, self.line_count() - 1); 424 | } else { 425 | self.interpol.clear(); 426 | } 427 | 428 | None 429 | } 430 | } 431 | 432 | /// Advances the play head to the line. The last line has to be 433 | /// specified for setting up the interpolations. 434 | /// Should be called in order of the track events, othewise 435 | /// desync() should be called first. 436 | pub fn play_line(&mut self, line: usize) -> Option { 437 | self.sync_interpol_to_play_line(line); 438 | 439 | let r = self.row(line); 440 | if r.note > 0 { Some(r.clone()) } else { None } 441 | } 442 | 443 | /// Returns the interpolated value of this track at the specified line. 444 | /// Only works if the interpolation was 445 | /// initialized with self.sync_interpol_to_play_line() in self.play_line()! 446 | pub fn get_value(&mut self, line: usize, fract_next_line: f64) -> f32 { 447 | let i = &mut self.interpol; 448 | 449 | if line < i.line_a { 450 | i.clear(); 451 | } 452 | 453 | let mut diff = i.line_b - i.line_a; 454 | if diff == 0 { diff = 1; } 455 | let diff = diff as f64; 456 | let line_f = line as f64 + fract_next_line; 457 | 458 | match i.int { 459 | Interpolation::Empty => 0.0, 460 | Interpolation::Step => { 461 | if line == i.line_b { 462 | i.val_b 463 | } else { 464 | i.val_a 465 | } 466 | }, 467 | Interpolation::Lerp => { 468 | let x = (line_f - (i.line_a as f64)) / diff; 469 | ( i.val_a as f64 * (1.0 - x) 470 | + i.val_b as f64 * x) 471 | as f32 472 | }, 473 | Interpolation::SStep => { 474 | let x = (line_f - (i.line_a as f64)) / diff; 475 | let x = if x < 0.0 { 0.0 } else { x }; 476 | let x = if x > 1.0 { 1.0 } else { x }; 477 | let x = x * x * (3.0 - 2.0 * x); 478 | 479 | ( i.val_a as f64 * (1.0 - x) 480 | + i.val_b as f64 * x) 481 | as f32 482 | }, 483 | Interpolation::Exp => { 484 | let x = (line_f - (i.line_a as f64)) / diff; 485 | let x = x * x; 486 | 487 | ( i.val_a as f64 * (1.0 - x) 488 | + i.val_b as f64 * x) 489 | as f32 490 | }, 491 | } 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /src/tracker.rs: -------------------------------------------------------------------------------- 1 | use crate::track::*; 2 | use crate::gui_painter::GUIPainter; 3 | 4 | /// This trait handles the output of a Tracker when being driven 5 | /// by the tick() method. It generates events for starting notes 6 | /// on a synthesizer and returns a vector of the interpolated values 7 | /// on all tracks. The emit_play_line() function gives feedback of the 8 | /// current song position in terms of the track line index. 9 | pub trait OutputHandler { 10 | /// Called by Tracker::tick() when a new line started and 11 | /// a track has a new value defined. Useful for driving note on/off 12 | /// events on a synthesizer. 13 | fn emit_event(&mut self, track_idx: usize, row: &Row); 14 | /// Called when the Tracker::tick() function advanced to a new line. 15 | fn emit_play_line(&mut self, play_line: i32); 16 | /// Is used to output the song position in seconds. 17 | fn song_pos(&mut self) -> &mut f32; 18 | } 19 | 20 | #[derive(Debug, Copy, Clone, PartialEq)] 21 | pub enum PlayHeadAction { 22 | TogglePause, 23 | Pause, 24 | Play, 25 | Restart, 26 | NextLine, 27 | PrevLine, 28 | } 29 | 30 | /// This trait provides an interface to synchronize the track data 31 | /// between two Tracker instances. The main purpose is to connect a 32 | /// frontend Tracker with an audio thread tracker in such a way, that 33 | /// changes to the track data is transmitted to the backend thread. 34 | /// How threading is done is up to the implementor of this trait. 35 | /// You may even not use threads at all and use some network protocol 36 | /// for synchronization. 37 | pub trait TrackerSync { 38 | /// Called by Tracker when a new Track is added. 39 | fn add_track(&mut self, t: Track); 40 | /// Called by Tracker when the a note in a specific track and line is added. 41 | fn set_note(&mut self, track_idx: usize, line: usize, value: u8); 42 | /// Called by Tracker when a value in a specific track and line 43 | /// is added. 44 | fn set_value(&mut self, track_idx: usize, line: usize, value: f32); 45 | /// Called by Tracker when the a flag value in a specific track and line 46 | /// is added. 47 | fn set_a(&mut self, track_idx: usize, line: usize, value: u8); 48 | /// Called by Tracker when the b flag value in a specific track and line 49 | /// is added. 50 | fn set_b(&mut self, track_idx: usize, line: usize, value: u8); 51 | /// Called by Tracker when an interpolation for a value should be set. 52 | /// Does nothing if no value at that position exists. 53 | fn set_int(&mut self, track_idx: usize, line: usize, int: Interpolation); 54 | /// Called by Tracker when a value is removed from a track. 55 | fn remove_value(&mut self, track_idx: usize, line: usize); 56 | /// Called when the tracker should change the play head state: 57 | fn play_head(&mut self, _act: PlayHeadAction) { } 58 | /// Called when track data is loaded 59 | fn deserialize_contents(&mut self, track_idx: usize, contents: TrackSerialized); 60 | } 61 | 62 | /// This is a Tracker synchronizer that does nothing. 63 | /// Use it if you don't want to or not need to sync. 64 | pub struct TrackerNopSync { } 65 | 66 | impl TrackerSync for TrackerNopSync { 67 | fn add_track(&mut self, _t: Track) { } 68 | fn set_value(&mut self, _track_idx: usize, _line: usize, _value: f32) { } 69 | fn set_note(&mut self, _track_idx: usize, _line: usize, _value: u8) { } 70 | fn set_a(&mut self, _track_idx: usize, _line: usize, _value: u8) { } 71 | fn set_b(&mut self, _track_idx: usize, _line: usize, _value: u8) { } 72 | fn set_int(&mut self, _track_idx: usize, _line: usize, _int: Interpolation) { } 73 | fn remove_value(&mut self, _track_idx: usize, _line: usize) { } 74 | fn deserialize_contents(&mut self, _track_idx: usize, _contents: TrackSerialized) { } 75 | fn play_head(&mut self, _act: PlayHeadAction) { } 76 | } 77 | 78 | /// This structure stores the state of a tracker. 79 | /// It stores the play state aswell as the actual track data. 80 | /// The SYNC type must implement the TrackerSync trait. 81 | /// It is responsible for connecting the tracker frontend 82 | /// in a graphics thread with a tracker in the audio thread. 83 | pub struct Tracker where SYNC: TrackerSync { 84 | /// lines per beat 85 | pub lpb: usize, 86 | /// ticks per row/line 87 | pub tpl: usize, 88 | /// number of lines per pattern 89 | pub lpp: usize, 90 | /// current play head, if -1 it will start with line 0 91 | pub play_line: i32, 92 | /// The actual track data. 93 | pub tracks: Vec, 94 | /// the synchronization class: 95 | sync: SYNC, 96 | /// number of played ticks 97 | tick_count: usize, 98 | /// interval between ticks in ms 99 | pub tick_interval: usize, 100 | } 101 | 102 | //impl DemOp for Tracker where SYNC: TrackerSync { 103 | // fn io_spec(&self, index: usize) -> DemOpIOSpec { 104 | // DemOpIOSpec { 105 | // inputs: vec![], 106 | // input_values: vec![], 107 | // input_defaults: vec![], 108 | // outputs: self.tracks.iter().map(|t| DemOpPort::new(&t.name, -9999.0, 9999.0)).collect(), 109 | // output_regs: self.output_regs, 110 | // index, 111 | // } 112 | // } 113 | // 114 | // fn init_regs(&mut self, start_reg: usize, regs: &mut [f32]) { 115 | // } 116 | // 117 | // fn get_output_reg(&mut self, name: &str) -> Option { 118 | // None 119 | // } 120 | // 121 | // fn set_input(&mut self, name: &str, to: OpIn, as_default: bool) -> bool { 122 | // false 123 | // } 124 | // 125 | // fn exec(&mut self, t: f32, regs: &mut [f32]) { 126 | // } 127 | //} 128 | 129 | impl Tracker where SYNC: TrackerSync { 130 | pub fn new(sync: SYNC) -> Self { 131 | Tracker { 132 | lpb: 4, // => 4 beats are 1 `Tackt`(de) 133 | tpl: 10, 134 | tick_interval: 5, 135 | lpp: 32, 136 | tracks: Vec::new(), 137 | play_line: -1, 138 | tick_count: 0, 139 | sync, 140 | } 141 | } 142 | 143 | pub fn draw

(&self, p: &mut P, state: &mut GUIState) where P: GUIPainter { 144 | let mut display_track_count = (p.get_area_size().0 / TRACK_WIDTH).floor() as usize; 145 | if display_track_count < 1 { 146 | display_track_count = 1; 147 | } 148 | let o = p.get_offs(); 149 | 150 | let half_track_count = display_track_count / 2; 151 | let skip_cnt = 152 | if state.cursor_track_idx > half_track_count { 153 | state.cursor_track_idx - half_track_count 154 | } else { 155 | 0 156 | }; 157 | 158 | for (i, t) in self.tracks.iter().enumerate().skip(skip_cnt) { 159 | if display_track_count == 0 { break; } 160 | display_track_count -= 1; 161 | 162 | state.track_index = i; 163 | state.cursor_on_track = state.cursor_track_idx == i; 164 | state.lpb = self.lpb; 165 | t.draw(p, state); 166 | 167 | if i == 0 { 168 | p.add_offs(FIRST_TRACK_WIDTH, 0.0); 169 | } else { 170 | p.add_offs(TRACK_WIDTH, 0.0); 171 | } 172 | } 173 | 174 | p.set_offs(o); 175 | } 176 | 177 | pub fn tick2song_pos_in_s(&self) -> f32 { 178 | (((self.tick_count as f64) 179 | * (self.tick_interval as f64)) 180 | / 1000.0) as f32 181 | } 182 | 183 | pub fn add_track(&mut self, t: Track) { 184 | self.sync.add_track(t.clone()); 185 | self.tracks.push(t); 186 | } 187 | 188 | pub fn max_line_count(&self) -> usize { 189 | let mut count = 0; 190 | for t in self.tracks.iter() { 191 | if t.line_count() > count { 192 | count = t.line_count(); 193 | } 194 | } 195 | count 196 | } 197 | 198 | pub fn reset_pos(&mut self) { 199 | self.tick_count = 0; 200 | self.play_line = -1; 201 | self.resync_tracks(); 202 | } 203 | 204 | pub fn play_head(&mut self, a: PlayHeadAction) { 205 | self.sync.play_head(a); 206 | } 207 | 208 | pub fn tick_to_prev_line(&mut self, output: &mut T, values: &std::rc::Rc>>) 209 | where T: OutputHandler { 210 | 211 | if self.play_line > 0 { 212 | self.tick_count = 213 | ((self.play_line - 1) * self.tpl as i32) as usize; 214 | }; 215 | 216 | self.resync_tracks(); 217 | self.handle_tick_count_change(output, values); 218 | } 219 | 220 | pub fn tick_to_next_line(&mut self, output: &mut T, values: &std::rc::Rc>>) 221 | where T: OutputHandler { 222 | 223 | self.tick_count = 224 | if self.play_line < 0 { 225 | self.tpl as usize 226 | } else { 227 | ((self.play_line + 1) * self.tpl as i32) as usize 228 | }; 229 | 230 | self.handle_tick_count_change(output, values); 231 | } 232 | 233 | pub fn handle_tick_count_change(&mut self, output: &mut T, values: &std::rc::Rc>>) 234 | where T: OutputHandler { 235 | 236 | let line_count = self.max_line_count(); 237 | 238 | let mut new_play_line = self.tick_count / self.tpl; 239 | let fract_ticks = 240 | ((self.tick_count - (new_play_line * self.tpl)) as f64) 241 | / self.tpl as f64; 242 | 243 | if new_play_line >= line_count { 244 | new_play_line = 0; 245 | self.tick_count = 1; 246 | self.play_line = -1; 247 | self.resync_tracks(); 248 | } 249 | 250 | if new_play_line as i32 != self.play_line { 251 | output.emit_play_line(new_play_line as i32); 252 | 253 | for (track_idx, t) in self.tracks.iter_mut().enumerate() { 254 | let e = t.play_line(new_play_line); 255 | if let Some(row) = e { 256 | output.emit_event(track_idx, &row); 257 | } 258 | } 259 | } 260 | //d// println!("TC: {} {}/{}", self.tick_count, new_play_line, self.play_line); 261 | 262 | self.play_line = new_play_line as i32; 263 | 264 | *(output.song_pos()) = self.tick2song_pos_in_s(); 265 | 266 | let mut v = values.borrow_mut(); 267 | for (idx, t) in self.tracks.iter_mut().enumerate() { 268 | if idx >= v.len() { break; } 269 | v[idx] = t.get_value(new_play_line, fract_ticks); 270 | } 271 | } 272 | 273 | pub fn tick(&mut self, output: &mut T, values: &std::rc::Rc>>) 274 | where T: OutputHandler { 275 | 276 | self.tick_count += 1; 277 | self.handle_tick_count_change(output, values); 278 | } 279 | 280 | pub fn set_int(&mut self, track_idx: usize, line: usize, int: Interpolation) { 281 | self.sync.set_int(track_idx, line, int); 282 | self.tracks[track_idx].set_int(line, int); 283 | } 284 | 285 | pub fn set_note(&mut self, track_idx: usize, line: usize, v: u8) { 286 | self.sync.set_note(track_idx, line, v); 287 | self.tracks[track_idx].set_note(line, v); 288 | } 289 | 290 | pub fn set_a(&mut self, track_idx: usize, line: usize, v: u8) { 291 | self.sync.set_a(track_idx, line, v); 292 | self.tracks[track_idx].set_a(line, v); 293 | } 294 | 295 | pub fn set_b(&mut self, track_idx: usize, line: usize, v: u8) { 296 | self.sync.set_b(track_idx, line, v); 297 | self.tracks[track_idx].set_b(line, v); 298 | } 299 | 300 | pub fn set_value(&mut self, track_idx: usize, line: usize, value: f32) { 301 | self.sync.set_value(track_idx, line, value); 302 | self.tracks[track_idx].set_value(line, value); 303 | } 304 | 305 | fn resync_tracks(&mut self) { 306 | for t in self.tracks.iter_mut() { t.desync(); } 307 | } 308 | 309 | pub fn remove_value(&mut self, track_idx: usize, line: usize) { 310 | self.sync.remove_value(track_idx, line); 311 | self.tracks[track_idx].remove_value(line); 312 | } 313 | 314 | pub fn serialize_tracks(&self) -> Vec { 315 | self.tracks.iter().map(|t| t.serialize_contents()).collect() 316 | } 317 | 318 | pub fn deserialize_contents(&mut self, track_idx: usize, contents: TrackSerialized) { 319 | self.sync.deserialize_contents(track_idx, contents.clone()); 320 | println!("DESER {}", track_idx); 321 | self.tracks[track_idx].deserialize_contents(&contents); 322 | } 323 | 324 | pub fn deserialize_tracks(&mut self, tracks: Vec) { 325 | for ts in tracks.iter() { 326 | if let Some((i, _)) = 327 | self.tracks.iter_mut().enumerate().find(|(_, t)| t.name == ts.name) { 328 | self.deserialize_contents(i, ts.clone()); 329 | } 330 | } 331 | } 332 | 333 | } 334 | 335 | 336 | -------------------------------------------------------------------------------- /src/tracker_editor.rs: -------------------------------------------------------------------------------- 1 | use crate::tracker::*; 2 | use crate::track::*; 3 | use crate::gui_painter::*; 4 | use std::rc::Rc; 5 | use std::cell::RefCell; 6 | 7 | pub struct TrackerEditor where SYNC: TrackerSync { 8 | pub tracker: Rc>>, 9 | cur_track_idx: usize, 10 | cur_line_idx: usize, 11 | scroll_offs: usize, 12 | redraw_flag: bool, 13 | step_size: usize, 14 | } 15 | 16 | #[derive(Debug, Copy, Clone, PartialEq)] 17 | pub enum TrackerInput { 18 | Delete, 19 | SetNote(u8), 20 | SetValue(f32), 21 | SetA(u8), 22 | SetB(u8), 23 | SetInterpStep, 24 | SetInterpLerp, 25 | SetInterpSStep, 26 | SetInterpExp, 27 | SetStep(usize), 28 | StepDown, 29 | StepUp, 30 | RowDown, 31 | RowUp, 32 | TrackLeft, 33 | TrackRight, 34 | PlayHead(PlayHeadAction), 35 | } 36 | 37 | impl TrackerEditor where SYNC: TrackerSync { 38 | pub fn new(tracker: Rc>>) -> Self { 39 | TrackerEditor { 40 | tracker, 41 | cur_track_idx: 0, 42 | cur_line_idx: 0, 43 | scroll_offs: 0, 44 | redraw_flag: true, 45 | step_size: 1, 46 | } 47 | } 48 | 49 | // fn calc_cursor_scroll(&mut self, max_rows: usize) { 50 | // if self.cur_line_idx >= self.tracker.borrow().lpp { 51 | // self.cur_line_idx = self.tracker.borrow().lpp - 1; 52 | // } 53 | // if self.cur_line_idx < self.scroll_line_offs { 54 | // self.scroll_line_offs = self.cur_line_idx; 55 | // } 56 | // if self.cur_line_idx >= (self.scroll_line_offs + max_rows) { 57 | // self.scroll_line_offs = self.cur_line_idx - (max_rows / 2); 58 | // } 59 | // } 60 | 61 | pub fn need_redraw(&self) -> bool { self.redraw_flag } 62 | 63 | pub fn draw

(&mut self, p: &mut P, play_line: i32) where P: GUIPainter { 64 | let mut gs = GUIState { 65 | cursor_track_idx: self.cur_track_idx, 66 | track_index: 0, 67 | cursor_on_track: false, 68 | cursor_on_line: false, 69 | scroll_offs: self.scroll_offs, 70 | play_on_line: false, 71 | pattern_index: 0, 72 | on_beat: false, 73 | cursor_line: self.cur_line_idx, 74 | lpb: 0, 75 | play_line, 76 | }; 77 | self.tracker.borrow_mut().draw(p, &mut gs); 78 | self.cur_track_idx = gs.cursor_track_idx; 79 | self.cur_line_idx = gs.cursor_line; 80 | self.scroll_offs = gs.scroll_offs; 81 | } 82 | 83 | pub fn process_input(&mut self, input: TrackerInput) { 84 | self.redraw_flag = true; 85 | 86 | match input { 87 | TrackerInput::SetNote(v) => { 88 | self.tracker.borrow_mut() 89 | .set_note( 90 | self.cur_track_idx, 91 | self.cur_line_idx, 92 | v); 93 | }, 94 | TrackerInput::SetA(v) => { 95 | self.tracker.borrow_mut() 96 | .set_a( 97 | self.cur_track_idx, 98 | self.cur_line_idx, 99 | v); 100 | }, 101 | TrackerInput::SetB(v) => { 102 | self.tracker.borrow_mut() 103 | .set_b( 104 | self.cur_track_idx, 105 | self.cur_line_idx, 106 | v); 107 | }, 108 | TrackerInput::SetValue(v) => { 109 | self.tracker.borrow_mut() 110 | .set_value( 111 | self.cur_track_idx, 112 | self.cur_line_idx, 113 | v); 114 | }, 115 | TrackerInput::Delete => { 116 | self.tracker.borrow_mut() 117 | .remove_value( 118 | self.cur_track_idx, 119 | self.cur_line_idx); 120 | }, 121 | TrackerInput::StepDown => { 122 | self.cur_line_idx += self.step_size; 123 | }, 124 | TrackerInput::StepUp => { 125 | if self.cur_line_idx > 0 { 126 | if self.step_size > self.cur_line_idx { 127 | self.cur_line_idx = 0; 128 | } else { 129 | self.cur_line_idx -= self.step_size; 130 | } 131 | } 132 | }, 133 | TrackerInput::RowDown => { 134 | self.cur_line_idx += 1; 135 | }, 136 | TrackerInput::RowUp => { 137 | if self.cur_line_idx > 0 { 138 | self.cur_line_idx -= 1; 139 | } 140 | }, 141 | TrackerInput::TrackLeft => { 142 | if self.cur_track_idx > 0 { 143 | self.cur_track_idx -= 1; 144 | } 145 | }, 146 | TrackerInput::TrackRight => { 147 | self.cur_track_idx += 1; 148 | }, 149 | TrackerInput::PlayHead(a) => { 150 | self.tracker.borrow_mut().play_head(a); 151 | }, 152 | TrackerInput::SetInterpStep => { 153 | self.tracker.borrow_mut() 154 | .set_int( 155 | self.cur_track_idx, 156 | self.cur_line_idx, 157 | Interpolation::Step); 158 | }, 159 | TrackerInput::SetInterpLerp => { 160 | self.tracker.borrow_mut() 161 | .set_int( 162 | self.cur_track_idx, 163 | self.cur_line_idx, 164 | Interpolation::Lerp); 165 | }, 166 | TrackerInput::SetInterpSStep => { 167 | self.tracker.borrow_mut() 168 | .set_int( 169 | self.cur_track_idx, 170 | self.cur_line_idx, 171 | Interpolation::SStep); 172 | }, 173 | TrackerInput::SetInterpExp => { 174 | self.tracker.borrow_mut() 175 | .set_int( 176 | self.cur_track_idx, 177 | self.cur_line_idx, 178 | Interpolation::Exp); 179 | }, 180 | TrackerInput::SetStep(s) => { 181 | self.step_size = s; 182 | }, 183 | }; 184 | 185 | if self.tracker.borrow().tracks.len() == 0 { 186 | return; 187 | } 188 | 189 | if self.cur_track_idx >= self.tracker.borrow().tracks.len() { 190 | self.cur_track_idx = self.tracker.borrow().tracks.len() - 1; 191 | } 192 | 193 | if self.cur_line_idx >= self.tracker.borrow().max_line_count() { 194 | self.cur_line_idx = self.tracker.borrow().max_line_count(); 195 | if self.cur_line_idx > 0 { self.cur_line_idx -= 1; } 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/tracker_thread.rs: -------------------------------------------------------------------------------- 1 | use crate::tracker::*; 2 | use crate::track::{Track, Row, Interpolation, TrackSerialized}; 3 | use crate::vval_opin::vv2opin; 4 | use wave_sickle::new_slaughter; 5 | use crate::audio_dev_thread::start_audio_thread; 6 | use wctr_signal_ops::*; 7 | use crate::scopes::{Scopes, SCOPE_SAMPLES, SCOPE_WIDTH}; 8 | use crate::audio::AudioFrontend; 9 | 10 | pub struct TrackerThreadOutput { 11 | pub pos: i32, 12 | pub song_pos_s: f32, 13 | pub cpu: (f64, f64, f64), 14 | pub audio_scope_samples: Vec>, 15 | pub audio_scope_done: bool, 16 | track_notes: Vec, 17 | events: Vec<(usize, u8, u8)>, 18 | } 19 | 20 | impl TrackerThreadOutput { 21 | pub fn new() -> Self { 22 | TrackerThreadOutput { 23 | pos: 0, 24 | song_pos_s: 0.0, 25 | cpu: (0.0, 0.0, 0.0), 26 | events: Vec::new(), 27 | track_notes: Vec::new(), 28 | audio_scope_samples: Vec::new(), 29 | audio_scope_done: false, 30 | } 31 | } 32 | 33 | pub fn collect_audio_scope_samples(&mut self, sample_rate: usize, bufs: &Vec>) { 34 | if bufs.len() != self.audio_scope_samples.len() { 35 | self.audio_scope_samples.resize(bufs.len(), Vec::new()); 36 | } 37 | 38 | // 2 times freq samples because of stereo signal! 39 | let a4_buf_len = 2 * ((sample_rate as f64 / 440.0).ceil() as usize); 40 | 41 | for (ab, ass) in bufs.iter().zip(self.audio_scope_samples.iter_mut()) { 42 | if a4_buf_len != ass.capacity() { 43 | ass.reserve(a4_buf_len); 44 | } 45 | 46 | let mut ass_len = ass.len(); 47 | 48 | if ass_len >= a4_buf_len { 49 | self.audio_scope_done = true; 50 | return; 51 | } 52 | 53 | let rest = a4_buf_len - ass_len; 54 | let rest = if ab.len() < rest { ab.len() } else { rest }; 55 | if rest > 0 { 56 | ass.extend_from_slice(&ab[0..rest]); 57 | ass_len = ass.len(); 58 | } 59 | 60 | if ass_len >= a4_buf_len { 61 | self.audio_scope_done = true; 62 | return; 63 | } 64 | } 65 | } 66 | } 67 | 68 | impl OutputHandler for TrackerThreadOutput { 69 | fn emit_event(&mut self, track_idx: usize, row: &Row) { 70 | if row.note > 0 { 71 | if track_idx >= self.track_notes.len() { 72 | self.track_notes.resize(track_idx + 1, 0); 73 | } 74 | 75 | if row.note > 1 { 76 | self.events.push( 77 | (track_idx, row.note, row.note)); 78 | } 79 | 80 | if self.track_notes[track_idx] > 0 { 81 | self.events.push((track_idx, 1, self.track_notes[track_idx])); 82 | } 83 | 84 | if row.note == 1 { 85 | self.track_notes[track_idx] = 0; 86 | } else { 87 | self.track_notes[track_idx] = row.note; 88 | } 89 | } 90 | //d// println!("EMIT: {}: {}/{}", track_idx, val, flags); 91 | } 92 | 93 | fn emit_play_line(&mut self, play_line: i32) { 94 | //d// println!("EMIT PLAYLINE OUT {}", play_line); 95 | self.pos = play_line; 96 | } 97 | 98 | fn song_pos(&mut self) -> &mut f32 { return &mut self.song_pos_s; } 99 | } 100 | 101 | fn calc_cpu_percentage(millis: u128, interval_ms: u128) -> f64 { 102 | ((millis * 100000) 103 | / ((interval_ms * 1000) as u128)) as f64 / 1000.0 104 | } 105 | 106 | use wlambda; 107 | use wlambda::{VVal, GlobalEnv, EvalContext, Env}; 108 | 109 | struct AudioThreadWLambdaContext { 110 | pub sim: Simulator, 111 | pub track_values: std::rc::Rc>>, 112 | pub sample_rate: usize, 113 | } 114 | 115 | fn eval_audio_script(mut msgh: wlambda::threads::MsgHandle, ctxref: std::rc::Rc>) { 116 | let genv = GlobalEnv::new_default(); 117 | 118 | genv.borrow_mut().add_func( 119 | "p", |env: &mut Env, _argc: usize| { 120 | println!("{}", env.arg(0).s_raw()); 121 | Ok(VVal::Bol(true)) 122 | }, Some(1), Some(1)); 123 | 124 | genv.borrow_mut().add_func( 125 | "signal_group", |env: &mut Env, _argc: usize| { 126 | let name = env.arg(0).s_raw(); 127 | env.with_user_do(|ctx: &mut AudioThreadWLambdaContext| { 128 | Ok(VVal::Int(ctx.sim.add_group(&name) as i64)) 129 | }) 130 | }, Some(1), Some(1)); 131 | 132 | genv.borrow_mut().add_func( 133 | "input", |env: &mut Env, _argc: usize| { 134 | let op_name = env.arg(0).s_raw(); 135 | let in_name = env.arg(1).s_raw(); 136 | let op_in = vv2opin(env.arg(2).clone()); 137 | if op_in.is_none() { 138 | return Ok(VVal::err_msg( 139 | &format!("bad op description: {}", env.arg(2).s()))); 140 | } 141 | env.with_user_do(|ctx: &mut AudioThreadWLambdaContext| { 142 | let op_idx = ctx.sim.get_op_index(&op_name); 143 | if op_idx.is_none() { 144 | return Ok(VVal::err_msg( 145 | &format!("bad op name: {}", op_name))); 146 | } 147 | ctx.sim.set_op_input(op_idx.unwrap(), &in_name, op_in.unwrap().clone(), true); 148 | ctx.sim.set_op_input(op_idx.unwrap(), &in_name, op_in.unwrap().clone(), false); 149 | Ok(VVal::Bol(true)) 150 | }) 151 | }, Some(3), Some(3)); 152 | 153 | genv.borrow_mut().add_func( 154 | "op", |env: &mut Env, _argc: usize| { 155 | let op_type = env.arg(0).s_raw(); 156 | let op_name = env.arg(1).s_raw(); 157 | let group_index = env.arg(2).i() as usize; 158 | env.with_user_do(|ctx: &mut AudioThreadWLambdaContext| { 159 | let op : Box = 160 | match &op_type[..] { 161 | "sin" => { 162 | let s = ops::Sin::new(); 163 | Box::new(s) 164 | }, 165 | "slaughter" => { 166 | let s = new_slaughter(ctx.sample_rate as f64); 167 | Box::new(s) 168 | }, 169 | "audio_send" => { 170 | let s = ops::AudioSend::new(); 171 | Box::new(s) 172 | }, 173 | _ => { return Ok(VVal::Nul); } 174 | }; 175 | 176 | match ctx.sim.add_op(op, op_name.clone(), group_index) { 177 | Some(i) => Ok(VVal::Int(i as i64)), 178 | None => Ok(VVal::Nul), 179 | } 180 | }) 181 | }, Some(3), Some(3)); 182 | 183 | genv.borrow_mut().add_func( 184 | "track_proxy", |env: &mut Env, _argc: usize| { 185 | let track_count = env.arg(0).i() as usize; 186 | let group_index = env.arg(1).i() as usize; 187 | println!("TR {} , {}", track_count, group_index); 188 | env.with_user_do(|ctx: &mut AudioThreadWLambdaContext| { 189 | let oprox = ops::OutProxy::new(track_count); 190 | ctx.track_values = oprox.values.clone(); 191 | ctx.sim.add_op(Box::new(oprox), String::from("T"), group_index); 192 | Ok(VVal::Bol(true)) 193 | }) 194 | }, Some(2), Some(2)); 195 | 196 | let mut wl_eval_ctx = 197 | wlambda::compiler::EvalContext::new_with_user(genv, ctxref); 198 | 199 | // match wl_eval_ctx.eval_file("tracker.wl") { 200 | // Ok(_) => (), 201 | // Err(e) => { panic!(format!("AUDIO SCRIPT ERROR: {}", e)); } 202 | // } 203 | 204 | println!("RUN"); 205 | msgh.run(&mut wl_eval_ctx); 206 | println!("RUN DONE"); 207 | } 208 | 209 | 210 | pub fn start_tracker_thread( 211 | msgh: wlambda::threads::MsgHandle, 212 | ext_out: std::sync::Arc>, 213 | rcv: std::sync::mpsc::Receiver, 214 | mut ep: SimulatorCommunicatorEndpoint) -> Scopes { 215 | 216 | let sr = Scopes::new(SCOPE_SAMPLES); 217 | let rr = sr.sample_row.clone(); 218 | 219 | let mut audio_f = AudioFrontend::new(); 220 | let audio_dev = audio_f.get_dev(); 221 | start_audio_thread(audio_dev); 222 | 223 | let mut last_iter = std::time::Instant::now(); 224 | 225 | std::thread::spawn(move || { 226 | audio_f.wait_backend_ready(); 227 | 228 | let ctxref = 229 | std::rc::Rc::new(std::cell::RefCell::new(AudioThreadWLambdaContext { 230 | sim: Simulator::new(), 231 | track_values: std::rc::Rc::new(std::cell::RefCell::new(vec![])), 232 | sample_rate: audio_f.get_sample_rate(), 233 | })); 234 | 235 | eval_audio_script(msgh, ctxref.clone()); 236 | 237 | // wlambda API: 238 | // - (audio thread) setup simulator groups 239 | // - (audio thread) setup simulator operators and their default vals 240 | // - (audio thread) setup audio buffers and routings between the audio 241 | // devices. 242 | // - (audio thread) specify which audio devices receive note events 243 | // from the tracks. 244 | // - (frontend thread) add tracks 245 | // - (frontend thread) configure tracker values (needs sync!) 246 | // - (frontend thread) specify project file name 247 | // - (frontend thread) turtle setup 248 | // - (frontend thread) frontend simulator setup (groups, operators, ...) 249 | // (insert backend values via OutProxy) 250 | 251 | let mut ctx = ctxref.borrow_mut(); 252 | 253 | let mut o = TrackerThreadOutput::new(); 254 | let mut t = Tracker::new(TrackerNopSync { }); 255 | 256 | let sample_buf_len = 257 | (((audio_f.get_sample_rate() * t.tick_interval) as f64).ceil() 258 | / 1000.0) 259 | as usize; 260 | 261 | let ticks_per_audio_scope_update = 262 | // 1000ms / 100ms / ms_per_tick => 10 times per second 263 | (100.0 as f64 / (t.tick_interval as f64)).ceil() as usize; 264 | 265 | let mut audio_buffers = ctx.sim.new_group_sample_buffers(sample_buf_len); 266 | 267 | let mut is_playing = true; 268 | let mut out_updated = false; 269 | let mut micros_min : u128 = 9999999; 270 | let mut micros_max : u128 = 0; 271 | let mut micros_sum : u128 = 0; 272 | let mut micros_cnt : u128 = 0; 273 | let mut audio_scope_counter : usize = 0; 274 | loop { 275 | let now = std::time::Instant::now(); 276 | 277 | ep.handle_ui_messages(&mut ctx.sim); 278 | 279 | let r = rcv.try_recv(); 280 | match r { 281 | Ok(TrackerSyncMsg::AddTrack(track)) => { 282 | t.add_track(track.clone()); 283 | println!("THRD: TRACK ADD TRACK"); 284 | }, 285 | Ok(TrackerSyncMsg::SetInt(track_idx, line, int)) => { 286 | t.set_int(track_idx, line, int); 287 | println!("THRD: SET VAL"); 288 | }, 289 | Ok(TrackerSyncMsg::SetValue(track_idx, line, v)) => { 290 | t.set_value(track_idx, line, v); 291 | println!("THRD: SET VAL"); 292 | }, 293 | Ok(TrackerSyncMsg::SetNote(track_idx, line, v)) => { 294 | t.set_note(track_idx, line, v); 295 | println!("THRD: SET NOTE {}", v); 296 | }, 297 | Ok(TrackerSyncMsg::SetA(track_idx, line, v)) => { 298 | t.set_a(track_idx, line, v); 299 | println!("THRD: SET A"); 300 | }, 301 | Ok(TrackerSyncMsg::SetB(track_idx, line, v)) => { 302 | t.set_b(track_idx, line, v); 303 | println!("THRD: SET B"); 304 | }, 305 | Ok(TrackerSyncMsg::RemoveValue(track_idx, line)) => { 306 | t.remove_value(track_idx, line); 307 | println!("THRD: REMO VAL"); 308 | }, 309 | Ok(TrackerSyncMsg::DeserializeContents(track_idx, contents)) => { 310 | t.deserialize_contents(track_idx, contents); 311 | }, 312 | Ok(TrackerSyncMsg::PlayHead(a)) => { 313 | match a { 314 | PlayHeadAction::TogglePause => { 315 | is_playing = !is_playing; 316 | }, 317 | PlayHeadAction::Pause => { is_playing = false; }, 318 | PlayHeadAction::Play => { is_playing = true; }, 319 | PlayHeadAction::NextLine => { 320 | println!("NEXT LINE"); 321 | t.tick_to_next_line(&mut o, &ctx.track_values); 322 | out_updated = true; 323 | is_playing = false; 324 | }, 325 | PlayHeadAction::PrevLine => { 326 | println!("PREV LINE"); 327 | t.tick_to_prev_line(&mut o, &ctx.track_values); 328 | out_updated = true; 329 | is_playing = false; 330 | }, 331 | PlayHeadAction::Restart => { 332 | t.reset_pos(); 333 | is_playing = true; 334 | }, 335 | // _ => (), 336 | } 337 | }, 338 | Err(std::sync::mpsc::TryRecvError::Empty) => (), 339 | Err(std::sync::mpsc::TryRecvError::Disconnected) => return (), 340 | } 341 | 342 | if is_playing { 343 | t.tick(&mut o, &ctx.track_values); 344 | out_updated = true; 345 | //d// println!("THRD: TICK {}", o.pos); 346 | } 347 | 348 | if out_updated { 349 | while !o.events.is_empty() { 350 | let e = o.events.pop().unwrap(); 351 | // TODO: Implement proper mapping of track->group, maybe 352 | // use the A value?! 353 | let ev = 354 | if e.1 == 1 { 355 | signals::Event::NoteOff(e.2) 356 | } else { 357 | signals::Event::NoteOn(e.1) 358 | }; 359 | ctx.sim.event(0, &ev); 360 | ctx.sim.event(1, &ev); 361 | ctx.sim.event(2, &ev); 362 | ctx.sim.event(3, &ev); 363 | ctx.sim.event(4, &ev); 364 | ctx.sim.event(5, &ev); 365 | ctx.sim.event(6, &ev); 366 | } 367 | 368 | ctx.sim.exec(o.song_pos_s, rr.clone()); 369 | } 370 | 371 | if is_playing { 372 | ctx.sim.render(sample_buf_len, 0, &mut audio_buffers); 373 | if audio_scope_counter > ticks_per_audio_scope_update { 374 | o.collect_audio_scope_samples( 375 | audio_f.get_sample_rate(), &audio_buffers); 376 | audio_scope_counter = 0; 377 | } else { 378 | audio_scope_counter += 1; 379 | } 380 | 381 | } else { 382 | ctx.sim.render_silence(sample_buf_len, 0, &mut audio_buffers); 383 | } 384 | 385 | if out_updated { 386 | out_updated = false; 387 | if let Ok(ref mut m) = ext_out.try_lock() { 388 | m.pos = o.pos; 389 | m.song_pos_s = o.song_pos_s; 390 | m.cpu = o.cpu; 391 | if o.audio_scope_done && !m.audio_scope_done { 392 | m.audio_scope_done = o.audio_scope_done; 393 | std::mem::swap( 394 | &mut m.audio_scope_samples, 395 | &mut o.audio_scope_samples); 396 | o.audio_scope_done = false; 397 | } 398 | } 399 | } 400 | 401 | 402 | // std::thread::sleep( 403 | // std::time::Duration::from_micros( 404 | // (((t.tick_interval * 1000) as f64) * 0.1) as u64)); 405 | 406 | let elap = now.elapsed().as_micros(); 407 | 408 | let wait = std::time::Instant::now(); 409 | audio_f.put_samples_blocking(&audio_buffers[0][..]); 410 | 411 | let whole = last_iter.elapsed().as_micros(); 412 | last_iter = std::time::Instant::now(); 413 | 414 | 415 | micros_sum += elap; 416 | micros_cnt += 1; 417 | if micros_min > elap { micros_min = elap; } 418 | if micros_max < elap { micros_max = elap; } 419 | 420 | if micros_cnt > 200 { 421 | println!("i elap={}, min={}, max={}, whole={}, wait={}", elap, micros_min, micros_max, whole, wait.elapsed().as_micros()); 422 | o.cpu = ( 423 | calc_cpu_percentage(micros_sum / micros_cnt, t.tick_interval as u128), 424 | calc_cpu_percentage(micros_min, t.tick_interval as u128), 425 | calc_cpu_percentage(micros_max, t.tick_interval as u128)); 426 | 427 | // println!("audio thread %cpu: min={:<6}, max={:<6}, {:<6} {:<4} | {:<4} / {:6.2}/{:6.2}/{:6.2}", 428 | // micros_min, 429 | // micros_max, 430 | // micros_sum, 431 | // micros_cnt, 432 | // micros_sum / micros_cnt, 433 | // o.cpu.0, 434 | // o.cpu.1, 435 | // o.cpu.2); 436 | 437 | micros_cnt = 0; 438 | micros_sum = 0; 439 | micros_min = 9999999; 440 | micros_max = 0; 441 | } 442 | 443 | // std::thread::sleep( 444 | // std::time::Duration::from_millis( 445 | // t.tick_interval as u64)); 446 | } 447 | }); 448 | 449 | sr 450 | } 451 | 452 | #[derive(Debug, Clone)] 453 | pub enum TrackerSyncMsg { 454 | AddTrack(Track), 455 | SetValue(usize, usize, f32), 456 | SetNote(usize, usize, u8), 457 | SetA(usize, usize, u8), 458 | SetB(usize, usize, u8), 459 | SetInt(usize, usize, Interpolation), 460 | RemoveValue(usize, usize), 461 | PlayHead(PlayHeadAction), 462 | DeserializeContents(usize, TrackSerialized), 463 | } 464 | 465 | pub struct ThreadTrackSync { 466 | send: std::sync::mpsc::Sender, 467 | } 468 | 469 | impl ThreadTrackSync { 470 | pub fn new(send: std::sync::mpsc::Sender) -> Self { 471 | ThreadTrackSync { send } 472 | } 473 | } 474 | 475 | impl TrackerSync for ThreadTrackSync { 476 | fn add_track(&mut self, t: Track) { 477 | self.send.send(TrackerSyncMsg::AddTrack(t)) 478 | .expect("tracker thread communication"); 479 | } 480 | fn set_int(&mut self, track_idx: usize, line: usize, int: Interpolation) { 481 | self.send.send(TrackerSyncMsg::SetInt(track_idx, line, int)) 482 | .expect("tracker thread communication"); 483 | } 484 | fn set_value(&mut self, track_idx: usize, line: usize, value: f32) { 485 | self.send.send(TrackerSyncMsg::SetValue(track_idx, line, value)) 486 | .expect("tracker thread communication"); 487 | } 488 | fn set_note(&mut self, track_idx: usize, line: usize, value: u8) { 489 | self.send.send(TrackerSyncMsg::SetNote(track_idx, line, value)) 490 | .expect("tracker thread communication"); 491 | } 492 | fn set_a(&mut self, track_idx: usize, line: usize, value: u8) { 493 | self.send.send(TrackerSyncMsg::SetA(track_idx, line, value)) 494 | .expect("tracker thread communication"); 495 | } 496 | fn set_b(&mut self, track_idx: usize, line: usize, value: u8) { 497 | self.send.send(TrackerSyncMsg::SetB(track_idx, line, value)) 498 | .expect("tracker thread communication"); 499 | } 500 | fn remove_value(&mut self, track_idx: usize, line: usize) { 501 | self.send.send(TrackerSyncMsg::RemoveValue(track_idx, line)) 502 | .expect("tracker thread communication"); 503 | } 504 | fn play_head(&mut self, act: PlayHeadAction) { 505 | self.send.send(TrackerSyncMsg::PlayHead(act)) 506 | .expect("tracker thread communication"); 507 | } 508 | fn deserialize_contents(&mut self, track_idx: usize, contents: TrackSerialized) { 509 | self.send.send(TrackerSyncMsg::DeserializeContents(track_idx, contents)) 510 | .expect("tracker thread communication"); 511 | } 512 | } 513 | 514 | -------------------------------------------------------------------------------- /src/vval_opin.rs: -------------------------------------------------------------------------------- 1 | use wlambda::vval::VVal; 2 | use wctr_signal_ops::signals::OpIn; 3 | 4 | pub fn vv2opin(v: VVal) -> Option { 5 | let t = if v.is_vec() { 6 | v.at(0).unwrap_or(VVal::Nul) 7 | } else { 8 | v.clone() 9 | }; 10 | 11 | if t.is_sym() || t.is_str() { 12 | let s = t.s_raw(); 13 | match &s[..] { 14 | "reg" => Some(OpIn::Reg( 15 | v.at(1).unwrap_or(VVal::Nul).i() as usize)), 16 | "mix2" => Some(OpIn::RegMix2( 17 | v.at(1).unwrap_or(VVal::Nul).i() as usize, 18 | v.at(2).unwrap_or(VVal::Nul).i() as usize, 19 | v.at(3).unwrap_or(VVal::Nul).f() as f32)), 20 | "add" => Some(OpIn::RegAdd( 21 | v.at(1).unwrap_or(VVal::Nul).i() as usize, 22 | v.at(2).unwrap_or(VVal::Nul).f() as f32)), 23 | "mul" => Some(OpIn::RegMul( 24 | v.at(1).unwrap_or(VVal::Nul).i() as usize, 25 | v.at(2).unwrap_or(VVal::Nul).f() as f32)), 26 | "addmul" => Some(OpIn::RegAddMul( 27 | v.at(1).unwrap_or(VVal::Nul).i() as usize, 28 | v.at(2).unwrap_or(VVal::Nul).f() as f32, 29 | v.at(3).unwrap_or(VVal::Nul).f() as f32)), 30 | "muladd" => Some(OpIn::RegMulAdd( 31 | v.at(1).unwrap_or(VVal::Nul).i() as usize, 32 | v.at(2).unwrap_or(VVal::Nul).f() as f32, 33 | v.at(3).unwrap_or(VVal::Nul).f() as f32)), 34 | "lerp" => Some(OpIn::RegLerp( 35 | v.at(1).unwrap_or(VVal::Nul).i() as usize, 36 | v.at(2).unwrap_or(VVal::Nul).f() as f32, 37 | v.at(3).unwrap_or(VVal::Nul).f() as f32)), 38 | "sstep" => Some(OpIn::RegSStep( 39 | v.at(1).unwrap_or(VVal::Nul).i() as usize, 40 | v.at(2).unwrap_or(VVal::Nul).f() as f32, 41 | v.at(3).unwrap_or(VVal::Nul).f() as f32)), 42 | "map" => Some(OpIn::RegMap( 43 | v.at(1).unwrap_or(VVal::Nul).i() as usize, 44 | v.at(2).unwrap_or(VVal::Nul).f() as f32, 45 | v.at(3).unwrap_or(VVal::Nul).f() as f32, 46 | v.at(4).unwrap_or(VVal::Nul).f() as f32, 47 | v.at(5).unwrap_or(VVal::Nul).f() as f32)), 48 | _ => None 49 | } 50 | } else { 51 | Some(OpIn::Constant(t.f() as f32)) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tracker.wl: -------------------------------------------------------------------------------- 1 | std:displayln "audio thread setting start!"; 2 | 3 | !g_main = audio_call :signal_group "Main"; 4 | audio_call :track_proxy 5 g_main; 5 | 6 | !g_sub = audio_call :signal_group "Sub"; 7 | !os = audio_call :op :sin "Sin1" g_sub; 8 | !os2 = audio_call :op :sin "Sin2" g_sub; 9 | 10 | range 1 100 1 { 11 | !i = _; 12 | audio_call :op :sin (std:str:cat "Sin" i) g_sub; 13 | }; 14 | 15 | !g_inst1 = audio_call :signal_group :Inst1; 16 | audio_call :op :slaughter "Sl1" g_inst1; 17 | audio_call :op :audio_send "AS1" g_inst1; 18 | 19 | !r = $[:addmul, 0, 1.0, 0.1]; 20 | audio_call :input "AS1" :vol_l r; 21 | audio_call :input "AS1" :vol_r r; 22 | 23 | audio_call :thread:quit; 24 | std:displayln "audio thread setting end!"; 25 | --------------------------------------------------------------------------------