├── .gitignore ├── 160x80_demo.h ├── LICENSE ├── README.md ├── as5600_module.ino ├── config.h ├── data ├── esp32_sampler_demo.mid ├── esp32_sampler_demo2.mid └── samples │ ├── pet_bottle.bin │ ├── pet_bottle.wav │ ├── sine_plug.bin │ └── sine_plug.wav ├── display_module.ino ├── doc └── board_info.md ├── esp32_midi_sampler.ino ├── midi_setup.md ├── ml_inline.ino ├── patch_manager.ino ├── sampler_module.ino ├── screens.ino ├── sdcard └── samples │ └── readme.txt ├── sine.ino ├── status_module.ino ├── targets.csv ├── vu_meter_module.ino └── z_config.ino /.gitignore: -------------------------------------------------------------------------------- 1 | # These are some examples of commonly ignored file patterns. 2 | # You should customize this list as applicable to your project. 3 | # Learn more about .gitignore: 4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore 5 | 6 | # Node artifact files 7 | node_modules/ 8 | dist/ 9 | 10 | # Compiled Java class files 11 | *.class 12 | 13 | # Compiled Python bytecode 14 | *.py[cod] 15 | 16 | # Log files 17 | *.log 18 | 19 | # Package files 20 | *.jar 21 | 22 | # Maven 23 | target/ 24 | dist/ 25 | 26 | # JetBrains IDE 27 | .idea/ 28 | 29 | # Unit test reports 30 | TEST*.xml 31 | 32 | # Generated by MacOS 33 | .DS_Store 34 | 35 | # Generated by Windows 36 | Thumbs.db 37 | 38 | # Applications 39 | *.app 40 | *.exe 41 | *.war 42 | 43 | # Large media files 44 | *.mp4 45 | *.tiff 46 | *.avi 47 | *.flv 48 | *.mov 49 | *.wmv 50 | 51 | arduino_build 52 | arduino_cache 53 | bld* 54 | *build 55 | /.cproject 56 | /.project 57 | 58 | *.bat 59 | makefile* 60 | *.mk -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp32_midi_sampler 2 | ESP32 Audio Kit Sampling MIDI Module - A little DIY Arduino based audio/synthesizer project 3 | 4 | - video presentation of the initial state of this project https://youtu.be/7uSobNW7_A4 5 | - little quick start guide to get started with arduino synthesizer / music projects: https://youtu.be/ZNxGCB-d68g 6 | 7 | 8 | > **⚠️ Note:** This project might be outdated and is no longer actively maintained. Development has continued in a new repository, which covers the **ESP32** and other boards as well. Please check out the latest version of the project here: **[ml_synth_sampler_example](https://github.com/marcel-licence/ml_synth_sampler_example)** 9 | 10 | 11 | 12 | The project is written for the ESP32 Audio Kit V2.2 13 | Arduino Settings: 14 | - Board: ESP32 Dev Module 15 | - PSRAM: Enabled 16 | 17 | ESP32 LittleFS Data Upload is required to upload samples into flash. 18 | 19 | Compile information: 20 | - board 'esp32' Version 1.0.6 21 | - core 'esp32' Version 1.0.6 22 | 23 | Following libraries are used: 24 | - FS in Version 1.0 25 | - LittleFS_esp32 in Version 1.0.6 26 | - SD_MMC in Version 1.0 27 | - WiFi in Version 1.0 28 | - AC101 in Version 0.0.1 29 | - Adafruit_NeoPixel in Version 1.7.0 30 | - Wire in Version 1.0.1 31 | - ML_SynthTools (https://github.com/marcel-licence/ML_SynthTools) 32 | 33 | Configuration of the Audio Kit: 34 | - Set DIP 2, 3 to on and all other DIP switches to off (required for SD card access) 35 | 36 | To store samples on the ESP32 you can upload them using the Arduino plugin: 37 | - ESP32 LittleFS Data Upload (src: https://github.com/lorol/arduino-esp32littlefs-plugin) 38 | 39 | A WS2812 8x8 led matrix can be connected to IO12, 3V3, GND (defined by LED_STRIP_PIN in config.h) 40 | ref: https://hackaday.com/2017/01/20/cheating-at-5v-ws2812-control-to-use-a-3-3v-data-line/ 41 | 42 | MIDI should be connected to IO18 (you can modify the define MIDI_RX_PIN in config.h) 43 | 44 | AS5600 can be now used for scratching. To use it, set the define "AS5600_ENABLED" in config.h as active. 45 | The connection is defined below: 46 | - SDA: IO21 47 | - SCL: IO22 48 | Note: MIDI_RX_PIN changed from IO18 to IO19 on the ESP32 Audio Kit. 49 | In that case you can attach a 160x80 display but its connection will change to: 50 | - MOS: IO23 51 | - SCLK: IO18 52 | - CS: IO5 53 | - DC: IO0 54 | - RST -> should be connected to RST / EN of the ESP32 55 | 56 | 57 | A controller mapping can be found in z_config.ino. 58 | --- 59 | Keys can be used with only one IO by a little modification of the ESP32 Audio Kit (ref: https://youtu.be/r0af0DB1R68) 60 | - move R66-70 to R60-R64 (0 Ohm resistors, you can also put a solder bridge) 61 | - place at R55-R59 a 1.8k resistor 62 | Finally the define AUDIO_KIT_BUTTON_ANALOG in config.h is required 63 | 64 | --- 65 | If you have questions or ideas please feel free to use the discussion area! 66 | 67 | --- 68 | External resources: 69 | reverb_module.ino is a ported from stm32 code (src. https://github.com/YetAnotherElectronicsChannel/STM32_DSP_Reverb/blob/master/code/Src/main.c) 70 | A great video explaining the reverb can be found on his channel: https://youtu.be/nRLXNmLmHqM 71 | 72 | Specifications and datasheets: 73 | --- 74 | 75 | ESP32-A1S (AC101 variant): https://www.makerfabs.com/desfile/files/ESP32-A1S%20Product%20Specification.pdf 76 | ESP32-A1S (ES8388 variant): https://github.com/marcel-licence/esp32_midi_sampler/files/7075076/esp32-a1s_v2.3_specification.pdf 77 | AC101 user manual: http://www.x-powers.com/en.php/Info/down/id/96 78 | ES8388 datasheet: http://www.everest-semi.com/pdf/ES8388%20DS.pdf 79 | ES8388 user guide: https://dl.radxa.com/rock2/docs/hw/ds/ES8388%20user%20Guide.pdf 80 | 81 | # Derived projects 82 | If you like please let me know if you want to get your project listed here which is based on the esp32_midi_sampler project. 83 | -------------------------------------------------------------------------------- /as5600_module.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Marcel Licence 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * Dieses Programm ist Freie Software: Sie können es unter den Bedingungen 18 | * der GNU General Public License, wie von der Free Software Foundation, 19 | * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren 20 | * veröffentlichten Version, weiter verteilen und/oder modifizieren. 21 | * 22 | * Dieses Programm wird in der Hoffnung bereitgestellt, dass es nützlich sein wird, jedoch 23 | * OHNE JEDE GEWÄHR,; sogar ohne die implizite 24 | * Gewähr der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. 25 | * Siehe die GNU General Public License für weitere Einzelheiten. 26 | * 27 | * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem 28 | * Programm erhalten haben. Wenn nicht, siehe . 29 | */ 30 | 31 | /** 32 | * @file as5600_module.ino 33 | * @author Marcel Licence 34 | * @date 25.07.2021 35 | * 36 | * @brief Implementation to use the AS5600 for scratching via I2C 37 | * @see https://ams.com/documents/20143/36005/AS5600_DS000365_5-00.pdf 38 | */ 39 | 40 | 41 | #ifdef __CDT_PARSER__ 42 | #include 43 | #endif 44 | 45 | #ifdef AS5600_ENABLED 46 | 47 | //#define AS5600_DEBUG_ENABLED /* use this to print out some debug messages */ 48 | 49 | //#include 50 | 51 | #define AS5600_ADDR 0x36 52 | 53 | int sumCh = 0; 54 | 55 | inline 56 | void AS5600_WriteReg(uint8_t reg, uint8_t value) 57 | { 58 | Wire.beginTransmission(AS5600_ADDR); 59 | Wire.write(reg); 60 | Wire.write(value); 61 | Wire.endTransmission(); 62 | } 63 | 64 | inline 65 | void AS5600_WriteRegU16(uint8_t reg, uint16_t value) 66 | { 67 | Wire.beginTransmission(AS5600_ADDR); 68 | Wire.write(reg); 69 | Wire.write((value >> 8) & 0xFF); 70 | Wire.write(value & 0xFF); 71 | Wire.endTransmission(); 72 | } 73 | 74 | inline 75 | uint8_t AS5600_ReadReg(uint8_t reg) 76 | { 77 | Wire.beginTransmission(AS5600_ADDR); 78 | Wire.write(reg); 79 | Wire.endTransmission(); 80 | 81 | Wire.requestFrom(AS5600_ADDR, 1); 82 | return Wire.read(); 83 | } 84 | 85 | inline 86 | uint16_t AS5600_ReadReg_u16(uint8_t reg) 87 | { 88 | Wire.beginTransmission(AS5600_ADDR); 89 | Wire.write(reg); 90 | Wire.endTransmission(); 91 | 92 | Wire.requestFrom(AS5600_ADDR, 2); 93 | return (((uint16_t)Wire.read()) << 8) + (uint16_t)Wire.read(); 94 | } 95 | 96 | #define REG_CONF 0x07 97 | #define REG_RAW_ANLGE 0x0C 98 | 99 | void AS5600_Setup() 100 | { 101 | #ifdef AS5600_DEBUG_ENABLED 102 | Serial.printf("CONF: 0x%08x\n", AS5600_ReadReg_u16(REG_CONF)); 103 | #endif 104 | AS5600_WriteRegU16(REG_CONF, 0x08); // hyst to 2 LSB to avoid static noise 105 | #ifdef AS5600_DEBUG_ENABLED 106 | Serial.printf("CONF: 0x%08x\n", AS5600_ReadReg_u16(REG_CONF)); 107 | #endif 108 | } 109 | 110 | void AS5600_Loop() 111 | { 112 | static uint16_t lastVal = 0; 113 | uint16_t newVal = AS5600_ReadReg_u16(REG_RAW_ANLGE); 114 | bool valUpdate = false; 115 | 116 | if (newVal > lastVal) 117 | { 118 | if (newVal - lastVal > 0) 119 | { 120 | valUpdate = true; 121 | } 122 | } 123 | else 124 | { 125 | if (lastVal - newVal > 0) 126 | { 127 | valUpdate = true; 128 | } 129 | } 130 | if (valUpdate) 131 | { 132 | int ch = ((int)newVal) - ((int)lastVal); 133 | if (ch > 2048) 134 | { 135 | ch -= 4096; 136 | } 137 | if (ch < -2048) 138 | { 139 | ch += 4096; 140 | } 141 | #ifdef AS5600_DEBUG_ENABLED 142 | Serial.printf("RAW_ANGLE: %d, %d\n", newVal, ch); 143 | #endif 144 | lastVal = newVal; 145 | sumCh += ch; 146 | } 147 | } 148 | 149 | static float lastPitch = 0; 150 | static float pitchOffset = -100.0f; 151 | 152 | float AS5600_GetPitch(uint8_t oversample) 153 | { 154 | float pitch = sumCh; 155 | 156 | sumCh = 0; 157 | pitch *= 0.25f; 158 | pitch /= oversample; 159 | if (pitch > 50) 160 | { 161 | pitch = 50; 162 | } 163 | if (pitch < -50) 164 | { 165 | pitch = -50; 166 | } 167 | 168 | for (int i = 0; i < oversample; i++) 169 | { 170 | lastPitch = pitch * 0.05 + lastPitch * 0.95; 171 | } 172 | return max(lastPitch, pitchOffset); 173 | } 174 | 175 | void AS5600_SetPitchOffset(float offset) 176 | { 177 | pitchOffset = offset; 178 | } 179 | 180 | #endif 181 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Marcel Licence 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * Dieses Programm ist Freie Software: Sie können es unter den Bedingungen 18 | * der GNU General Public License, wie von der Free Software Foundation, 19 | * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren 20 | * veröffentlichten Version, weiter verteilen und/oder modifizieren. 21 | * 22 | * Dieses Programm wird in der Hoffnung bereitgestellt, dass es nützlich sein wird, jedoch 23 | * OHNE JEDE GEWÄHR,; sogar ohne die implizite 24 | * Gewähr der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. 25 | * Siehe die GNU General Public License für weitere Einzelheiten. 26 | * 27 | * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem 28 | * Programm erhalten haben. Wenn nicht, siehe . 29 | */ 30 | 31 | /** 32 | * @file config.h 33 | * @author Marcel Licence 34 | * @date 12.05.2021 35 | * 36 | * @brief This file contains the project configuration 37 | * 38 | * All definitions are visible in the entire project 39 | * 40 | * Put all your project settings here (defines, numbers, etc.) 41 | * configurations which are requiring knowledge of types etc. 42 | * shall be placed in z_config.ino (will be included at the end) 43 | */ 44 | 45 | 46 | #ifndef CONFIG_H_ 47 | #define CONFIG_H_ 48 | 49 | 50 | #ifdef __CDT_PARSER__ 51 | #include 52 | #endif 53 | 54 | 55 | #define SERIAL_BAUDRATE 115200 56 | 57 | //#define NOTE_ON_AFTER_SETUP /* used to get a test tone without MIDI input. Can be deactivated */ 58 | 59 | /* 60 | * you can select one of the pre-defined boards 61 | * look into ML_SynthTools in ml_boards.h for more information 62 | * @see https://github.com/marcel-licence/ML_SynthTools 63 | */ 64 | #define BOARD_ML_V1 /* activate this when using the ML PCB V1 */ 65 | //#define BOARD_ESP32_AUDIO_KIT_AC101 /* activate this when using the ESP32 Audio Kit v2.2 with the AC101 codec */ 66 | //#define BOARD_ESP32_AUDIO_KIT_ES8388 /* activate this when using the ESP32 Audio Kit v2.2 with the ES8388 codec */ 67 | //#define BOARD_ESP32_DOIT /* activate this when using the DOIT ESP32 DEVKIT V1 board */ 68 | 69 | /* can be used to pass line in through audio processing to output */ 70 | //#define AUDIO_PASS_THROUGH 71 | 72 | /* this changes latency but also speed of processing */ 73 | #define SAMPLE_BUFFER_SIZE 64 74 | 75 | /* enable the following to get a vt100 compatible output which can be displayed for example with teraterm pro */ 76 | //#define VT100_ENABLED 77 | 78 | /* following can be activated to output the key function in the status output */ 79 | #define STATUS_SHOW_BUTTON_TEXT 80 | 81 | /* this will force using const velocity for all notes, remove this to get dynamic velocity */ 82 | //#define MIDI_USE_CONST_VELOCITY 83 | 84 | /* this variable defines the max length of the delay and also the memory consumption */ 85 | #define MAX_DELAY (SAMPLE_RATE/4) /* 1/2s -> @ 44100 samples */ 86 | 87 | /* you can receive MIDI messages via serial-USB connection */ 88 | /* 89 | * you could use for example https://projectgus.github.io/hairless-midiserial/ 90 | * to connect your MIDI device via computer to the serial port 91 | */ 92 | #define MIDI_RECV_FROM_SERIAL 93 | 94 | /* 95 | * you can use this to use the sampler without PSRAM 96 | * keep in mind that the there is just a small buffer available to store samples 97 | */ 98 | //#define SAMPLER_USE_HEAP_ONLY (128*1024) 99 | 100 | /* 101 | * activate MIDI via USB (not implemented in this project) 102 | * 103 | * This requires the MAX3421E connected via SPI to the ESP32 104 | * 105 | * @see https://youtu.be/Mt3rT-SVZww 106 | */ 107 | //#define MIDI_VIA_USB_ENABLED 108 | 109 | /* 110 | * use this to display a scope on the oled display 111 | * It shares the I2C with the AS5600 112 | */ 113 | //#define OLED_OSC_DISP_ENABLED 114 | 115 | //#define MIDI_STREAM_PLAYER_ENABLED /* activate this to use the midi stream playback module */ 116 | 117 | /* 118 | * Following define enables the AS5600 processing (for scratching) 119 | * It should be connected to I2C_SDA, I2C_SCL 120 | * It may be defined by the selected board (e.g. ESP32 Audio Kit with ES8388) 121 | * In case it is not defined please make your own defines 122 | * Ensure that only unused pins are used for I2C 123 | */ 124 | //#define AS5600_ENABLED 125 | 126 | 127 | //#define I2C_SDA 18 128 | //#define I2C_SCL 23 129 | 130 | #define I2C_SPEED 1000000 131 | 132 | /* 133 | * include the board configuration 134 | * there you will find the most hardware depending pin settings 135 | */ 136 | #include /* requires the ML_SynthTools library: https://github.com/marcel-licence/ML_SynthTools */ 137 | 138 | #ifdef BOARD_ML_V1 139 | #elif (defined BOARD_ESP32_AUDIO_KIT_AC101) 140 | #elif (defined BOARD_ESP32_AUDIO_KIT_ES8388) 141 | #elif (defined BOARD_ESP32_DOIT) 142 | #else 143 | /* there is room left for other configurations */ 144 | #endif 145 | 146 | /* 147 | * Some additional stuff when using the audio kit 148 | */ 149 | #ifdef ESP32_AUDIO_KIT 150 | 151 | /* 152 | * use the following when you've modified the audio kit like shown in video: https://youtu.be/r0af0DB1R68 153 | * move R66-70 to R60-R64 (0 Ohm resistors, you can also put a solder bridge) 154 | * place at R55-R59 a 1.8k resistor 155 | */ 156 | #define AUDIO_KIT_BUTTON_ANALOG 157 | 158 | 159 | //#define DISPLAY_160x80_ENABLED /* activate this when a 160x80 ST7735 compatible display is connected */ 160 | 161 | 162 | #ifdef DISPLAY_160x80_ENABLED 163 | #define SCREEN_ENABLED 164 | 165 | #ifndef AS5600_ENABLED /* this will conflict with I2C */ 166 | #define TFT_MOSI 0 167 | #define TFT_SCLK 19 168 | #define TFT_CS 23 169 | #define TFT_RST -1 // 5 // Or set to -1 and connect to Arduino RESET pin 170 | #define TFT_DC 21 171 | 172 | #else 173 | #define TFT_MOSI 23 174 | #define TFT_SCLK 18 175 | #define TFT_CS 5 176 | #define MCP_CS 5 // 15 177 | #define TFT_RST -1 // Or set to -1 and connect to Arduino RESET pin 178 | #define TFT_DC 0 //12 is for SD usally, 21 179 | 180 | #endif 181 | 182 | #define TFT_BLK_PIN 14 // do not connect it would cause the Audio Kit to get stuck 183 | #endif 184 | 185 | #endif /* ESP32_AUDIO_KIT */ 186 | 187 | /* 188 | * You can modify the sample rate as you want 189 | */ 190 | #ifdef ESP32_AUDIO_KIT 191 | #define SAMPLE_RATE 44100 192 | #define SAMPLE_SIZE_16BIT 193 | #else 194 | #define SAMPLE_RATE 44100 195 | #define SAMPLE_SIZE_16BIT 196 | #endif 197 | 198 | 199 | #endif /* CONFIG_H_ */ 200 | 201 | -------------------------------------------------------------------------------- /data/esp32_sampler_demo.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcel-licence/esp32_midi_sampler/267fe92e84c039ea1b4222c754a38b5b63201f0a/data/esp32_sampler_demo.mid -------------------------------------------------------------------------------- /data/esp32_sampler_demo2.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcel-licence/esp32_midi_sampler/267fe92e84c039ea1b4222c754a38b5b63201f0a/data/esp32_sampler_demo2.mid -------------------------------------------------------------------------------- /data/samples/pet_bottle.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcel-licence/esp32_midi_sampler/267fe92e84c039ea1b4222c754a38b5b63201f0a/data/samples/pet_bottle.bin -------------------------------------------------------------------------------- /data/samples/pet_bottle.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcel-licence/esp32_midi_sampler/267fe92e84c039ea1b4222c754a38b5b63201f0a/data/samples/pet_bottle.wav -------------------------------------------------------------------------------- /data/samples/sine_plug.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcel-licence/esp32_midi_sampler/267fe92e84c039ea1b4222c754a38b5b63201f0a/data/samples/sine_plug.bin -------------------------------------------------------------------------------- /data/samples/sine_plug.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcel-licence/esp32_midi_sampler/267fe92e84c039ea1b4222c754a38b5b63201f0a/data/samples/sine_plug.wav -------------------------------------------------------------------------------- /display_module.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Marcel Licence 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * Dieses Programm ist Freie Software: Sie können es unter den Bedingungen 18 | * der GNU General Public License, wie von der Free Software Foundation, 19 | * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren 20 | * veröffentlichten Version, weiter verteilen und/oder modifizieren. 21 | * 22 | * Dieses Programm wird in der Hoffnung bereitgestellt, dass es nützlich sein wird, jedoch 23 | * OHNE JEDE GEWÄHR,; sogar ohne die implizite 24 | * Gewähr der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. 25 | * Siehe die GNU General Public License für weitere Einzelheiten. 26 | * 27 | * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem 28 | * Programm erhalten haben. Wenn nicht, siehe . 29 | */ 30 | 31 | /** 32 | * @file display_module.ino 33 | * @author Marcel Licence 34 | * @date 23.06.2021 35 | * 36 | * @brief this file includes some implementation to drive a display (experimental code) 37 | */ 38 | 39 | 40 | #ifdef __CDT_PARSER__ 41 | #include 42 | #endif 43 | 44 | #ifdef DISPLAY_160x80_ENABLED 45 | 46 | #include /* requires library Adafruit-GFX-Library from https://github.com/adafruit/Adafruit-GFX-Library */ 47 | #include /* requires library Adafruit-ST7735-Library from https://github.com/adafruit/Adafruit-ST7735-Library */ 48 | //#include // Hardware-specific library for ST7789 49 | #include 50 | 51 | 52 | #include "160x80_demo.h" 53 | 54 | 55 | #define DW 160 56 | #define DH 80 57 | 58 | static SPIClass DispSpi; 59 | static Adafruit_ST7735 tft = Adafruit_ST7735(&DispSpi, TFT_CS, TFT_DC, TFT_RST); 60 | 61 | struct screen_s 62 | { 63 | const char *head; 64 | void(*button_up)(void); 65 | void(*button_down)(void); 66 | void(*button_left)(void); 67 | void(*button_right)(void); 68 | }; 69 | 70 | struct screen_s main = 71 | { 72 | "Main", 73 | NULL, 74 | NULL, 75 | NULL, 76 | NULL, 77 | }; 78 | 79 | static char display_header[27]; 80 | static char display_fulltext[27]; 81 | 82 | volatile struct screen_s *activeScreen = &main; 83 | volatile struct screen_s *lastScreen = NULL; 84 | static volatile int curSel = -1; 85 | static volatile int curOffset = 0; 86 | 87 | #define PWM_Ch 1 88 | 89 | 90 | void drawRGBBitmapP(int16_t x, int16_t y, PGM_VOID_P bitmap, 91 | int16_t w, int16_t h) 92 | { 93 | uint16_t pixel_data_temp[160]; 94 | for (int i = 0; i < h; i++) 95 | { 96 | memcpy_P(pixel_data_temp, &((struct gimpImg_s *)bitmap)->pixel_data[160 * 2 * i], sizeof(pixel_data_temp)); 97 | tft.drawRGBBitmap(x, y + i, pixel_data_temp, w, 1); 98 | } 99 | } 100 | 101 | 102 | void Display_Setup() 103 | { 104 | display_header[0] = 0; 105 | display_fulltext[0] = 0; 106 | 107 | DispSpi.begin(TFT_SCLK, -1, TFT_MOSI); 108 | 109 | tft.initR(INITR_096_160x80_IPS); // Init ST7735S mini display 110 | //tft.setRotation(3); // set display orientation 111 | tft.setRotation(1); // set display orientation 112 | 113 | #if 1 114 | drawRGBBitmapP(0, 0, &gimp_image_ml, 160, 80); 115 | delay(250); 116 | tft.invertDisplay(true); 117 | delay(250); 118 | tft.invertDisplay(false); 119 | delay(250); 120 | tft.invertDisplay(true); 121 | delay(250); 122 | tft.invertDisplay(false); 123 | delay(250); 124 | #endif 125 | tft.fillScreen(ST77XX_WHITE); 126 | 127 | #if 0 128 | tft.fillScreen(ST77XX_WHITE); 129 | char ttt[] = "1234567890123456789012345#"; // # 26 130 | testdrawtext(ttt, ST77XX_BLACK, ST77XX_WHITE); 131 | tft.drawFastHLine(0, 11, tft.width(), 0); 132 | 133 | for (int i = 0; i < 8; i++) 134 | { 135 | char testStr[] = "###4567890123456789012345#"; 136 | testdrawtextline(testStr, ST77XX_BLACK, ST77XX_WHITE, i); 137 | } 138 | 139 | delay(50); 140 | 141 | tft.fillScreen(ST77XX_BLACK); 142 | char bk[] = "black"; 143 | testdrawtext(bk, 0xFFFF, ST77XX_BLACK); 144 | delay(50); 145 | 146 | tft.fillScreen(0xF800); 147 | char red[] = "red"; 148 | testdrawtext(red, 0xFFFF, 0xF800); 149 | delay(50); 150 | 151 | tft.fillScreen(0x07C0); 152 | char gr[] = "green"; 153 | testdrawtext(gr, 0xFFFF, 0x07C0); 154 | delay(50); 155 | 156 | tft.fillScreen(0x003F); 157 | char bl[] = "blue"; 158 | testdrawtext(bl, 0xFFFF, 0x003F); 159 | delay(50); 160 | 161 | tft.fillScreen(ST77XX_WHITE); 162 | char wr[] = "white"; 163 | testdrawtext(wr, ST77XX_BLACK, ST77XX_WHITE); 164 | delay(50); 165 | #endif 166 | 167 | #ifdef TFT_BLK_PIN 168 | ledcAttachPin(TFT_BLK_PIN, PWM_Ch); 169 | ledcSetup(PWM_Ch, 800, 8); 170 | #endif 171 | } 172 | 173 | char display_line[8][27]; 174 | 175 | static bool redraw = false; 176 | 177 | void Display_Draw() 178 | { 179 | #if 0 180 | if (activeScreen != lastScreen) 181 | { 182 | //for (int n = 0; n<7; n++) 183 | { 184 | // curSel = n; 185 | 186 | 187 | Serial.printf("redraw %d\n", curSel); 188 | lastScreen = activeScreen; 189 | //tft.fillScreen(ST77XX_WHITE); 190 | if (display_header[0] == 0) 191 | { 192 | testdrawtext(activeScreen->head, ST77XX_BLACK, ST77XX_WHITE); 193 | } 194 | else 195 | { 196 | testdrawtext(display_header, ST77XX_BLACK, ST77XX_WHITE); 197 | } 198 | tft.drawFastHLine(0, 11, tft.width(), 0); 199 | 200 | for (int i = 0; i < 8; i++) 201 | { 202 | uint16_t bg = ST77XX_WHITE; 203 | if (i == curSel) 204 | { 205 | //tft.fillRect(0, 14 + (8 * i), tft.width(), 8, ST77XX_GREEN); 206 | bg = ST77XX_GREEN; 207 | } 208 | //char longLine[64]; 209 | sprintf(display_line[i], "#%d#45678901234A67890123#%d#", i, curSel + 1); 210 | testdrawtextline(display_line[i], ST77XX_BLACK, bg, i); 211 | } 212 | //delay(250); 213 | } 214 | 215 | } 216 | #endif 217 | if (redraw) 218 | { 219 | if (display_fulltext[0] != 0) 220 | { 221 | tft.setTextSize(3); 222 | testdrawtext(display_fulltext, ST77XX_BLACK, ST77XX_WHITE); 223 | } 224 | else 225 | { 226 | { 227 | uint16_t bg = ST77XX_WHITE; 228 | if (curSel == -1) 229 | { 230 | bg = ST77XX_GREEN; 231 | } 232 | testdrawtext(display_header, ST77XX_BLACK, bg); 233 | } 234 | tft.drawFastHLine(0, 11, tft.width(), 0); 235 | 236 | for (int i = 0; i < 8; i++) 237 | { 238 | uint16_t bg = ST77XX_WHITE; 239 | if (i == curSel) 240 | { 241 | bg = ST77XX_GREEN; 242 | } 243 | testdrawtextline(display_line[i], ST77XX_BLACK, bg, i); 244 | } 245 | } 246 | redraw = false; 247 | } 248 | } 249 | 250 | bool Display_Busy() 251 | { 252 | return redraw; 253 | } 254 | 255 | void Display_Redraw() 256 | { 257 | redraw = true; 258 | } 259 | 260 | void Display_SetLine(int i, char *text) 261 | { 262 | memset(display_line[i], ' ', 26); 263 | memcpy(display_line[i], text, strlen(text)); 264 | } 265 | 266 | #define min(a,b) ((a>b)?(b):(a)) 267 | 268 | void Display_SetHeader(const char *headerText) 269 | { 270 | memset(display_header, ' ', 26); 271 | memcpy(display_header, headerText, min(26, strlen(headerText))); 272 | //lastScreen = NULL; 273 | } 274 | 275 | void Display_SetFullText(const char *headerText) 276 | { 277 | memset(display_fulltext, ' ', 26); 278 | memcpy(display_fulltext, headerText, min(26, strlen(headerText))); 279 | //lastScreen = NULL; 280 | } 281 | 282 | void Display_SetCurSel(int sel) 283 | { 284 | curSel = sel; 285 | } 286 | 287 | void Display_SetBacklight(uint8_t DutyCycle) 288 | { 289 | #ifdef TFT_BLK_PIN 290 | ledcWrite(PWM_Ch, DutyCycle); 291 | #else 292 | Serial.printf("Cannot change backlight, TFT_BLK_PIN not set!\n"); 293 | #endif 294 | } 295 | 296 | void testdrawtext(const char *text, uint16_t color, uint16_t bg) 297 | { 298 | tft.setCursor(2, 3); 299 | tft.setTextColor(color, bg); 300 | tft.setTextWrap(true); 301 | 302 | char textLine[] = " "; 303 | memcpy(textLine, text, strlen(text)); 304 | 305 | tft.print(textLine); 306 | } 307 | 308 | void testdrawtextline(char *text, uint16_t color, uint16_t bg, uint8_t line) 309 | { 310 | tft.setCursor(2, 14 + 8 * line); 311 | tft.setTextColor(color, bg); 312 | tft.setTextWrap(true); 313 | 314 | char textLine[] = " "; 315 | memcpy(textLine, text, strlen(text)); 316 | 317 | tft.print(textLine); 318 | } 319 | 320 | #endif 321 | -------------------------------------------------------------------------------- /doc/board_info.md: -------------------------------------------------------------------------------- 1 |

Board Build Variants

2 | 3 | Below you will find a list of build which can be configured and should compile without any problems 4 | 5 |
6 | Core: ESP32 Arduino
7 | Version: 2.0.13
8 | Board: ESP32 Dev Module
9 |
10 | Program storage space: 904449 bytes
11 | Dynamic memory: 117876 bytes
12 |
13 | JTAG Adapter: Disabled
14 | PSRAM: Disabled
15 | Partition Scheme: Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS)
16 | CPU Frequency: 240MHz (WiFi/BT)
17 | Flash Mode: QIO
18 | Flash Frequency: 80MHz
19 | Flash Size: 4MB (32Mb)
20 | Upload Speed: 921600
21 | Arduino Runs On: Core 1
22 | Events Run On: Core 1
23 | Core Debug Level: None
24 | Erase All Flash Before Sketch Upload: Disabled
25 |
26 | Used libraries:
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |
NameVersionUrlGitCore library
ML SynthTools1.3.1https://github.com/marcel-licence/ML_SynthToolshttps://github.com/marcel-licence/ML_SynthTools.gitFalse
WiFi2.0.0https://github.com/espressif/arduino-esp32.gitTrue
Wire2.0.0http://arduino.cc/en/Reference/Wirehttps://github.com/espressif/arduino-esp32.gitTrue
FS2.0.0https://github.com/espressif/arduino-esp32.gitTrue
LittleFS2.0.0https://github.com/espressif/arduino-esp32.gitTrue
SD_MMC2.0.0https://github.com/espressif/arduino-esp32.gitTrue
Adafruit GFX Library1.11.5https://github.com/adafruit/Adafruit-GFX-Libraryhttps://github.com/adafruit/Adafruit-GFX-LibraryFalse
Adafruit BusIO1.14.1https://github.com/adafruit/Adafruit_BusIOhttps://github.com/adafruit/Adafruit_BusIOFalse
SPI2.0.0http://arduino.cc/en/Reference/SPIhttps://github.com/espressif/arduino-esp32.gitTrue
Adafruit SSD13062.5.7https://github.com/adafruit/Adafruit_SSD1306https://github.com/adafruit/Adafruit_SSD1306False
Adafruit NeoPixel1.11.0https://github.com/adafruit/Adafruit_NeoPixelhttps://github.com/adafruit/Adafruit_NeoPixelFalse
-------------------------------------------------------------------------------- /esp32_midi_sampler.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Marcel Licence 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * Dieses Programm ist Freie Software: Sie können es unter den Bedingungen 18 | * der GNU General Public License, wie von der Free Software Foundation, 19 | * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren 20 | * veröffentlichten Version, weiter verteilen und/oder modifizieren. 21 | * 22 | * Dieses Programm wird in der Hoffnung bereitgestellt, dass es nützlich sein wird, jedoch 23 | * OHNE JEDE GEWÄHR,; sogar ohne die implizite 24 | * Gewähr der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. 25 | * Siehe die GNU General Public License für weitere Einzelheiten. 26 | * 27 | * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem 28 | * Programm erhalten haben. Wenn nicht, siehe . 29 | */ 30 | 31 | /* 32 | * this file should be opened with arduino, this is the main project file 33 | * 34 | * shown in: https://youtu.be/7uSobNW7_A4 35 | * 36 | * Some features listet here: 37 | * - loading/saving from/to sd 38 | * - add patch parameters saving 39 | * - buffered audio processing -> higher polyphony 40 | * - sd or littleFs usage (selectable) 41 | * - global pitchbend and modulation 42 | * - vu meter (using neopixel lib) 43 | * - adsr vca control 44 | * - file system selection -> littleFs support 45 | * - master reverb effect 46 | * - precise pitching of samples (just using simple float didn't work well) 47 | * - sound removal support -> defrag 48 | * - normalize record 49 | * - patch selection 50 | * - save with automatic increment of number in filename 51 | * - display / vt100 terminal support 52 | * - support of ST7735 160x80 compatible display -> update of Adafruit-ST7735-Library required 53 | * - allows using the AS5600 for scratching 54 | * 55 | * Author: Marcel Licence 56 | */ 57 | 58 | #ifdef __CDT_PARSER__ 59 | #include 60 | #endif 61 | 62 | /* 63 | * 64 | /$$$$$$$$ /$$ /$$ /$$$$$$$ /$$$$$$ /$$$$$$$ /$$$$$$ /$$ /$$ /$$ /$$ 65 | | $$_____/ | $$ | $$ | $$__ $$ /$$__ $$| $$__ $$ /$$__ $$| $$$ /$$$| $$| $$ 66 | | $$ /$$$$$$$ /$$$$$$ | $$$$$$$ | $$ /$$$$$$ | $$ \ $$| $$ \__/| $$ \ $$| $$ \ $$| $$$$ /$$$$| $$| $$ 67 | | $$$$$ | $$__ $$ |____ $$| $$__ $$| $$ /$$__ $$ | $$$$$$$/| $$$$$$ | $$$$$$$/| $$$$$$$$| $$ $$/$$ $$| $$| $$ 68 | | $$__/ | $$ \ $$ /$$$$$$$| $$ \ $$| $$| $$$$$$$$ | $$____/ \____ $$| $$__ $$| $$__ $$| $$ $$$| $$|__/|__/ 69 | | $$ | $$ | $$ /$$__ $$| $$ | $$| $$| $$_____/ | $$ /$$ \ $$| $$ \ $$| $$ | $$| $$\ $ | $$ 70 | | $$$$$$$$| $$ | $$| $$$$$$$| $$$$$$$/| $$| $$$$$$$ | $$ | $$$$$$/| $$ | $$| $$ | $$| $$ \/ | $$ /$$ /$$ 71 | |________/|__/ |__/ \_______/|_______/ |__/ \_______/ |__/ \______/ |__/ |__/|__/ |__/|__/ |__/|__/|__/ 72 | 73 | 74 | */ 75 | 76 | /* 77 | * todos: 78 | * - better sound morphing - in progress 79 | * - fix of auto gain - improved 80 | * - add tremolo 81 | * - modulation after time 82 | * - harmonics? 83 | * - ignore other tags in wav files 84 | * 85 | * done: 86 | * - add wav saving to sd 87 | * - add wav loading from sd 88 | * - add patch parameter saving 89 | * - buffered audio processing poly from 6 to 14 90 | * - sd or littleFs usage 91 | * - pitchbend and modulation 92 | * - vu meter neopixel 93 | * - adsr control 94 | * - file system selection -> littleFs support 95 | * - add reverb effect 96 | * - increase of precision required of sample pitch 97 | * - sound removal support -> defrag 98 | * - normalize record 99 | * - patch selection 100 | * - save with automatic increment 101 | * - display 102 | * - simple scratching 103 | */ 104 | 105 | #include "config.h" 106 | 107 | #include 108 | #include 109 | 110 | #ifdef AS5600_ENABLED 111 | #include 112 | #endif 113 | 114 | /* requires the ML_SynthTools library: https://github.com/marcel-licence/ML_SynthTools */ 115 | #include 116 | #include 117 | #include 118 | #include 119 | #ifdef OLED_OSC_DISP_ENABLED 120 | #include 121 | #endif 122 | 123 | #include 124 | 125 | #define ML_SYNTH_INLINE_DECLARATION 126 | #include 127 | #undef ML_SYNTH_INLINE_DECLARATION 128 | 129 | 130 | /* 131 | * use this to activate auto loading 132 | */ 133 | #define AUTO_LOAD_PATCHES_FROM_LITTLEFS 134 | //#define AUTO_LOAD_PATCHES_FROM_SD_MMC 135 | 136 | /* 137 | * you can activate this but be careful 138 | * this will pass trough the mic signal to output during record 139 | * this may cause heavy feedbacks! 140 | */ 141 | //#define SAMPLER_PASS_TROUGH_DURING_RECORD 142 | 143 | /* 144 | * Chip is ESP32D0WDQ5 (revision 1) 145 | * Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None 146 | 147 | * ref.: https://www.makerfabs.com/desfile/files/ESP32-A1S%20Product%20Specification.pdf 148 | 149 | * Board: ESP32 Dev Module 150 | * Flash Size: 32Mbit -> 4MB 151 | * RAM internal: 520KB SRAM 152 | * PSRAM: 4M (set to enabled!!!) 153 | * 154 | */ 155 | 156 | 157 | /* to avoid the high click when turning on the microphone */ 158 | static float click_supp_gain = 0.0f; 159 | 160 | float *vuInL; 161 | float *vuInR; 162 | float *vuOutL; 163 | float *vuOutR; 164 | 165 | extern uint32_t sampleRecordCount; 166 | 167 | /* this application starts here */ 168 | void setup() 169 | { 170 | // put your setup code here, to run once: 171 | delay(500); 172 | 173 | Serial.begin(SERIAL_BAUDRATE); 174 | 175 | Serial.println(); 176 | 177 | 178 | #ifdef AS5600_ENABLED 179 | // digitalWrite(TFT_CS, HIGH); 180 | // pinMode(TFT_CS, OUTPUT); 181 | 182 | Wire.setClock(I2C_SPEED); 183 | #endif 184 | 185 | click_supp_gain = 0.0f; 186 | 187 | #ifdef BLINK_LED_PIN 188 | Blink_Setup(); 189 | #endif 190 | Status_Setup(); 191 | 192 | Audio_Setup(); 193 | 194 | #ifdef AS5600_ENABLED 195 | Wire.begin(I2C_SDA, I2C_SCL); 196 | Wire.setClock(I2C_SPEED); 197 | AS5600_Setup(); 198 | #endif 199 | 200 | #ifdef ESP32_AUDIO_KIT 201 | button_setup(); 202 | #endif 203 | 204 | Sine_Init(); 205 | 206 | /* 207 | * setup midi module / rx port 208 | */ 209 | Midi_Setup(); 210 | 211 | #ifdef ARP_MODULE_ENABLED 212 | Arp_Init(24 * 4); /* slowest tempo one step per bar */ 213 | #endif 214 | 215 | { 216 | static int16_t *storage = NULL; 217 | uint32_t storageLen = 0; 218 | 219 | #ifndef SAMPLER_USE_HEAP_ONLY 220 | psramInit(); 221 | Serial.printf("Total PSRAM: %d\n", ESP.getPsramSize()); 222 | Serial.printf("Free PSRAM: %d\n", ESP.getFreePsram()); 223 | 224 | if (ESP.getPsramSize() == 0) 225 | { 226 | Serial.printf("PSRAM is not available! Please ensure PSRAM is enabled and also available on your ESP32"); 227 | while (true) 228 | { 229 | delay(1); 230 | } 231 | } 232 | 233 | if (ESP.getFreePsram() == 0) 234 | { 235 | Serial.printf("PSRAM has no free Memory! Please ensure PSRAM is enabled and also available on your ESP32"); 236 | while (true) 237 | { 238 | delay(1); 239 | } 240 | } 241 | 242 | storageLen = ESP.getFreePsram() / sizeof(int16_t); 243 | uint32_t storageBytes = storageLen * sizeof(int16_t); 244 | 245 | Serial.printf("Try to allocate %d bytes\n", storageBytes); 246 | 247 | storage = (int16_t *)ps_calloc(storageLen, sizeof(int16_t)); 248 | if (storage == NULL) 249 | { 250 | Serial.printf("Not able to allocate the complete PSRAM buffer!\nNow trying to reduce the allocation buffer size"); 251 | 252 | /* 253 | * for some reason using a newer ESP32 board library you cannot allocate the complete PSRAM memory 254 | * this loop will decrease the buffer size and repeat the allocation step again until it is successful 255 | */ 256 | while ((storage == NULL)) 257 | { 258 | storageLen -= 1; 259 | storageBytes = storageLen * sizeof(int16_t); 260 | storage = (int16_t *)ps_calloc(storageLen, sizeof(int16_t)); 261 | } 262 | } 263 | 264 | Serial.printf("Total PSRAM: %d\n", ESP.getPsramSize()); 265 | Serial.printf("Free PSRAM: %d\n", ESP.getFreePsram()); 266 | #else 267 | storageLen = SAMPLER_USE_HEAP_ONLY; 268 | uint32_t storageBytes = storageLen * sizeof(int16_t); 269 | 270 | Serial.printf("Try to allocate %d bytes from heap\n", storageBytes); 271 | 272 | storage = (int16_t *)calloc(storageLen, sizeof(int16_t)); 273 | #endif 274 | 275 | if (storage == NULL) 276 | { 277 | Serial.printf("Memory couldn't be allocated as sample storage!\n"); 278 | 279 | #ifdef ESP32 280 | Serial.printf("ESP.getFreeHeap() %d\n", ESP.getFreeHeap()); 281 | Serial.printf("ESP.getMinFreeHeap() %d\n", ESP.getMinFreeHeap()); 282 | Serial.printf("ESP.getHeapSize() %d\n", ESP.getHeapSize()); 283 | Serial.printf("ESP.getMaxAllocHeap() %d\n", ESP.getMaxAllocHeap()); 284 | 285 | Serial.printf("Total heap: %d\n", ESP.getHeapSize()); 286 | Serial.printf("Free heap: %d\n", ESP.getFreeHeap()); 287 | 288 | /* PSRAM will be fully used by the looper */ 289 | Serial.printf("Total PSRAM: %d\n", ESP.getPsramSize()); 290 | Serial.printf("Free PSRAM: %d\n", ESP.getFreePsram()); 291 | #endif 292 | 293 | while (true) 294 | { 295 | delay(1); 296 | } 297 | } 298 | 299 | Serial.printf("Allocated %d bytes\n", storageBytes); 300 | Serial.printf("storageLen: %d\n", storageLen); 301 | Serial.printf("storageLen: %0.2fs\n", ((float)storageLen) / ((float)SAMPLE_RATE)); 302 | 303 | Sampler_Init(storage, storageLen); 304 | } 305 | 306 | /* 307 | * Initialize reverb 308 | * The buffer shall be static to ensure that 309 | * the memory will be exclusive available for the reverb module 310 | */ 311 | static float revBuffer[REV_BUFF_SIZE]; 312 | Reverb_Setup(revBuffer); 313 | 314 | #ifdef MAX_DELAY 315 | /* 316 | * Prepare a buffer which can be used for the delay 317 | */ 318 | static int16_t *delBuffer1 = (int16_t *)malloc(sizeof(int16_t) * MAX_DELAY); 319 | static int16_t *delBuffer2 = (int16_t *)malloc(sizeof(int16_t) * MAX_DELAY); 320 | Delay_Init2(delBuffer1, delBuffer2, MAX_DELAY); 321 | if ((delBuffer1 == NULL) || (delBuffer2 == NULL)) 322 | { 323 | /* 324 | * we will end up here if the requested buffer is too big 325 | * the malloc memory is limited and you should keep it small as possible 326 | */ 327 | Serial.printf("Memory for delay couldn't be initialized!\n"); 328 | while (true) 329 | { 330 | delay(1); 331 | } 332 | } 333 | Serial.printf("delay samples: %d\n", MAX_DELAY); 334 | Serial.printf("Max delay time: %0.2fs\n", ((float)MAX_DELAY) / ((float)SAMPLE_RATE)); 335 | #endif 336 | 337 | #ifdef ESP32 338 | Serial.printf("ESP.getFreeHeap() %d\n", ESP.getFreeHeap()); 339 | Serial.printf("ESP.getMinFreeHeap() %d\n", ESP.getMinFreeHeap()); 340 | Serial.printf("ESP.getHeapSize() %d\n", ESP.getHeapSize()); 341 | Serial.printf("ESP.getMaxAllocHeap() %d\n", ESP.getMaxAllocHeap()); 342 | 343 | Serial.printf("Total heap: %d\n", ESP.getHeapSize()); 344 | Serial.printf("Free heap: %d\n", ESP.getFreeHeap()); 345 | 346 | /* PSRAM will be fully used by the looper */ 347 | Serial.printf("Total PSRAM: %d\n", ESP.getPsramSize()); 348 | Serial.printf("Free PSRAM: %d\n", ESP.getFreePsram()); 349 | #endif 350 | 351 | vuInL = VuMeterMatrix_GetPtr(0); 352 | vuInR = VuMeterMatrix_GetPtr(1); 353 | vuOutL = VuMeterMatrix_GetPtr(6); 354 | vuOutR = VuMeterMatrix_GetPtr(7); 355 | 356 | #ifdef AUTO_LOAD_PATCHES_FROM_LITTLEFS 357 | /* finally we can preload some data if available */ 358 | PatchManager_SetDestination(0, 1); 359 | Sampler_LoadPatchFile("/samples/pet_bottle.wav"); 360 | Sampler_LoadPatchFile("/samples/sine_plug.wav"); 361 | #endif 362 | 363 | /* select sd card */ 364 | #ifdef AUTO_LOAD_PATCHES_FROM_SD_MMC 365 | PatchManager_SetDestination(1, 1); 366 | Sampler_LoadPatchFile("/samples/PlateDeep.wav"); 367 | Sampler_LoadPatchFile("/samples/76_Pure.wav"); 368 | #endif 369 | 370 | Serial.printf("Firmware started successfully\n"); 371 | 372 | /* use this to easily test the output */ 373 | #ifdef NOTE_ON_AFTER_SETUP 374 | Sampler_NoteOn(0, 64, 1); 375 | #endif 376 | 377 | #ifdef MIDI_STREAM_PLAYER_ENABLED 378 | MidiStreamPlayer_Init(); 379 | //char midiFile[] = "/esp32_sampler_demo.mid"; 380 | char midiFile[] = "/esp32_sampler_demo2.mid"; 381 | MidiStreamPlayer_PlayMidiFile_fromLittleFS(midiFile, 2); 382 | #endif 383 | 384 | #ifdef AS5600_ENABLED 385 | /* starts first loaded sample and activates this for scratching */ 386 | Sampler_SetScratchSample(0, 1); 387 | #endif 388 | 389 | #ifdef ESP32 390 | Core0TaskInit(); 391 | #else 392 | #error only supported by ESP32 platform 393 | #endif 394 | } 395 | 396 | #ifdef ESP32 397 | /* 398 | * Core 0 399 | */ 400 | /* this is used to add a task to core 0 */ 401 | TaskHandle_t Core0TaskHnd; 402 | 403 | inline 404 | void Core0TaskInit() 405 | { 406 | /* we need a second task for the terminal output */ 407 | xTaskCreatePinnedToCore(Core0Task, "CoreTask0", 8000, NULL, 999, &Core0TaskHnd, 0); 408 | } 409 | 410 | inline 411 | void Core0TaskSetup() 412 | { 413 | /* 414 | * init your stuff for core0 here 415 | */ 416 | 417 | #ifdef OLED_OSC_DISP_ENABLED 418 | ScopeOled_Setup(); 419 | #endif 420 | 421 | #ifdef DISPLAY_160x80_ENABLED 422 | Display_Setup(); 423 | #ifdef SCREEN_ENABLED 424 | Screen_Setup(); 425 | #endif 426 | App_SetBrightness(0, 0.25); 427 | #endif 428 | 429 | VuMeterMatrix_Init(); 430 | } 431 | 432 | inline 433 | void Core0TaskLoop() 434 | { 435 | Status_Process(); 436 | #ifndef AS5600_ENABLED /* does not work together */ 437 | VuMeterMatrix_Display(); 438 | #endif 439 | 440 | #ifdef SCREEN_ENABLED 441 | Screen_Loop(); 442 | #endif 443 | #ifdef DISPLAY_160x80_ENABLED 444 | Display_Draw(); 445 | #endif 446 | 447 | #ifdef OLED_OSC_DISP_ENABLED 448 | ScopeOled_Process(); 449 | #endif 450 | } 451 | 452 | void Core0Task(void *parameter) 453 | { 454 | Core0TaskSetup(); 455 | 456 | while (true) 457 | { 458 | Core0TaskLoop(); 459 | 460 | /* this seems necessary to trigger the watchdog */ 461 | delay(1); 462 | yield(); 463 | } 464 | } 465 | #endif /* ESP32 */ 466 | 467 | static uint32_t midiSyncCount = 0; 468 | 469 | void Midi_SyncRecvd() 470 | { 471 | midiSyncCount += 1; 472 | } 473 | 474 | void Synth_RealTimeMsg(uint8_t msg) 475 | { 476 | #ifndef MIDI_SYNC_MASTER 477 | switch (msg) 478 | { 479 | case 0xfa: /* start */ 480 | Arp_Reset(); 481 | break; 482 | case 0xf8: /* Timing Clock */ 483 | Midi_SyncRecvd(); 484 | break; 485 | } 486 | #endif 487 | } 488 | 489 | #ifdef MIDI_SYNC_MASTER 490 | 491 | #define MIDI_PPQ 24 492 | #define SAMPLES_PER_MIN (SAMPLE_RATE*60) 493 | 494 | static float midi_tempo = 120.0f; 495 | 496 | void MidiSyncMasterLoop(void) 497 | { 498 | static float midiDiv = 0; 499 | midiDiv += SAMPLE_BUFFER_SIZE; 500 | if (midiDiv >= (SAMPLES_PER_MIN) / (MIDI_PPQ * midi_tempo)) 501 | { 502 | midiDiv -= (SAMPLES_PER_MIN) / (MIDI_PPQ * midi_tempo); 503 | Midi_SyncRecvd(); 504 | } 505 | } 506 | 507 | void Synth_SetMidiMasterTempo(uint8_t unused, float val) 508 | { 509 | midi_tempo = 60.0f + val * (240.0f - 60.0f); 510 | } 511 | 512 | #endif 513 | 514 | void Synth_SongPosition(uint16_t pos) 515 | { 516 | Serial.printf("Songpos: %d\n", pos); 517 | if (pos == 0) 518 | { 519 | Arp_Reset(); 520 | } 521 | } 522 | 523 | void Synth_SongPosReset(uint8_t unused, float var) 524 | { 525 | if (var > 0) 526 | { 527 | Synth_SongPosition(0); 528 | } 529 | } 530 | 531 | float master_output_gain = 1.0f; 532 | 533 | /* little enum to make switching more clear */ 534 | enum acSource 535 | { 536 | acSrcLine, 537 | acSrcMic 538 | }; 539 | 540 | /* line in is used by default, so it should not be changed here */ 541 | enum acSource selSource = acSrcLine; 542 | 543 | /* be carefull when calling this function, microphones can cause very bad feedback!!! */ 544 | void App_ToggleSource(uint8_t channel, float value) 545 | { 546 | if (value > 0) 547 | { 548 | switch (selSource) 549 | { 550 | case acSrcLine: 551 | click_supp_gain = 0.0f; 552 | #ifdef AC101_ENABLED 553 | ac101_setSourceMic(); 554 | #endif 555 | selSource = acSrcMic; 556 | Status_TestMsg("Input: Microphone"); 557 | break; 558 | case acSrcMic: 559 | click_supp_gain = 0.0f; 560 | #ifdef AC101_ENABLED 561 | ac101_setSourceLine(); 562 | #endif 563 | selSource = acSrcLine; 564 | Status_TestMsg("Input: LineIn"); 565 | break; 566 | } 567 | } 568 | } 569 | 570 | void App_SetOutputLevel(uint8_t not_used, float value) 571 | { 572 | master_output_gain = value; 573 | } 574 | 575 | /* 576 | * this should avoid having a constant offset on our signal 577 | * I am not sure if that is required, but in case it can avoid early clipping 578 | */ 579 | static float fl_offset = 0.0f; 580 | static float fr_offset = 0.0f; 581 | 582 | 583 | static float fl_sample[SAMPLE_BUFFER_SIZE]; 584 | static float fr_sample[SAMPLE_BUFFER_SIZE]; 585 | 586 | #ifndef absf 587 | #define absf(a) ((a>=0.0f)?(a):(-a)) 588 | #endif 589 | 590 | /* 591 | * the main audio task 592 | */ 593 | inline void audio_task() 594 | { 595 | memset(fl_sample, 0, sizeof(fl_sample)); 596 | memset(fr_sample, 0, sizeof(fr_sample)); 597 | 598 | Audio_Input(fl_sample, fr_sample); 599 | 600 | for (int n = 0; n < SAMPLE_BUFFER_SIZE; n++) 601 | { 602 | /* 603 | * this avoids the high peak coming over the mic input when switching to it 604 | */ 605 | fl_sample[n] *= click_supp_gain; 606 | fr_sample[n] *= click_supp_gain; 607 | 608 | if (click_supp_gain < 1.0f) 609 | { 610 | click_supp_gain += 0.00001f; 611 | } 612 | else 613 | { 614 | click_supp_gain = 1.0f; 615 | } 616 | 617 | /* make it a bit quieter */ 618 | fl_sample[n] *= 0.5f; 619 | fr_sample[n] *= 0.5f; 620 | 621 | /* 622 | * this removes dc from signal 623 | */ 624 | fl_offset = fl_offset * 0.99 + fl_sample[n] * 0.01; 625 | fr_offset = fr_offset * 0.99 + fr_sample[n] * 0.01; 626 | 627 | fl_sample[n] -= fl_offset; 628 | fr_sample[n] -= fr_offset; 629 | 630 | /* 631 | * put vu values to vu meters 632 | */ 633 | *vuInL = max(*vuInL, absf(fl_sample[n])); 634 | *vuInR = max(*vuInR, absf(fr_sample[n])); 635 | } 636 | 637 | /* 638 | * main loop core 639 | */ 640 | Sampler_Process(fl_sample, fr_sample, SAMPLE_BUFFER_SIZE); 641 | 642 | #ifdef MAX_DELAY 643 | /* 644 | * process delay line 645 | */ 646 | Delay_Process_Buff2(fl_sample, fr_sample, SAMPLE_BUFFER_SIZE); 647 | #endif 648 | 649 | /* 650 | * add some mono reverb 651 | */ 652 | Reverb_Process(fl_sample, SAMPLE_BUFFER_SIZE); 653 | memcpy(fr_sample, fl_sample, sizeof(fr_sample)); 654 | 655 | /* 656 | * apply master output gain 657 | */ 658 | for (int n = 0; n < SAMPLE_BUFFER_SIZE; n++) 659 | { 660 | /* apply master_output_gain */ 661 | fl_sample[n] *= master_output_gain; 662 | fr_sample[n] *= master_output_gain; 663 | 664 | /* output signal to vu meter */ 665 | *vuOutL = max(*vuOutL, absf(fl_sample[n])); 666 | *vuOutR = max(*vuOutR, absf(fr_sample[n])); 667 | } 668 | 669 | Audio_Output(fl_sample, fr_sample); 670 | 671 | #ifdef OLED_OSC_DISP_ENABLED 672 | ScopeOled_AddSamples(fl_sample, fr_sample, SAMPLE_BUFFER_SIZE); 673 | #endif 674 | 675 | Status_Process_Sample(SAMPLE_BUFFER_SIZE); 676 | } 677 | 678 | /* 679 | * this function will be called once a second 680 | * call can be delayed when one operation needs more time (> 1/44100s) 681 | */ 682 | void loop_1Hz(void) 683 | { 684 | #ifdef BLINK_LED_PIN 685 | Blink_Process(); 686 | #endif 687 | #if 0 /* use this line to to show analog button values to setup their values */ 688 | printf("ADC: %d\n", analogRead(36)); 689 | #endif 690 | } 691 | 692 | void loop_100Hz(void) 693 | { 694 | static uint32_t loop_cnt; 695 | 696 | loop_cnt += 1; 697 | if ((loop_cnt) >= 100) 698 | { 699 | loop_cnt = 0; 700 | loop_1Hz(); 701 | } 702 | 703 | /* 704 | * Put your functions here which should be called 100 times a second 705 | */ 706 | #ifdef ESP32_AUDIO_KIT 707 | button_loop(); 708 | #endif 709 | } 710 | 711 | #ifdef AS5600_ENABLED 712 | inline 713 | void loop_4th() 714 | { 715 | #ifdef AS5600_ENABLED 716 | #ifdef DISPLAY_160x80_ENABLED 717 | if (Display_Busy() == false) 718 | #endif 719 | { 720 | AS5600_Loop(); 721 | Sampler_SetPitchAbs(AS5600_GetPitch(4)); 722 | } 723 | #endif 724 | } 725 | #endif 726 | 727 | /* 728 | * this is the main loop 729 | */ 730 | void loop() 731 | { 732 | audio_task(); /* audio tasks blocks for one sample -> 1/44100s */ 733 | 734 | #ifdef AS5600_ENABLED 735 | static uint32_t prec4 = 0; 736 | prec4++; 737 | if ((prec4 % 4) == 0) 738 | { 739 | loop_4th(); 740 | } 741 | #endif 742 | 743 | VuMeterMatrix_Process(); /* calculates slow falling bars */ 744 | 745 | static uint32_t loop_cnt; 746 | 747 | loop_cnt += SAMPLE_BUFFER_SIZE; 748 | if ((loop_cnt) >= (SAMPLE_RATE / 100)) 749 | { 750 | loop_cnt = 0; 751 | loop_100Hz(); 752 | } 753 | 754 | /* 755 | * doing midi only 64 times per sample cycle 756 | */ 757 | Midi_Process(); 758 | 759 | #ifdef MIDI_STREAM_PLAYER_ENABLED 760 | MidiStreamPlayer_Tick(SAMPLE_BUFFER_SIZE); 761 | #endif 762 | } 763 | 764 | uint8_t baseKey = 64 + 12 + 7; // c 765 | 766 | #ifdef KEYS_TO_MIDI_NOTES 767 | 768 | void App_Key1Down() 769 | { 770 | Sampler_NoteOn(1, baseKey + 0, 1); 771 | } 772 | 773 | void App_Key2Down() 774 | { 775 | Sampler_NoteOn(1, baseKey + 2, 1); 776 | } 777 | 778 | void App_Key3Down() 779 | { 780 | Sampler_NoteOn(1, baseKey + 4, 1); 781 | } 782 | 783 | void App_Key4Down() 784 | { 785 | Sampler_NoteOn(1, baseKey + 5, 1); 786 | } 787 | 788 | void App_Key5Down() 789 | { 790 | Sampler_NoteOn(1, baseKey + 7, 1); 791 | } 792 | 793 | void App_Key6Down() 794 | { 795 | Sampler_NoteOn(1, baseKey + 9, 1); 796 | } 797 | 798 | void App_Key1Up() 799 | { 800 | 801 | Sampler_NoteOff(1, baseKey + 0); 802 | } 803 | 804 | void App_Key2Up() 805 | { 806 | Sampler_NoteOff(1, baseKey + 2); 807 | } 808 | 809 | void App_Key3Up() 810 | { 811 | Sampler_NoteOff(1, baseKey + 4); 812 | } 813 | 814 | void App_Key4Up() 815 | { 816 | Sampler_NoteOff(1, baseKey + 5); 817 | } 818 | 819 | void App_Key5Up() 820 | { 821 | Sampler_NoteOff(1, baseKey + 7); 822 | } 823 | 824 | void App_Key6Up() 825 | { 826 | Sampler_NoteOff(1, baseKey + 9); 827 | } 828 | #else 829 | 830 | enum keyMode_e 831 | { 832 | keyMode_playbackChrom, 833 | keyMode_record, 834 | keyMode_playbackSample, 835 | keyMode_storage 836 | }; 837 | 838 | enum keyMode_e currentKeyMode = keyMode_playbackChrom; 839 | 840 | #ifdef AS5600_ENABLED 841 | 842 | uint8_t scratchNote = 0; 843 | 844 | void App_ButtonCbPlaySample(uint8_t key, uint8_t down) 845 | { 846 | if (down > 0) 847 | { 848 | switch (key) 849 | { 850 | case 0: 851 | scratchNote++; 852 | Sampler_SetScratchSample(scratchNote, 1); 853 | break; 854 | 855 | case 1: 856 | if (scratchNote > 0) 857 | { 858 | scratchNote--; 859 | } 860 | Sampler_SetScratchSample(scratchNote, 1); 861 | break; 862 | } 863 | } 864 | switch (key) 865 | { 866 | case 2: 867 | delay(250); /* to avoid recording the noise of the button */ 868 | Sampler_RecordWait(0, down); 869 | break; 870 | 871 | case 3: 872 | //Sampler_Record(0, down); 873 | if (down > 0) 874 | { 875 | AS5600_SetPitchOffset(1.0f); 876 | } 877 | else 878 | { 879 | AS5600_SetPitchOffset(-100.0f); 880 | } 881 | break; 882 | } 883 | } 884 | 885 | #else 886 | 887 | uint8_t lastCh = 1; 888 | 889 | void App_ButtonCb(uint8_t key, uint8_t down) 890 | { 891 | if ((key == 0) && (down > 0)) 892 | { 893 | switch (currentKeyMode) 894 | { 895 | case keyMode_storage: 896 | currentKeyMode = keyMode_record; 897 | #ifdef DISPLAY_160x80_ENABLED 898 | Display_SetHeader("Record"); 899 | #endif 900 | Status_LogMessage("Record"); 901 | 902 | Status_SetKeyText(0, "1:select"); 903 | Status_SetKeyText(1, "2:rWait"); 904 | Status_SetKeyText(2, "3:record"); 905 | Status_SetKeyText(3, "4:littFs"); 906 | Status_SetKeyText(4, "5:sdMmc"); 907 | Status_SetKeyText(5, "6:save"); 908 | 909 | break; 910 | case keyMode_record: 911 | currentKeyMode = keyMode_playbackSample; 912 | #ifdef DISPLAY_160x80_ENABLED 913 | Display_SetHeader("Sample playback"); 914 | #endif 915 | Status_LogMessage("Sample playback"); 916 | 917 | Status_SetKeyText(0, "1:select"); 918 | Status_SetKeyText(1, "2:smpl1"); 919 | Status_SetKeyText(2, "3:smpl2"); 920 | Status_SetKeyText(3, "4:smpl3"); 921 | Status_SetKeyText(4, "5:smpl4"); 922 | Status_SetKeyText(5, "6:smpl5"); 923 | break; 924 | case keyMode_playbackSample: 925 | currentKeyMode = keyMode_playbackChrom; 926 | #ifdef DISPLAY_160x80_ENABLED 927 | Display_SetHeader("Chromatic playback"); 928 | #endif 929 | Status_LogMessage("Chromatic playback"); 930 | 931 | Status_SetKeyText(0, "1:select"); 932 | Status_SetKeyText(1, "2:c"); 933 | Status_SetKeyText(2, "3:c#"); 934 | Status_SetKeyText(3, "4:d"); 935 | Status_SetKeyText(4, "5:d#"); 936 | Status_SetKeyText(5, "6:e"); 937 | break; 938 | 939 | case keyMode_playbackChrom: 940 | currentKeyMode = keyMode_storage; 941 | #ifdef DISPLAY_160x80_ENABLED 942 | Display_SetHeader("Sample loader"); 943 | #endif 944 | Status_LogMessage("Sample loader"); 945 | 946 | Status_SetKeyText(0, "1:select"); 947 | Status_SetKeyText(1, "2:next"); 948 | Status_SetKeyText(2, "3:prev"); 949 | Status_SetKeyText(3, "4:littFs"); 950 | Status_SetKeyText(4, "5:sdMmc"); 951 | Status_SetKeyText(5, "6:load"); 952 | break; 953 | } 954 | } 955 | else 956 | { 957 | switch (currentKeyMode) 958 | { 959 | case keyMode_playbackChrom: 960 | if (down > 0) 961 | { 962 | Sampler_NoteOn(lastCh, baseKey + key, 1); 963 | } 964 | else 965 | { 966 | Sampler_NoteOff(lastCh, baseKey + key); 967 | } 968 | break; 969 | case keyMode_record: 970 | switch (key) 971 | { 972 | case 1: 973 | delay(250); /* to avoid recording the noise of the button */ 974 | Sampler_RecordWait(0, down); 975 | break; 976 | case 2: 977 | Sampler_Record(0, down); 978 | break; 979 | case 3: 980 | PatchManager_SetDestination(0, 0); 981 | break; 982 | case 4: 983 | PatchManager_SetDestination(0, 1); 984 | break; 985 | case 5: 986 | Sampler_SavePatch(0, down); 987 | break; 988 | 989 | } 990 | break; 991 | case keyMode_playbackSample: 992 | if (down > 0) 993 | { 994 | Sampler_NoteOn(0, key - 1, 1); 995 | lastCh = key; 996 | } 997 | else 998 | { 999 | Sampler_NoteOff(0, key - 1); 1000 | } 1001 | break; 1002 | case keyMode_storage: 1003 | if (down > 0) 1004 | { 1005 | switch (key) 1006 | { 1007 | case 1: 1008 | PatchManager_FileIdxInc(0, 1); 1009 | break; 1010 | case 2: 1011 | PatchManager_FileIdxDec(0, 1); 1012 | break; 1013 | case 3: 1014 | PatchManager_SetDestination(0, 0); 1015 | break; 1016 | case 4: 1017 | PatchManager_SetDestination(0, 1); 1018 | break; 1019 | case 5: 1020 | Sampler_LoadPatch(0, 1); 1021 | break; 1022 | } 1023 | } 1024 | break; 1025 | } 1026 | } 1027 | } 1028 | 1029 | #endif 1030 | #endif 1031 | 1032 | void App_SetBrightness(uint8_t unused, float value) 1033 | { 1034 | #ifdef DISPLAY_160x80_ENABLED 1035 | Display_SetBacklight(value * 255.0f); 1036 | #endif 1037 | VuMeterMatrix_SetBrighness(unused, value / 8.0f); 1038 | } 1039 | 1040 | -------------------------------------------------------------------------------- /midi_setup.md: -------------------------------------------------------------------------------- 1 | # MIDI Setup 2 | 3 | ## Board selection 4 | 5 | First, please ensure that the correct board is selected. For that check the config.h. 6 | In case you are using the ESP32 Audio Kit you should find the line: 7 | #define ESP32_AUDIO_KIT 8 | otherwise if you are not using the ESP32 Audio Kit (just ESP32 for example DOIT ESP32 DEVKIT V1) you should change the line to: 9 | //#define ESP32_AUDIO_KIT 10 | In that case the macro will not be active 11 | 12 | ## MIDI input connection 13 | 14 | The MIDI input pin is defined using MIDI_RX_PIN. To be more flexible I've prepared two defines for quicker testing on the ESP32 Audio Kit and also on other hardware. 15 | 16 | For example IO18 can be used as MIDI input on the ESP32 Audio Kit using: 17 | #define MIDI_RX_PIN 18 18 | 19 | ## MIDI baudrate 20 | 21 | The MIDI baud rate is specified to be 31250 22 | It can be modified if needed (for example to send messages via computer using another interface). 23 | In that case you can use: 24 | #define MIDI_SERIAL2_BAUDRATE 115200 /* you can use this to change the serial speed */ 25 | 26 | If you want to use the default MIDI baud rate ensure that the setting is commented-out: 27 | //#define MIDI_SERIAL2_BAUDRATE 28 | 29 | ## MIDI mapping 30 | 31 | ### Identify control elements 32 | Standard MIDI messages like noteOn/noteOff/pitchbend are used as specified in MIDI. 33 | In z_config.ino you will find a predefined MIDI map which can be replaced by your own to map functions to your controller. 34 | 35 | To identify your different controls go into midi_module.ino and change '//#define DUMP_SERIAL2_TO_SERIAL' to '#define DUMP_SERIAL2_TO_SERIAL' 36 | In that case you will see all received MIDI messages in your console window. Moving a specific controller would create messages like this 37 | ">b0 12 37", ">b0 12 38" etc. 38 | 39 | Now you can create a line for each control element: 40 | { 0x0, 0x12, "Pot 1", NULL, NULL, 0}, 41 | 42 | The first number is the channel (that is the one after the ">b"). The second number is the data1 value, also called the control number. After that you can define a name so you know which knob, slider etc. it is. 43 | When this controller is not mapped to any function you should add ", NULL, NULL, 0}," at the end of the line. 44 | 45 | ### Map control elements 46 | In my code you will find that there are different functions mapped. 47 | 48 | For example Reverb is mapped to a controller which I call "R2": 49 | { 0x0, 0x12, "R9", NULL, Reverb_SetLevel, 0}, 50 | 51 | So that means that 52 | - channel 0 53 | - control number 0x12 54 | - with the name "R9" 55 | is mapped to 56 | - NULL (a specific type of function) 57 | - Reverb_SetLevel 58 | - with additional parameter value of 0 (the value will also provided to the called function) 59 | 60 | For example by setting the destination the additional parameter is used to determine which destination should be used: 61 | { 0x4, 0x50, "A5", NULL, PatchManager_SetDestination, 0}, 62 | { 0x5, 0x50, "A6", NULL, PatchManager_SetDestination, 1}, 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /ml_inline.ino: -------------------------------------------------------------------------------- 1 | 2 | #define ML_SYNTH_INLINE_DEFINITION 3 | #include 4 | #undef ML_SYNTH_INLINE_DEFINITION 5 | -------------------------------------------------------------------------------- /patch_manager.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Marcel Licence 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * Dieses Programm ist Freie Software: Sie können es unter den Bedingungen 18 | * der GNU General Public License, wie von der Free Software Foundation, 19 | * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren 20 | * veröffentlichten Version, weiter verteilen und/oder modifizieren. 21 | * 22 | * Dieses Programm wird in der Hoffnung bereitgestellt, dass es nützlich sein wird, jedoch 23 | * OHNE JEDE GEWÄHR,; sogar ohne die implizite 24 | * Gewähr der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. 25 | * Siehe die GNU General Public License für weitere Einzelheiten. 26 | * 27 | * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem 28 | * Programm erhalten haben. Wenn nicht, siehe . 29 | */ 30 | 31 | /* 32 | * this file contains the implementation of the "patch" manager 33 | * It allows loading and saving the samples including parameters 34 | * 35 | * Saved data will appear as pairs: .wav and .bin 36 | * The parameters are in the .bin file 37 | * 38 | * Hot swap of sd card is supported. SD is busy only during read/write operation 39 | * 40 | * Support of loading wav files without parameters 41 | * - in case no parameters are existing default values are used 42 | * 43 | * only signed 16 bit wav files are supported at the moment 44 | * sampleRate is ignored -> you can pitch it afterwards 45 | *​ 46 | * Author: Marcel Licence 47 | */ 48 | 49 | 50 | #ifdef __CDT_PARSER__ 51 | #include 52 | #endif 53 | 54 | 55 | #include 56 | 57 | #ifdef USE_SPIFFS_LEGACY 58 | 59 | #include /* Using library SPIFFS at version 1.0 from https://github.com/espressif/arduino-esp32 */ 60 | #define LittleFS SPIFFS 61 | 62 | #else /* USE_SPIFFS_LEGACY */ 63 | 64 | #include 65 | #ifdef ARDUINO_RUNNING_CORE /* tested with arduino esp32 core version 2.0.2 */ 66 | #include /* Using library LittleFS at version 2.0.0 from https://github.com/espressif/arduino-esp32 */ 67 | #else 68 | #include /* Using library LittleFS_esp32 at version 1.0.6 from https://github.com/lorol/LITTLEFS */ 69 | #define LittleFS LITTLEFS 70 | #endif 71 | 72 | #endif /* USE_SPIFFS_LEGACY */ 73 | 74 | #include 75 | 76 | 77 | #define FST fs::FS 78 | 79 | 80 | enum patchDst 81 | { 82 | patch_dest_littlefs, 83 | patch_dest_sd_mmc, 84 | }; 85 | 86 | struct patchParamV0_s 87 | { 88 | float pitch; 89 | float loop_start; 90 | float loop_end; 91 | }; 92 | 93 | struct patchParamV1_s 94 | { 95 | float attack; 96 | float decay; 97 | float sustain; 98 | float release; 99 | }; 100 | 101 | struct patchParam_s 102 | { 103 | union 104 | { 105 | struct 106 | { 107 | uint32_t version; 108 | 109 | struct patchParamV0_s patchParamV0; 110 | struct patchParamV1_s patchParamV1; 111 | 112 | }; 113 | uint8_t buffer[512]; /*! raw data */ 114 | }; 115 | char filename[64]; 116 | }; 117 | 118 | /* 119 | * union is very handy for easy conversion of bytes to the wav header information 120 | */ 121 | union wavHeader 122 | { 123 | struct 124 | { 125 | char riff[4]; /*!< 'RIFF' */ 126 | uint32_t fileSize; /*!< bytes to write containing all data (header + data) */ 127 | char waveType[4]; /*!< 'WAVE' */ 128 | 129 | char format[4]; /*!< 'fmt ' */ 130 | uint32_t lengthOfData; /*!< length of the fmt header (16 bytes) */ 131 | uint16_t format_tag; /*!< 0x0001: PCM */ 132 | uint16_t numberOfChannels; /*!< 'WAVE' */ 133 | uint32_t sampleRate; 134 | uint32_t byteRate; 135 | 136 | uint16_t bytesPerSample; 137 | uint16_t bitsPerSample; 138 | char dataStr[4]; 139 | uint32_t dataSize; /* 22052 */ 140 | }; 141 | uint8_t wavHdr[44]; 142 | }; 143 | 144 | 145 | static void PatchManager_SaveWavefile(FST &fs, char *filename, int16_t *buffer, uint32_t bufferSize); 146 | static void PatchManager_FilenameFromIdx(FST &fs, const char *dirname, uint8_t index); 147 | static int PatchManager_GetFileList(FST &fs, const char *dirname, void(*fileInd)(char *filename, int offset), int offset); 148 | static uint32_t PatchManager_LoadWavefile(FST &fs, char *filename, int16_t *buffer, uint32_t bufferSize); 149 | static void PatchManager_CreateDir(FST &fs, const char *path); 150 | static void PatchManager_SavePatchParam(FST &fs, char *filename, struct patchParam_s *patchParam); 151 | static void PatchManager_LoadPatchParam(FST &fs, char *filename, struct patchParam_s *patchParam); 152 | static void PatchManager_CreateNewFileNames(FST &fs); 153 | 154 | 155 | uint32_t patch_selectedFileIndex = 0; 156 | char currentFileNameWav[64] = "/samples/testSample.wav\0"; 157 | char currentFileNameBin[64] = "/samples/testSample.bin\0"; 158 | 159 | 160 | enum patchDst patchManagerDest = patch_dest_littlefs; 161 | 162 | /* 163 | * last written files 164 | */ 165 | char wavNewFileName[64]; 166 | char parNewFileName[64]; 167 | 168 | 169 | void PatchManager_Init(void) 170 | { 171 | /* nothing to do */ 172 | } 173 | 174 | static int PatchManager_GetFileList(FST &fs, const char *dirname, void(*fileInd)(char *filename, int offset), int offset) 175 | { 176 | #ifdef PATCHMANAGER_DEBUG 177 | Serial.printf("Listing directory: %s\n", dirname); 178 | #endif 179 | File root = fs.open(dirname); 180 | if (!root) 181 | { 182 | Status_LogMessage("Failed to open directory"); 183 | return 0; 184 | } 185 | if (!root.isDirectory()) 186 | { 187 | Status_LogMessage("Not a directory"); 188 | return 0; 189 | } 190 | 191 | File file = root.openNextFile(); 192 | 193 | 194 | int foundFiles = 0; 195 | 196 | while (file) 197 | { 198 | if (file.isDirectory()) 199 | { 200 | #if 0 201 | Serial.print(" DIR : "); 202 | Serial.println(file.name()); 203 | #endif 204 | } 205 | else 206 | { 207 | #if 0 208 | Serial.printf("%03d - FILE: ", patch_selectedFileIndex); 209 | Serial.print(file.name()); 210 | Serial.print(" SIZE: "); 211 | 212 | Serial.println(file.size()); 213 | #endif 214 | strcpy(currentFileNameWav, file.name()); 215 | strcpy(currentFileNameBin, file.name()); 216 | strcpy(¤tFileNameBin[strlen(currentFileNameBin) - 3], "bin"); 217 | 218 | if (strcmp(".wav", &file.name()[strlen(file.name()) - 4]) == 0) 219 | { 220 | #if 0 221 | Serial.printf("ignore %s, %s\n", ".wav", &file.name()[strlen(file.name()) - 4]); 222 | #endif 223 | 224 | 225 | if (offset > 0) 226 | { 227 | offset--; 228 | } 229 | else 230 | { 231 | fileInd(currentFileNameWav, foundFiles++); 232 | } 233 | } 234 | } 235 | file = root.openNextFile(); 236 | } 237 | return foundFiles; 238 | } 239 | 240 | int PatchManager_GetFileListExt(void(*fileInd)(char *filename, int offset), int offset) 241 | { 242 | if (patchManagerDest == patch_dest_sd_mmc) 243 | { 244 | if (PatchManager_PrepareSdCard()) 245 | { 246 | return PatchManager_GetFileList(SD_MMC, "/samples", fileInd, offset); 247 | } 248 | } 249 | #ifdef ESP32 250 | else 251 | { 252 | if (PatchManager_PrepareLittleFs()) 253 | { 254 | return PatchManager_GetFileList(LittleFS, "/samples", fileInd, offset); 255 | } 256 | } 257 | #endif 258 | return 0; 259 | } 260 | 261 | static void PatchManager_FilenameFromIdx(FST &fs, const char *dirname, uint8_t index) 262 | { 263 | #ifdef PATCHMANAGER_DEBUG 264 | Serial.printf("Listing directory: %s\n", dirname); 265 | #endif 266 | File root = fs.open(dirname); 267 | if (!root) 268 | { 269 | Status_LogMessage("Failed to open directory"); 270 | return; 271 | } 272 | if (!root.isDirectory()) 273 | { 274 | Status_LogMessage("Not a directory"); 275 | return; 276 | } 277 | 278 | File file = root.openNextFile(); 279 | patch_selectedFileIndex = 0; 280 | while (file) 281 | { 282 | if (file.isDirectory()) 283 | { 284 | #if 0 285 | Serial.print(" DIR : "); 286 | Serial.println(file.name()); 287 | #endif 288 | } 289 | else 290 | { 291 | #if 0 292 | Serial.printf("%03d - FILE: ", patch_selectedFileIndex); 293 | Serial.print(file.name()); 294 | Serial.print(" SIZE: "); 295 | 296 | Serial.println(file.size()); 297 | #endif 298 | strcpy(currentFileNameWav, file.name()); 299 | strcpy(currentFileNameBin, file.name()); 300 | strcpy(¤tFileNameBin[strlen(currentFileNameBin) - 3], "bin"); 301 | 302 | if (strcmp(".wav", &file.name()[strlen(file.name()) - 4]) == 0) 303 | { 304 | #if 0 305 | Serial.printf("ignore %s, %s\n", ".wav", &file.name()[strlen(file.name()) - 4]); 306 | #endif 307 | if (index > 0) 308 | { 309 | index--; 310 | } 311 | else 312 | { 313 | return; 314 | } 315 | patch_selectedFileIndex++; 316 | } 317 | 318 | 319 | } 320 | file = root.openNextFile(); 321 | } 322 | patch_selectedFileIndex--; 323 | } 324 | 325 | char lastSelectedFile[128] = ""; 326 | 327 | void PatchManager_UpdateFilename(void) 328 | { 329 | if (patchManagerDest == patch_dest_sd_mmc) 330 | { 331 | if (PatchManager_PrepareSdCard()) 332 | { 333 | PatchManager_FilenameFromIdx(SD_MMC, "/samples", patch_selectedFileIndex); 334 | #ifdef ESP32 335 | SD_MMC.end(); 336 | #endif 337 | #ifdef PATCHMANAGER_DEBUG 338 | Serial.printf("Active file: %03d - %s\n", patch_selectedFileIndex, currentFileNameWav); 339 | Serial.printf("Active file: %03d - %s\n", patch_selectedFileIndex, currentFileNameBin); 340 | #endif 341 | sprintf(lastSelectedFile, "SD_MMC: %s", currentFileNameWav); 342 | Status_FileName(lastSelectedFile); 343 | sprintf(lastSelectedFile, "%s", currentFileNameWav); 344 | } 345 | } 346 | #ifdef ESP32 347 | else 348 | { 349 | if (PatchManager_PrepareLittleFs()) 350 | { 351 | PatchManager_FilenameFromIdx(LittleFS, "/samples", patch_selectedFileIndex); 352 | LittleFS.end(); 353 | #ifdef PATCHMANAGER_DEBUG 354 | Serial.printf("Active file: %03d - %s\n", patch_selectedFileIndex, currentFileNameWav); 355 | Serial.printf("Active file: %03d - %s\n", patch_selectedFileIndex, currentFileNameBin); 356 | #endif 357 | sprintf(lastSelectedFile, "LittleFS: %s", currentFileNameWav); 358 | Status_FileName(lastSelectedFile); 359 | sprintf(lastSelectedFile, "%s", currentFileNameWav); 360 | } 361 | } 362 | #endif 363 | } 364 | 365 | void PatchManager_FileIdxInc(uint8_t unused, float value) 366 | { 367 | if (value > 0) 368 | { 369 | patch_selectedFileIndex++; 370 | PatchManager_UpdateFilename(); 371 | } 372 | } 373 | 374 | void PatchManager_FileIdxDec(uint8_t unused, float value) 375 | { 376 | if (value > 0) 377 | { 378 | if (patch_selectedFileIndex > 0) 379 | { 380 | patch_selectedFileIndex--; 381 | } 382 | PatchManager_UpdateFilename(); 383 | } 384 | } 385 | 386 | static void PatchManager_SaveWavefile(FST &fs, char *filename, int16_t *buffer, uint32_t bufferSize) 387 | { 388 | File f = fs.open(filename, FILE_WRITE); 389 | if (!f) 390 | { 391 | Status_LogMessage("Could not create new file\n"); 392 | return; 393 | } 394 | 395 | uint32_t dataSizeOfbuffer = sizeof(int16_t) * bufferSize; 396 | union wavHeader wavHeader; 397 | 398 | memcpy(wavHeader.riff, "RIFF", 4); 399 | wavHeader.fileSize = 44 + dataSizeOfbuffer; 400 | memcpy(wavHeader.waveType, "WAVE", 4); 401 | memcpy(wavHeader.format, "fmt ", 4); 402 | wavHeader.lengthOfData = 16; /* length of the fmt header */ 403 | wavHeader.format_tag = 0x0001; /* 0x0001: PCM */ 404 | wavHeader.numberOfChannels = 1; 405 | wavHeader.sampleRate = 44100; 406 | wavHeader.byteRate = 44100 * 2; 407 | wavHeader.bytesPerSample = 2; 408 | wavHeader.bitsPerSample = 16; 409 | 410 | memcpy(wavHeader.dataStr, "data", 4); 411 | wavHeader.dataSize = dataSizeOfbuffer; 412 | 413 | #ifdef ESP32 414 | f.seek(0, SeekSet); 415 | #else 416 | f.seek(0); 417 | #endif 418 | f.write(wavHeader.wavHdr, 44); 419 | 420 | /* avoid watchdog */ 421 | delay(1); 422 | 423 | f.write((uint8_t *)buffer, dataSizeOfbuffer); 424 | f.close(); 425 | 426 | /* avoid watchdog */ 427 | delay(1); 428 | } 429 | 430 | static uint32_t PatchManager_LoadWavefile(FST &fs, char *filename, int16_t *buffer, uint32_t bufferSize) 431 | { 432 | File f = fs.open(filename, FILE_READ); 433 | if (!f) 434 | { 435 | Status_LogMessage("Could not read file\n"); 436 | return 0; 437 | } 438 | 439 | union wavHeader wavHeader; 440 | 441 | memset(wavHeader.wavHdr, 0, sizeof(wavHeader)); 442 | 443 | #ifdef ESP32 444 | f.seek(0, SeekSet); 445 | #else 446 | f.seek(0); 447 | #endif 448 | f.read(wavHeader.wavHdr, 44); 449 | 450 | /* avoid watchdog */ 451 | delay(1); 452 | 453 | uint32_t bufferIn = 0; 454 | 455 | if (wavHeader.numberOfChannels == 1) 456 | { 457 | #ifdef ESP32 458 | f.read((uint8_t *)buffer, wavHeader.dataSize); 459 | #else 460 | uint32_t leftSize = wavHeader.dataSize; 461 | uint32_t bin = 0; 462 | while (leftSize > 256) 463 | { 464 | f.read((uint8_t *)&buffer[bin], 256); 465 | bin += 128; // because its int16 takes to bytes 466 | leftSize -= 256; 467 | } 468 | if (leftSize > 0) 469 | { 470 | f.read((uint8_t *)&buffer[bin], leftSize); 471 | } 472 | #endif 473 | bufferIn += wavHeader.dataSize; 474 | } 475 | else 476 | { 477 | uint32_t dataLeft = wavHeader.dataSize; 478 | uint32_t dataRead; 479 | 480 | while (true) 481 | { 482 | uint8_t tempBuffer[512]; 483 | if (dataLeft > 512) 484 | { 485 | dataRead = 512; 486 | } 487 | else 488 | { 489 | dataRead = dataLeft; 490 | } 491 | 492 | f.read(tempBuffer, dataRead); 493 | dataLeft -= dataRead; 494 | 495 | for (int i = 0; i < dataRead; i++) 496 | { 497 | /* 498 | * reading only the left channel 499 | * right channel will be ignored and data just skipped 500 | */ 501 | ((uint8_t *)buffer)[bufferIn] = tempBuffer[i]; 502 | bufferIn += 1; 503 | i += 1; 504 | ((uint8_t *)buffer)[bufferIn] = tempBuffer[i]; 505 | bufferIn += 1; 506 | i += 1; 507 | 508 | i += 1; 509 | } 510 | 511 | if (dataLeft == 0) 512 | { 513 | break; 514 | } 515 | } 516 | } 517 | f.close(); 518 | 519 | /* avoid watchdog */ 520 | delay(1); 521 | 522 | return bufferIn / sizeof(int16_t); 523 | } 524 | 525 | static void PatchManager_CreateDir(FST &fs, const char *path) 526 | { 527 | Serial.printf("Creating Dir: %s\n", path); 528 | if (fs.mkdir(path)) 529 | { 530 | Serial.println("Dir created"); 531 | } 532 | else 533 | { 534 | Status_LogMessage("mkdir failed"); 535 | } 536 | } 537 | 538 | bool PatchManager_PrepareSdCard(void) 539 | { 540 | #ifdef ESP32 541 | if (!SD_MMC.begin("/sdcard", true)) /* makes less noise on recording! */ 542 | #else 543 | if (!card.init(SD_DETECT_NONE)) 544 | #endif 545 | { 546 | Status_LogMessage("Card Mount Failed"); 547 | delay(1000); 548 | return false; 549 | } 550 | 551 | #ifdef ESP32 552 | uint8_t cardType = SD_MMC.cardType(); 553 | 554 | if (cardType == CARD_NONE) 555 | { 556 | Status_LogMessage("No SD card attached"); 557 | 558 | delay(1000); 559 | return false; 560 | } 561 | 562 | if (cardType == CARD_MMC) 563 | { 564 | Status_LogMessage("Card Access: MMC"); 565 | } 566 | else if (cardType == CARD_SD) 567 | { 568 | Status_LogMessage("Card Access: SDSC"); 569 | } 570 | else if (cardType == CARD_SDHC) 571 | { 572 | Status_LogMessage("Card Access: SDHC"); 573 | } 574 | else 575 | { 576 | Status_LogMessage("Card Access: UNKNOWN"); 577 | } 578 | #endif 579 | 580 | return true; 581 | } 582 | 583 | #ifdef ESP32 584 | bool PatchManager_PrepareLittleFs(void) 585 | { 586 | if (!LittleFS.begin()) 587 | { 588 | Status_LogMessage("LittleFS Mount Failed"); 589 | return false; 590 | } 591 | 592 | return true; 593 | } 594 | #endif 595 | 596 | static void PatchManager_SavePatchParam(FST &fs, char *filename, struct patchParam_s *patchParam) 597 | { 598 | File f = fs.open(filename, FILE_WRITE); 599 | if (!f) 600 | { 601 | Status_LogMessage("Could not create new file\n"); 602 | return; 603 | } 604 | 605 | f.write((uint8_t *)patchParam, sizeof(*patchParam)); 606 | 607 | f.close(); 608 | } 609 | 610 | static void PatchManager_LoadPatchParam(FST &fs, char *filename, struct patchParam_s *patchParam) 611 | { 612 | File f = fs.open(filename, FILE_READ); 613 | if (!f) 614 | { 615 | #ifdef PATCHMANAGER_DEBUG 616 | Serial.printf("No patch parameter\nUsing default values\n"); 617 | #endif 618 | patchParam->version = 0; 619 | patchParam->patchParamV0.pitch = 1; 620 | patchParam->patchParamV0.loop_start = 0; 621 | patchParam->patchParamV0.loop_end = 9999999; /* using sample length would be better */ 622 | 623 | patchParam->patchParamV1.attack = 1; 624 | patchParam->patchParamV1.decay = 1; 625 | patchParam->patchParamV1.release = 0; 626 | patchParam->patchParamV1.sustain = 1; 627 | return; 628 | } 629 | 630 | f.read((uint8_t *)patchParam, sizeof(*patchParam)); 631 | 632 | #ifdef PATCHMANAGER_DEBUG 633 | Serial.printf("patchParam:\n"); 634 | Serial.printf(" version: %d\n", patchParam->version); 635 | 636 | Serial.printf(" pitch: %0.06f\n", patchParam->patchParamV0.pitch); 637 | Serial.printf(" loop_start: %0.06f\n", patchParam->patchParamV0.loop_start); 638 | Serial.printf(" loop_end: %0.06f\n", patchParam->patchParamV0.loop_end); 639 | 640 | if (patchParam->version >= 1) 641 | { 642 | Serial.printf(" attack: %0.06f\n", patchParam->patchParamV1.attack); 643 | Serial.printf(" decay: %0.06f\n", patchParam->patchParamV1.decay); 644 | Serial.printf(" sustain: %0.06f\n", patchParam->patchParamV1.sustain); 645 | Serial.printf(" release: %0.06f\n", patchParam->patchParamV1.release); 646 | } 647 | #endif 648 | 649 | f.close(); 650 | } 651 | 652 | void PatchManager_SetDestination(uint8_t destination, float value) 653 | { 654 | if (value > 0) 655 | { 656 | switch (destination) 657 | { 658 | case 0: 659 | patchManagerDest = patch_dest_littlefs; 660 | Status_TestMsg("Patch storage: little fs"); 661 | break; 662 | case 1: 663 | patchManagerDest = patch_dest_sd_mmc; 664 | Status_TestMsg("Patch storage: sd mmc"); 665 | break; 666 | } 667 | PatchManager_UpdateFilename(); 668 | } 669 | } 670 | 671 | static void PatchManager_CreateNewFileNames(FST &fs) 672 | { 673 | 674 | int i = 0; 675 | 676 | while (true) 677 | { 678 | sprintf(wavNewFileName, "/samples/newSample%03d.wav", i); 679 | sprintf(parNewFileName, "/samples/newSample%03d.bin", i); 680 | 681 | if (fs.exists(wavNewFileName)) 682 | { 683 | i++; 684 | continue; 685 | } 686 | 687 | if (fs.exists(parNewFileName)) 688 | { 689 | i++; 690 | continue; 691 | } 692 | 693 | break; 694 | } 695 | } 696 | 697 | void PatchManager_SaveNewPatch(struct patchParam_s *patchParam, int16_t *buffer, int bufferSize) 698 | { 699 | if (patchManagerDest == patch_dest_sd_mmc) 700 | { 701 | if (PatchManager_PrepareSdCard()) 702 | { 703 | PatchManager_CreateDir(SD_MMC, "/samples"); 704 | PatchManager_CreateNewFileNames(SD_MMC); 705 | PatchManager_SavePatchParam(SD_MMC, parNewFileName, patchParam); 706 | PatchManager_SaveWavefile(SD_MMC, wavNewFileName, buffer, bufferSize); 707 | #ifdef ESP32 708 | SD_MMC.end(); 709 | #endif 710 | #ifdef PATCHMANAGER_DEBUG 711 | Serial.printf("Written %d to %s on SD_MMC\n", bufferSize, wavFileName); 712 | #else 713 | Status_ValueChangedInt("Written to SD_MMC", bufferSize); 714 | #endif 715 | } 716 | } 717 | #ifdef ESP32 718 | else 719 | { 720 | if (PatchManager_PrepareLittleFs()) 721 | { 722 | PatchManager_CreateDir(LittleFS, "/samples"); 723 | PatchManager_CreateNewFileNames(LittleFS); 724 | PatchManager_SavePatchParam(LittleFS, parNewFileName, patchParam); 725 | PatchManager_SaveWavefile(LittleFS, wavNewFileName, buffer, bufferSize); 726 | LittleFS.end(); 727 | #ifdef PATCHMANAGER_DEBUG 728 | Serial.printf("Written %d to %s on LittleFS\n", bufferSize, wavFileName); 729 | #else 730 | Status_ValueChangedInt("Written to LittleFS", bufferSize); 731 | #endif 732 | } 733 | } 734 | #endif 735 | } 736 | 737 | void PatchManager_SetFilename(const char *filename) 738 | { 739 | strcpy(currentFileNameWav, filename); 740 | strcpy(currentFileNameBin, filename); 741 | strcpy(¤tFileNameBin[strlen(currentFileNameBin) - 3], "bin"); 742 | } 743 | 744 | uint32_t PatchManager_LoadPatch(struct patchParam_s *patchParam, int16_t *buffer, int bufferSize) 745 | { 746 | memset(patchParam, 0, sizeof(*patchParam)); 747 | 748 | uint32_t readBufferBytes = 0 ; 749 | 750 | if (patchManagerDest == patch_dest_sd_mmc) 751 | { 752 | if (PatchManager_PrepareSdCard()) 753 | { 754 | PatchManager_LoadPatchParam(SD_MMC, currentFileNameBin, patchParam); 755 | 756 | readBufferBytes = PatchManager_LoadWavefile(SD_MMC, currentFileNameWav, buffer, bufferSize); 757 | #ifdef ESP32 758 | SD_MMC.end(); 759 | #endif 760 | #ifdef PATCHMANAGER_DEBUG 761 | Serial.printf("Read %d from %s on SD_MMC\n", readBufferBytes, currentFileNameWav); 762 | #else 763 | Status_ValueChangedInt("Read from SD_MMC", readBufferBytes); 764 | #endif 765 | } 766 | } 767 | #ifdef ESP32 768 | else 769 | { 770 | if (PatchManager_PrepareLittleFs()) 771 | { 772 | PatchManager_LoadPatchParam(LittleFS, currentFileNameBin, patchParam); 773 | 774 | readBufferBytes = PatchManager_LoadWavefile(LittleFS, currentFileNameWav, buffer, bufferSize); 775 | LittleFS.end(); 776 | #ifdef PATCHMANAGER_DEBUG 777 | Serial.printf("Read %d from %s on LittleFS\n", readBufferBytes, currentFileNameWav); 778 | #else 779 | Status_ValueChangedInt("Read from LittleFS", readBufferBytes); 780 | #endif 781 | } 782 | } 783 | #endif 784 | 785 | memcpy(patchParam->filename, currentFileNameBin, sizeof(patchParam->filename)); 786 | 787 | return readBufferBytes; 788 | } 789 | -------------------------------------------------------------------------------- /sampler_module.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Marcel Licence 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * Dieses Programm ist Freie Software: Sie können es unter den Bedingungen 18 | * der GNU General Public License, wie von der Free Software Foundation, 19 | * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren 20 | * veröffentlichten Version, weiter verteilen und/oder modifizieren. 21 | * 22 | * Dieses Programm wird in der Hoffnung bereitgestellt, dass es nützlich sein wird, jedoch 23 | * OHNE JEDE GEWÄHR,; sogar ohne die implizite 24 | * Gewähr der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. 25 | * Siehe die GNU General Public License für weitere Einzelheiten. 26 | * 27 | * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem 28 | * Programm erhalten haben. Wenn nicht, siehe . 29 | */ 30 | 31 | /* 32 | * this file contains the implementation of the sampling core 33 | * a big PSRAM buffer will be allocated 34 | * you can record to the buffer and playback samples 35 | * MIDI ch1 noteOn message will trigger different samples 36 | * MIDI ch2-16 noteOn will trigger each a certain sample with different pitch 37 | * 38 | * setting loop start/end is only a rough and crappy implementation 39 | * - variable/functions name are confusing​ 40 | * 41 | * Author: Marcel Licence 42 | */ 43 | 44 | #ifdef __CDT_PARSER__ 45 | #include 46 | #endif 47 | 48 | /* using exp release curve would never reach 0 a defined limit is required */ 49 | #define AUDIBLE_LIMIT (0.25f/32768.0f) 50 | 51 | #define SAMPLE_MAX_RECORDS 32 52 | 53 | #define NOTE_NORMAL 69 /* this is an a -> playback a with original recorded speed */ 54 | 55 | #define SAMPLE_MAX_PLAYERS 8 /* max polyphony, higher values 'may' not be processed in time */ 56 | 57 | #define MAX_FILENAME_LENGTH 64 58 | 59 | /* 60 | * little helpers 61 | */ 62 | #ifndef absf 63 | #define absf(a) ((a>=0.0f)?(a):(-a)) 64 | #endif 65 | 66 | #ifndef absI 67 | #define absI(a) ((a >= 0)?(a):(-a)) 68 | #endif 69 | 70 | #define maxI(a, b) (a>b)?(a):(b) 71 | 72 | enum samplStatusE 73 | { 74 | sampler_idle, 75 | sampler_rec, 76 | sampler_recWait, 77 | sampler_measureThreshold, 78 | }; 79 | 80 | enum sampleADSR 81 | { 82 | adsr_attack, 83 | adsr_decay, 84 | adsr_sustain, 85 | adsr_release, 86 | }; 87 | 88 | /* 89 | * parameters for each sample 90 | */ 91 | struct sample_record_s 92 | { 93 | uint32_t start; 94 | uint32_t end; 95 | uint8_t channels; 96 | bool valid; 97 | float loop_end; 98 | float loop_start; 99 | float pitch; 100 | 101 | float attack; 102 | float decay; 103 | float sustain; 104 | float release; 105 | 106 | char filename[MAX_FILENAME_LENGTH]; 107 | }; 108 | 109 | struct sample_player_s 110 | { 111 | struct sample_record_s *sample_rec; 112 | int32_t pos; 113 | float pos_f; 114 | float pitch; 115 | bool playing; 116 | bool pressed; 117 | uint8_t ch; 118 | uint8_t note; 119 | int normNote; 120 | float slow; 121 | 122 | float velocity; 123 | float adsr_gain; 124 | enum sampleADSR adsr_state; 125 | }; 126 | 127 | 128 | struct sample_record_s sampleRecords[SAMPLE_MAX_RECORDS]; 129 | struct sample_player_s samplePlayers[SAMPLE_MAX_PLAYERS]; 130 | 131 | void (*sampler_recordDoneCb)(void) = NULL; 132 | 133 | uint32_t sampleRecordCount = 0; /*!< count of samples in buffer and valid sampleRecords */ 134 | uint32_t sampleStorageInPos = 0; /*!< next free sample in sampleStorage */ 135 | uint32_t sampleStorageLen = 0; /*!< max len of samples storage */ 136 | int16_t *sampleStorage = NULL; /*!< here is were the audio data will be stored */ 137 | 138 | bool samplerManualRecord = false; /*!< manual record avoids stopping the record by threshold */ 139 | 140 | enum samplStatusE sampleStatus = sampler_idle; 141 | 142 | /* vu meter pointer values */ 143 | static float *vuStoreLen; 144 | static float *vuThrInput; 145 | static float *vuAbsInput; 146 | static float *vuSlwInput; 147 | 148 | float samplerThreshold = 0.02f; 149 | 150 | float inputMonoAbs = 0.0f; 151 | float inputMaxFiltered = 0; 152 | 153 | uint32_t lastIn = 0; 154 | 155 | bool loop_param_lock = false; /*!< ignore changes of loop start/end when set to true - required for nervous MIDI controllers */ 156 | 157 | float loop_start_c = 0; 158 | float loop_start_f = 0; 159 | 160 | float loop_end_c = 0; 161 | float loop_end_f = 0; 162 | float loop_end_mul = 0.0f; 163 | 164 | float modulationDepth = 0.0f; 165 | float modulationSpeed = 5.0f; // 7 maybe better? 166 | float modulationPitch = 1.0f; 167 | float pitchBendValue = 0.0f; 168 | 169 | #ifdef AS5600_ENABLED 170 | struct sample_record_s scratchRec; 171 | #endif 172 | 173 | struct sample_record_s *lastActiveRec = NULL; 174 | 175 | /* 176 | * used for looped playback 177 | */ 178 | 179 | struct sample_player_s *beatPlayer = NULL; 180 | 181 | uint8_t sampler_lastCh = 0xFF; 182 | uint8_t sampler_lastNote = 0xFF; 183 | 184 | void Sampler_Init(int16_t *storage, uint32_t storageLen) 185 | { 186 | /* remember storage pointer and length */ 187 | sampleStorage = storage; 188 | sampleStorageLen = storageLen; 189 | 190 | for (int i = 0; i < SAMPLE_MAX_PLAYERS; i++) 191 | { 192 | samplePlayers[i].sample_rec = &sampleRecords[0]; 193 | samplePlayers[i].playing = false; 194 | samplePlayers[i].slow = 0.0f; 195 | } 196 | 197 | for (int i = 0; i < SAMPLE_MAX_RECORDS; i++) 198 | { 199 | sampleRecords[i].pitch = 1.0f; 200 | sampleRecords[i].release = 1.0f; 201 | sampleRecords[i].decay = 1.0f; 202 | sampleRecords[i].attack = 1.0f; 203 | sampleRecords[i].sustain = 1.0f; 204 | } 205 | 206 | vuStoreLen = VuMeterMatrix_GetPtr(2); 207 | vuThrInput = VuMeterMatrix_GetPtr(4); 208 | vuAbsInput = VuMeterMatrix_GetPtr(5); 209 | vuSlwInput = VuMeterMatrix_GetPtr(3); 210 | } 211 | 212 | float Modulation(void) 213 | { 214 | float modSpeed = modulationSpeed; 215 | return modulationDepth * modulationPitch * (SineNorm((modSpeed * ((float)millis()) / 1000.0f))); 216 | } 217 | 218 | float FrequencyFromVoice(struct sample_record_s *const voice, float note) 219 | { 220 | #if 0 221 | float noteA = voice->oldNote; 222 | uint8_t noteB = voice->activeNote; 223 | float port = voice->port; 224 | float note = (((float)(noteA)) * (1.0f - port) + ((float)(noteB)) * port) - 69.0f + pitchBendValue + Modulation(); 225 | #endif 226 | 227 | note += pitchBendValue + Modulation(); 228 | float f = ((pow(2.0f, note / 12.0f))); /* no frequency so dont use * 440.0f */ 229 | return f; 230 | } 231 | 232 | inline 233 | void Sampler_ProcessADSR(struct sample_player_s *player) 234 | { 235 | switch (player->adsr_state) 236 | { 237 | case adsr_attack: 238 | /* multiplier sounds bad so we should increase gain linear here */ 239 | player->adsr_gain += player->sample_rec->attack; 240 | if (player->adsr_gain >= 1.0f) 241 | { 242 | player->adsr_gain = 1.0f; 243 | player->adsr_state = adsr_decay; 244 | } 245 | break; 246 | case adsr_decay: 247 | player->adsr_gain *= player->sample_rec->decay; 248 | if (player->adsr_gain <= player->sample_rec->sustain) 249 | { 250 | player->adsr_state = adsr_sustain; 251 | player->adsr_gain = player->sample_rec->sustain; /* avoid undershoot */ 252 | } 253 | break; 254 | case adsr_sustain: 255 | player->adsr_gain = player->sample_rec->sustain; 256 | break; 257 | case adsr_release: 258 | player->adsr_gain *= player->sample_rec->release; 259 | break; 260 | } 261 | } 262 | 263 | void Sampler_Process(float *signal_l, float *signal_r, const int buffLen) 264 | { 265 | *vuStoreLen = ((float)sampleStorageInPos) / ((float)sampleStorageLen); 266 | *vuThrInput = samplerThreshold; 267 | 268 | for (int n = 0; n < buffLen; n++) 269 | { 270 | 271 | inputMonoAbs = max(absf(signal_l[n]), absf(signal_r[n])); 272 | 273 | *vuAbsInput = inputMonoAbs; 274 | 275 | switch (sampleStatus) 276 | { 277 | case sampler_measureThreshold: 278 | if (inputMonoAbs > samplerThreshold) 279 | { 280 | samplerThreshold = inputMonoAbs; 281 | } 282 | 283 | break; 284 | case sampler_rec: 285 | case sampler_recWait: 286 | case sampler_idle: 287 | /* no action */ 288 | break; 289 | } 290 | 291 | if (sampleStatus == sampler_recWait) 292 | { 293 | if ((inputMonoAbs > samplerThreshold)) 294 | { 295 | Sampler_RecordStart(); 296 | inputMaxFiltered = 1.0f; 297 | } 298 | } 299 | 300 | inputMaxFiltered = max(inputMonoAbs, inputMaxFiltered); 301 | inputMaxFiltered *= 0.9996843825158444074f; /* same as 0.98 every 64 frames = (pow(1/64) */ 302 | 303 | *vuSlwInput = inputMaxFiltered; 304 | 305 | if (sampleStatus == sampler_rec) 306 | { 307 | static uint32_t recEndTimeout = 0; 308 | int16_t s16 = 0U; 309 | s16 += (int16_t)(((float)0x8000) * signal_l[n]); 310 | //s16 += (((float)0x8000) * signal_r[n]); 311 | sampleStorage[sampleStorageInPos] = s16; 312 | sampleStorageInPos++; 313 | if (sampleStorageInPos >= sampleStorageLen) 314 | { 315 | Status_TestMsg("PSRAM memory full!"); 316 | Sampler_RecordStop(); 317 | } 318 | 319 | if ((inputMaxFiltered < samplerThreshold) && (samplerManualRecord == false)) 320 | { 321 | recEndTimeout ++; 322 | } 323 | else 324 | { 325 | recEndTimeout = 0; 326 | } 327 | 328 | if (recEndTimeout > 11025) /* record 250ms after signal went under the threshold */ 329 | { 330 | Status_TestMsg("Stopped by low threshold!"); 331 | Sampler_RecordStop(); 332 | } 333 | } 334 | 335 | #if 0 336 | /* no pass through */ 337 | signal_l[n] = slowL; 338 | signal_r[n] = slowR; 339 | 340 | slowL *= 0.99f; 341 | slowR *= 0.99f; 342 | #endif 343 | 344 | #ifdef SAMPLER_PASS_TROUGH_DURING_RECORD 345 | if (sampleStatus == sampler_idle) 346 | #endif 347 | #ifndef SAMPLER_ALWAYS_PASS_THROUGH 348 | { 349 | /* make quiet to prepare for next step */ 350 | signal_l[n] = 0.0f; 351 | signal_r[n] = 0.0f; 352 | } 353 | #endif 354 | } 355 | 356 | for (int i = 0; i < SAMPLE_MAX_PLAYERS; i++) 357 | { 358 | struct sample_player_s *player = &samplePlayers[i]; 359 | 360 | /* calc pitch only once per buffer */ 361 | player->pitch = player->sample_rec->pitch * FrequencyFromVoice(player->sample_rec, player->normNote); 362 | 363 | for (int n = 0; n < buffLen; n++) 364 | { 365 | signal_l[n] += player->slow; 366 | signal_r[n] += player->slow; 367 | player->slow *= 0.99f; 368 | 369 | if (player->playing) 370 | { 371 | float f2 = player->pos_f; 372 | float f1 = 1.0f - f2; 373 | 374 | float sample_f = 0; 375 | sample_f += f1 * ((float)sampleStorage[player->pos]) / ((float)0x8000); 376 | sample_f += f2 * ((float)sampleStorage[player->pos + 1]) / ((float)0x8000); 377 | 378 | sample_f *= player->velocity; 379 | /* 380 | * adsr 381 | */ 382 | sample_f *= player->adsr_gain; 383 | 384 | Sampler_ProcessADSR(player); 385 | 386 | if ((player->pos_f == 0.0f) && (player->pos == 0)) 387 | { 388 | player->slow -= sample_f; 389 | } 390 | 391 | signal_l[n] += sample_f; 392 | signal_r[n] += sample_f; 393 | 394 | /* move to next sample */ 395 | int32_t pitch_u = player->pitch; 396 | player->pos_f += player->pitch - pitch_u; /* does not work great when pos_f is bigger */ 397 | player->pos += pitch_u; 398 | 399 | int posI = player->pos_f; 400 | player->pos += posI; 401 | player->pos_f -= posI; 402 | 403 | if (player->pressed) 404 | { 405 | float sampleLen = player->sample_rec->loop_end - player->sample_rec->loop_start; 406 | /* 407 | * sampleLen = 34896 408 | * 409 | */ 410 | if (player->pos - ((float)player->sample_rec->start) >= (player->sample_rec->loop_end)) 411 | { 412 | uint32_t sampleLenU = sampleLen; 413 | player->pos -= sampleLenU; 414 | player->pos_f -= sampleLen - sampleLenU; 415 | } 416 | #ifdef AS5600_ENABLED 417 | if (player->pos - ((float)player->sample_rec->start) < player->sample_rec->loop_start) 418 | { 419 | uint32_t sampleLenU = sampleLen; 420 | player->pos += sampleLenU; 421 | player->pos_f += sampleLen - sampleLenU; 422 | } 423 | #endif 424 | } 425 | 426 | /* stop playback when end has been reached */ 427 | /* stop playback when signal is not audible anymore */ 428 | if ((player->pos >= player->sample_rec->end) || (player->adsr_gain < AUDIBLE_LIMIT)) 429 | { 430 | player->playing = false; 431 | player->slow += sample_f; 432 | } 433 | } 434 | } 435 | } 436 | 437 | for (int n = 0; n < buffLen; n++) 438 | { 439 | /* 440 | * make it a bit quieter to avoid distortion in next stage 441 | */ 442 | signal_l[n] *= 0.25f; 443 | signal_r[n] *= 0.25f; 444 | } 445 | } 446 | 447 | struct sample_player_s *getFreeSamplePlayer(void) 448 | { 449 | for (int i = 0; i < SAMPLE_MAX_PLAYERS; i++) 450 | { 451 | if (samplePlayers[i].playing == false) 452 | { 453 | return &samplePlayers[i]; 454 | } 455 | } 456 | 457 | struct sample_player_s *quietestPlayer = NULL; 458 | float lowestGain = 1.0f; 459 | 460 | for (int i = 0; i < SAMPLE_MAX_PLAYERS; i++) 461 | { 462 | if (samplePlayers[i].adsr_gain <= lowestGain) 463 | { 464 | quietestPlayer = &samplePlayers[i]; 465 | lowestGain = quietestPlayer->adsr_gain; 466 | } 467 | } 468 | 469 | return quietestPlayer; 470 | } 471 | 472 | 473 | void Sampler_StartSamplePlayer(struct sample_player_s *player, struct sample_record_s *rec) 474 | { 475 | player->sample_rec = rec; 476 | player->pos = player->sample_rec->start; 477 | player->pos_f = 0.0f; 478 | player->playing = true; 479 | player->pressed = true; 480 | player->pitch = player->sample_rec->pitch; 481 | 482 | 483 | player->normNote = 0; 484 | 485 | player->adsr_gain = AUDIBLE_LIMIT; 486 | player->adsr_state = adsr_attack; 487 | Sampler_ProcessADSR(player); 488 | } 489 | 490 | inline struct sample_player_s *Sampler_NoteOnInt(uint8_t ch, uint8_t note, float vel) 491 | { 492 | struct sample_player_s *freePlayer = NULL; 493 | if (ch == 0) 494 | { 495 | freePlayer = getFreeSamplePlayer(); 496 | struct sample_record_s *rec = &sampleRecords[note % sampleRecordCount]; 497 | 498 | if (freePlayer != NULL) 499 | { 500 | Sampler_StartSamplePlayer(freePlayer, rec); 501 | 502 | freePlayer->ch = ch; 503 | freePlayer->note = note; 504 | freePlayer->velocity = vel; 505 | 506 | lastActiveRec = freePlayer->sample_rec; 507 | } 508 | } 509 | else 510 | { 511 | freePlayer = getFreeSamplePlayer(); 512 | struct sample_record_s *rec = &sampleRecords[(ch - 1) % sampleRecordCount]; /* decrease by one because we want to start with the first sample here */ 513 | 514 | if (freePlayer != NULL) 515 | { 516 | Sampler_StartSamplePlayer(freePlayer, rec); 517 | 518 | freePlayer->ch = ch; 519 | freePlayer->note = note; 520 | freePlayer->velocity = vel; 521 | freePlayer->normNote = note - NOTE_NORMAL; 522 | freePlayer->pitch *= pow(2.0f, 1.0f / 12.0f * (note - NOTE_NORMAL)); /* this would be the a as middle */ 523 | 524 | lastActiveRec = freePlayer->sample_rec; 525 | } 526 | } 527 | 528 | return freePlayer; 529 | } 530 | 531 | void Sampler_NoteOn(uint8_t ch, uint8_t note, float vel) 532 | { 533 | if (sampleRecordCount == 0) 534 | { 535 | return; 536 | } 537 | 538 | sampler_lastCh = ch; 539 | sampler_lastNote = note; 540 | 541 | (void *)Sampler_NoteOnInt(ch, note, vel); 542 | } 543 | 544 | void Sampler_NoteOff(uint8_t ch, uint8_t note) 545 | { 546 | for (int i = 0; i < SAMPLE_MAX_PLAYERS; i++) 547 | { 548 | if ((samplePlayers[i].ch == ch) && (samplePlayers[i].note == note)) 549 | { 550 | samplePlayers[i].pressed = false; 551 | samplePlayers[i].adsr_state = adsr_release; 552 | } 553 | } 554 | } 555 | 556 | void Sampler_RecordStop(void) 557 | { 558 | if (sampleStatus == sampler_rec) 559 | { 560 | if (sampleStorageInPos > sampleRecords[sampleRecordCount].start) 561 | { 562 | sampleRecords[sampleRecordCount].valid = true; 563 | sampleRecords[sampleRecordCount].end = sampleStorageInPos - 1; /* pointing to last sample */ 564 | sampleRecordCount += 1; 565 | } 566 | sampleStatus = sampler_idle; 567 | Status_TestMsg("Recording done!"); 568 | 569 | struct sample_record_s *tempRec = lastActiveRec; 570 | lastActiveRec = &sampleRecords[sampleRecordCount - 1]; 571 | Sampler_NormalizeActiveRecording(0, 1); 572 | lastActiveRec = tempRec; 573 | 574 | if (sampler_recordDoneCb != NULL) 575 | { 576 | sampler_recordDoneCb(); 577 | } 578 | } 579 | else if (sampleStatus == sampler_recWait) 580 | { 581 | Status_TestMsg("Recording cancelled!"); 582 | sampleStatus = sampler_idle; 583 | } 584 | } 585 | 586 | void Sampler_RecordStart(void) 587 | { 588 | sampleRecords[sampleRecordCount].start = sampleStorageInPos; 589 | sampleStatus = sampler_rec; 590 | Status_TestMsg("Recording started.."); 591 | } 592 | 593 | void Sampler_MeasureThreshold(uint8_t quarter, float value) 594 | { 595 | if ((value > 0) && (sampleStatus == sampler_idle)) 596 | { 597 | sampleStatus = sampler_measureThreshold; 598 | samplerThreshold = 0.0f; 599 | Status_TestMsg("Measuring threshold started.."); 600 | } 601 | 602 | if ((value == 0) && (sampleStatus == sampler_measureThreshold)) 603 | { 604 | sampleStatus = sampler_idle; 605 | Status_ValueChangedFloat("SamplerThreshold", samplerThreshold); 606 | } 607 | } 608 | 609 | void Sampler_Record(uint8_t quarter, float value) 610 | { 611 | if (value > 0) 612 | { 613 | if (sampleRecordCount < SAMPLE_MAX_RECORDS) 614 | { 615 | samplerManualRecord = true; 616 | if (sampleStatus != sampler_rec) 617 | { 618 | Sampler_RecordStart(); 619 | } 620 | } 621 | else 622 | { 623 | Status_TestMsg("No free sample handlers available!"); 624 | } 625 | } 626 | else 627 | { 628 | samplerManualRecord = false; 629 | Sampler_RecordStop(); 630 | } 631 | } 632 | 633 | void Sampler_Stop(uint8_t quarter, float value) 634 | { 635 | if (value > 0) 636 | { 637 | samplerManualRecord = false; 638 | Sampler_RecordStop(); 639 | 640 | if (beatPlayer != NULL) 641 | { 642 | Sampler_NoteOff(beatPlayer->ch, beatPlayer->note); 643 | } 644 | } 645 | } 646 | 647 | void Sampler_Play(uint8_t unused, float value) 648 | { 649 | if (value > 0) 650 | { 651 | if (sampler_lastCh != 0xFF) 652 | { 653 | struct sample_record_s *tempActiveRec = lastActiveRec; 654 | beatPlayer = Sampler_NoteOnInt(sampler_lastCh, sampler_lastNote, 1); 655 | lastActiveRec = tempActiveRec; 656 | } 657 | } 658 | } 659 | 660 | #ifdef AS5600_ENABLED 661 | struct sample_player_s *scratchPlayer = NULL; 662 | 663 | void Sampler_SetScratchSample(uint8_t selSample, float value) 664 | { 665 | if (value > 0) 666 | { 667 | Sampler_NoteOff(0xEE, 0xEE); 668 | 669 | if (selSample == 0xFF) 670 | { 671 | /* use last sample for scratching */ 672 | memcpy(&scratchRec, lastActiveRec, sizeof(scratchRec)); 673 | } 674 | else 675 | { 676 | /* use selected sample for scratching */ 677 | memcpy(&scratchRec, &sampleRecords[selSample % sampleRecordCount], sizeof(scratchRec)); 678 | } 679 | 680 | float sampleLen = (scratchRec.end - scratchRec.start); 681 | 682 | scratchRec.loop_start = 0; 683 | scratchRec.loop_end = sampleLen - 1; 684 | scratchRec.release = 0; 685 | scratchRec.pitch = 0; 686 | 687 | { 688 | scratchPlayer = getFreeSamplePlayer(); 689 | struct sample_record_s *rec = &scratchRec; 690 | 691 | if (scratchPlayer != NULL) 692 | { 693 | Sampler_StartSamplePlayer(scratchPlayer, rec); 694 | 695 | scratchPlayer->ch = 0xEE; 696 | scratchPlayer->note = 0xEE; 697 | scratchPlayer->velocity = 1; 698 | #ifdef DISPLAY_160x80_ENABLED 699 | Display_SetFullText(scratchRec.filename); 700 | Display_Redraw(); 701 | #endif 702 | } 703 | } 704 | } 705 | } 706 | 707 | void Sampler_ScratchFader(uint8_t unused, float value) 708 | { 709 | float vol = /*1.0f -*/ value; 710 | vol *= 5; 711 | if (vol > 1) 712 | { 713 | vol = 1; 714 | } 715 | if (scratchPlayer != NULL) 716 | { 717 | scratchPlayer->velocity = vol; 718 | } 719 | 720 | vol = 1.0f - value; 721 | vol *= 5; 722 | if (vol > 1) 723 | { 724 | vol = 1; 725 | } 726 | 727 | if (beatPlayer != NULL) 728 | { 729 | beatPlayer->velocity = vol; 730 | } 731 | } 732 | #endif 733 | 734 | void Sampler_LoopStartC(uint8_t quarter, float value) 735 | { 736 | if (loop_param_lock) 737 | { 738 | return; 739 | } 740 | 741 | loop_start_c = (10000.0f * value); 742 | Sampler_UpdateLoopRange(); 743 | if (lastActiveRec != NULL) 744 | { 745 | Status_ValueChangedFloat("loop_start", lastActiveRec->loop_start); 746 | } 747 | } 748 | 749 | void Sampler_LoopStartF(uint8_t quarter, float value) 750 | { 751 | if (loop_param_lock) 752 | { 753 | return; 754 | } 755 | 756 | loop_start_f = value * 100.f; 757 | Sampler_UpdateLoopRange(); 758 | if (lastActiveRec != NULL) 759 | { 760 | Status_ValueChangedFloat("loop_start", lastActiveRec->loop_start); 761 | } 762 | } 763 | 764 | void Sampler_LoopEndC(uint8_t quarter, float value) 765 | { 766 | if (loop_param_lock) 767 | { 768 | return; 769 | } 770 | 771 | /* 772 | * length range one octave 773 | * 0 -> 1 774 | * 0.5 -> 1.4142... = sqrt(2) 775 | * 1 -> 2 776 | */ 777 | float samples_a440 = (1.0f / 440.0f) * 44100.0f; 778 | 779 | samples_a440 *= 1.0f / pow(2.0f, (value * 8.0) - 4.0f); 780 | 781 | loop_end_c = samples_a440; 782 | loop_end_c = 1.0f / pow(2.0f, (value / 0.5f)); /* two octaves */ 783 | Sampler_UpdateLoopRange(); 784 | if (lastActiveRec != NULL) 785 | { 786 | Status_ValueChangedFloat("loop_end", lastActiveRec->loop_end); 787 | } 788 | } 789 | 790 | void Sampler_LoopEndF(uint8_t quarter, float value) 791 | { 792 | if (loop_param_lock) 793 | { 794 | return; 795 | } 796 | 797 | loop_end_f = 1.0f / pow(2.0f, (value / 24.0f)); /* quarter note */ 798 | Sampler_UpdateLoopRange(); 799 | if (lastActiveRec != NULL) 800 | { 801 | Status_ValueChangedFloat("loop_end", lastActiveRec->loop_end); 802 | } 803 | } 804 | 805 | void Sampler_LoopAll(uint8_t index, float value) 806 | { 807 | if ((value > 0) && (lastActiveRec != NULL)) 808 | { 809 | float sampleLen = (lastActiveRec->end - lastActiveRec->start); 810 | 811 | lastActiveRec->loop_start = 0; 812 | lastActiveRec->loop_end = sampleLen - 1; 813 | 814 | Status_ValueChangedFloat("len", lastActiveRec->end - lastActiveRec->start); 815 | Status_ValueChangedFloat("loop_start", lastActiveRec->loop_start); 816 | Status_ValueChangedFloat("loop_end", lastActiveRec->loop_end); 817 | } 818 | } 819 | 820 | void Sampler_LoopLock(uint8_t not_used, float value) 821 | { 822 | if (value > 0) 823 | { 824 | Status_TestMsg("Parameters now locked!"); 825 | loop_param_lock = true; 826 | } 827 | } 828 | 829 | void Sampler_LoopUnlock(uint8_t not_used, float value) 830 | { 831 | if (value > 0) 832 | { 833 | Status_TestMsg("Parameters now unlocked!"); 834 | loop_param_lock = false; 835 | } 836 | } 837 | 838 | void Sampler_LoopRemove(uint8_t index, float value) 839 | { 840 | if ((value > 0) && (lastActiveRec != NULL)) 841 | { 842 | lastActiveRec->loop_start = lastActiveRec->end; 843 | lastActiveRec->loop_end = lastActiveRec->end; 844 | } 845 | } 846 | 847 | void Sampler_SetLoopEndMultiplier(uint8_t quarter, float value) 848 | { 849 | int loop_end_mul_i = 1 + ((value) * 127.0f); /* 128 octaves */ 850 | loop_end_mul = loop_end_mul_i; 851 | Sampler_UpdateLoopRange(); 852 | if (lastActiveRec != NULL) 853 | { 854 | Status_ValueChangedFloat("loop_end", lastActiveRec->loop_end); 855 | } 856 | } 857 | 858 | void Sampler_SetPitch(uint8_t quarter, float value) 859 | { 860 | if (lastActiveRec != NULL) 861 | { 862 | lastActiveRec->pitch = 0.5f * pow(2.0f, 2.0f * value); 863 | Status_ValueChangedFloat("pitch", lastActiveRec->pitch); 864 | } 865 | } 866 | 867 | #ifdef AS5600_ENABLED 868 | void Sampler_SetPitchAbs(float value) 869 | { 870 | scratchRec.pitch = value; 871 | } 872 | #endif 873 | 874 | void Sampler_UpdateLoopRange(void) 875 | { 876 | if (lastActiveRec != NULL) 877 | { 878 | float loop_len = 440 * loop_end_mul * loop_end_c * loop_end_f; 879 | if (loop_len >= lastActiveRec->end - lastActiveRec->start) 880 | { 881 | loop_len = lastActiveRec->end - lastActiveRec->start; 882 | } 883 | lastActiveRec->loop_start = loop_start_c + loop_start_f; 884 | lastActiveRec->loop_end = loop_len + lastActiveRec->loop_start; 885 | lastActiveRec->loop_start = min(lastActiveRec->loop_start, lastActiveRec->loop_end); 886 | } 887 | } 888 | 889 | void Sampler_RecordWait(uint8_t quarter, float value) 890 | { 891 | if (value > 0) 892 | { 893 | if (sampleRecordCount < SAMPLE_MAX_RECORDS) 894 | { 895 | Status_TestMsg("Wait to record..."); 896 | sampleStatus = sampler_recWait; 897 | } 898 | else 899 | { 900 | Status_TestMsg("No free sample handlers available!"); 901 | } 902 | } 903 | } 904 | 905 | void Sampler_ModulationWheel(uint8_t ch, float value) 906 | { 907 | modulationDepth = value; 908 | } 909 | 910 | void Sampler_ModulationSpeed(uint8_t ch, float value) 911 | { 912 | modulationSpeed = value * 10; 913 | Status_ValueChangedFloat("ModulationSpeed", modulationSpeed); 914 | } 915 | 916 | void Sampler_ModulationPitch(uint8_t ch, float value) 917 | { 918 | modulationPitch = value * 5; 919 | Status_ValueChangedFloat("ModulationDepth", modulationPitch); 920 | } 921 | 922 | void Sampler_PitchBend(uint8_t ch, float bend) 923 | { 924 | pitchBendValue = bend; 925 | } 926 | 927 | void Sampler_Panic(uint8_t ch, float value) 928 | { 929 | for (int i = 0; i < SAMPLE_MAX_PLAYERS; i++) 930 | { 931 | if ((samplePlayers[i].ch != 0xEE) && (samplePlayers[i].note != 0xEE)) /* do not kill scratch sample */ 932 | { 933 | if (samplePlayers[i].playing) 934 | { 935 | /* show information about stuck notes */ 936 | Serial.printf("KillNote %d\n", i); 937 | } 938 | samplePlayers[i].pressed = false; 939 | samplePlayers[i].playing = false; 940 | Status_TestMsg("Panic! All notes off..."); 941 | } 942 | } 943 | } 944 | 945 | void Sampler_NormalizeActiveRecording(uint8_t unused, float value) 946 | { 947 | if ((value > 0) && (lastActiveRec != NULL)) 948 | { 949 | int16_t maxLvl = 0; 950 | 951 | Status_TestMsg("Normalizing sample..."); 952 | 953 | for (int i = lastActiveRec->start; i < lastActiveRec->end; i++) 954 | { 955 | maxLvl = maxI(absI(sampleStorage[i]), maxLvl); 956 | } 957 | 958 | float gainMultiplier = ((float)32767) / ((float)maxLvl); 959 | 960 | for (int i = lastActiveRec->start; i < lastActiveRec->end; i++) 961 | { 962 | sampleStorage[i] = ((float)sampleStorage[i]) * gainMultiplier; 963 | } 964 | 965 | Status_TestMsg("Sample normalized"); 966 | } 967 | } 968 | 969 | void Sampler_RemoveActiveRecording(uint8_t unused, float value) 970 | { 971 | if ((value > 0) && (lastActiveRec != NULL)) 972 | { 973 | if (lastActiveRec->valid == false) 974 | { 975 | Status_TestMsg("No record selected for removal"); 976 | return; 977 | } 978 | Status_TestMsg("Erasing record..."); 979 | uint32_t recordLength = lastActiveRec->end - lastActiveRec->start; 980 | for (int i = lastActiveRec->start; i + recordLength < sampleStorageLen; i++) 981 | { 982 | sampleStorage[i] = sampleStorage[i + recordLength]; 983 | } 984 | sampleStorageInPos -= recordLength; 985 | 986 | for (int i = 0; i < sampleRecordCount; i++) 987 | { 988 | if (sampleRecords[i].start > lastActiveRec->start) 989 | { 990 | sampleRecords[i].start -= recordLength; 991 | sampleRecords[i].end -= recordLength; 992 | } 993 | } 994 | 995 | lastActiveRec->valid = false; 996 | 997 | if (sampleRecordCount > 1) 998 | { 999 | memcpy(lastActiveRec, &sampleRecords[sampleRecordCount - 1], sizeof(struct sample_record_s)); 1000 | sampleRecords[sampleRecordCount - 1].valid = false; 1001 | sampleRecordCount -= 1; 1002 | } 1003 | 1004 | lastActiveRec = NULL; 1005 | Status_TestMsg("Record removed..."); 1006 | } 1007 | } 1008 | 1009 | void Sampler_SavePatch(uint8_t quarter, float value) 1010 | { 1011 | if (value > 0) 1012 | { 1013 | if (lastActiveRec != NULL) 1014 | { 1015 | struct patchParam_s patchParam; 1016 | memset(&patchParam, 0, sizeof(patchParam)); 1017 | patchParam.version = 1; 1018 | 1019 | patchParam.patchParamV0.pitch = lastActiveRec->pitch; 1020 | patchParam.patchParamV0.loop_start = lastActiveRec->loop_start; 1021 | patchParam.patchParamV0.loop_end = lastActiveRec->loop_end; 1022 | 1023 | patchParam.patchParamV1.attack = lastActiveRec->attack; 1024 | patchParam.patchParamV1.decay = lastActiveRec->decay; 1025 | patchParam.patchParamV1.sustain = lastActiveRec->sustain; 1026 | patchParam.patchParamV1.release = lastActiveRec->release; 1027 | 1028 | PatchManager_SaveNewPatch(&patchParam, &sampleStorage[lastActiveRec->start], lastActiveRec->end - lastActiveRec->start); 1029 | //PatchManager_SaveNewPatch(lastActiveRec, sampleStorage); 1030 | } 1031 | } 1032 | } 1033 | 1034 | void Sampler_LoadPatchFile(const char *filename) 1035 | { 1036 | PatchManager_SetFilename(filename); 1037 | Sampler_LoadPatch(0, 1); 1038 | } 1039 | 1040 | void Sampler_LoadPatch(uint8_t unused, float value) 1041 | { 1042 | if (value > 0) 1043 | { 1044 | struct patchParam_s patchParam; 1045 | 1046 | uint32_t newSampleLen = PatchManager_LoadPatch(&patchParam, &sampleStorage[sampleStorageInPos], sampleStorageLen - sampleStorageInPos); 1047 | 1048 | if (newSampleLen > 0) 1049 | { 1050 | struct sample_record_s *newPatch = &sampleRecords[sampleRecordCount]; 1051 | sampleRecordCount++; 1052 | 1053 | memcpy(newPatch->filename, patchParam.filename, sizeof(newPatch->filename)); 1054 | 1055 | newPatch->pitch = patchParam.patchParamV0.pitch; 1056 | newPatch->loop_start = patchParam.patchParamV0.loop_start; 1057 | newPatch->loop_end = patchParam.patchParamV0.loop_end; 1058 | 1059 | if (patchParam.version >= 1) 1060 | { 1061 | newPatch->attack = patchParam.patchParamV1.attack; 1062 | newPatch->decay = patchParam.patchParamV1.decay; 1063 | newPatch->sustain = patchParam.patchParamV1.sustain; 1064 | newPatch->release = patchParam.patchParamV1.release; 1065 | } 1066 | else 1067 | { 1068 | newPatch->attack = 1.0f; 1069 | newPatch->decay = 1.0f; 1070 | newPatch->sustain = 1.0f; 1071 | newPatch->release = 1.0f; 1072 | } 1073 | 1074 | newPatch->start = sampleStorageInPos; 1075 | newPatch->end = newPatch->start + newSampleLen - 1; /* pointing to last sample */ 1076 | newPatch->valid = true; 1077 | lastIn = sampleStorageInPos; 1078 | sampleStorageInPos += newSampleLen; 1079 | 1080 | Status_ValueChangedInt("Loaded sample", newPatch->end - newPatch->start); 1081 | 1082 | /* select new recorded sample */ 1083 | lastActiveRec = newPatch; 1084 | } 1085 | else 1086 | { 1087 | lastActiveRec = NULL; 1088 | } 1089 | } 1090 | } 1091 | 1092 | void Sampler_SetADSR_Attack(uint8_t not_used, float value) 1093 | { 1094 | if (lastActiveRec != NULL) 1095 | { 1096 | float attackInv = pow(2.0f, value * 4) - 1.0f; 1097 | Status_ValueChangedFloat("attackInv", attackInv); 1098 | if (attackInv > 0.0f) 1099 | { 1100 | lastActiveRec->attack = 1 / (attackInv * 44100.0f); 1101 | } 1102 | else 1103 | { 1104 | lastActiveRec->attack = 1 / AUDIBLE_LIMIT; 1105 | } 1106 | Status_ValueChangedFloat("attack", lastActiveRec->attack); 1107 | } 1108 | } 1109 | 1110 | void Sampler_SetADSR_Decay(uint8_t not_used, float value) 1111 | { 1112 | if (lastActiveRec != NULL) 1113 | { 1114 | lastActiveRec->decay = pow(value, 10.0f / 44100.0f); /* value controls the level after one tenth of a second */ 1115 | Status_ValueChangedFloat("decay", lastActiveRec->decay); 1116 | } 1117 | } 1118 | 1119 | void Sampler_SetADSR_Release(uint8_t not_used, float value) 1120 | { 1121 | if (lastActiveRec != NULL) 1122 | { 1123 | lastActiveRec->release = pow(value, 10.0f / 44100.0f); /* value controls the level after one tenth of a second */ 1124 | Status_ValueChangedFloat("release", lastActiveRec->release); 1125 | } 1126 | } 1127 | 1128 | void Sampler_SetADSR_Sustain(uint8_t not_used, float value) 1129 | { 1130 | if (lastActiveRec != NULL) 1131 | { 1132 | lastActiveRec->sustain = value; 1133 | Status_ValueChangedFloat("sustain", lastActiveRec->sustain); 1134 | } 1135 | } 1136 | 1137 | void Sampler_SetRecordDoneCallback(void(*callback)(void)) 1138 | { 1139 | sampler_recordDoneCb = callback; 1140 | } 1141 | 1142 | void Sampler_AddSection(float pitch_keycenter, uint32_t offset, uint32_t end, uint32_t loop_start, uint32_t loop_end, const char *soundName) 1143 | { 1144 | struct sample_record_s *newPatch = &sampleRecords[sampleRecordCount]; 1145 | sampleRecordCount++; 1146 | 1147 | memset(newPatch->filename, 0, MAX_FILENAME_LENGTH); 1148 | memcpy(newPatch->filename, soundName, strnlen(soundName, MAX_FILENAME_LENGTH)); 1149 | 1150 | newPatch->pitch = ((pow(2.0f, (69.0f - ((float)pitch_keycenter)) / 12.0f))); 1151 | newPatch->start = lastIn + offset; 1152 | newPatch->end = lastIn + end; 1153 | 1154 | if ((offset == loop_start) && (end == (loop_end + 1))) 1155 | { 1156 | newPatch->loop_start = 0; 1157 | newPatch->loop_end = 0xFFFFFFFF; 1158 | } 1159 | else 1160 | { 1161 | newPatch->loop_start = loop_start - offset; 1162 | newPatch->loop_end = loop_end - offset; 1163 | } 1164 | 1165 | newPatch->sustain = 1; 1166 | newPatch->attack = 1; 1167 | newPatch->decay = 1; 1168 | newPatch->release = 0.99; 1169 | 1170 | newPatch->valid = true; 1171 | } 1172 | 1173 | -------------------------------------------------------------------------------- /screens.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Marcel Licence 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * Dieses Programm ist Freie Software: Sie können es unter den Bedingungen 18 | * der GNU General Public License, wie von der Free Software Foundation, 19 | * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren 20 | * veröffentlichten Version, weiter verteilen und/oder modifizieren. 21 | * 22 | * Dieses Programm wird in der Hoffnung bereitgestellt, dass es nützlich sein wird, jedoch 23 | * OHNE JEDE GEWÄHR,; sogar ohne die implizite 24 | * Gewähr der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. 25 | * Siehe die GNU General Public License für weitere Einzelheiten. 26 | * 27 | * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem 28 | * Programm erhalten haben. Wenn nicht, siehe . 29 | */ 30 | 31 | /* 32 | * screens.ino 33 | * 34 | * Created on: 08.07.2021 35 | * Author: Marcel Licence 36 | */ 37 | 38 | #ifdef SCREEN_ENABLED 39 | 40 | struct screen_t 41 | { 42 | struct screen_t *l_Screen; 43 | struct screen_t *r_Screen; 44 | void (*enter)(void); 45 | void (*btnCallback)(uint8_t key, uint8_t down); 46 | void (*offsetChanged)(int offset); 47 | }; 48 | 49 | int scrCurMax = 0; 50 | int scrCurSel = -1; 51 | int scrOffset = 0; 52 | 53 | struct screen_t ScrSampleParam = 54 | { 55 | NULL, 56 | NULL, 57 | Screen_SampleEditDraw, 58 | NULL, 59 | NULL, 60 | }; 61 | 62 | struct screen_t ScrStorage = 63 | { 64 | NULL, 65 | NULL, 66 | Screen_StorageAccessDraw, 67 | Screen_StorageAccessBtn, 68 | NULL, 69 | }; 70 | 71 | struct screen_t ScrSampleLoad = 72 | { 73 | NULL, 74 | NULL, 75 | Screen_SampleLoadDraw, 76 | Screen_SampleLoadBtnCb, 77 | Screen_SampleLoadOffset, 78 | }; 79 | 80 | struct screen_t myscreens[] = 81 | { 82 | { 83 | NULL, 84 | NULL, 85 | Screen_SampleEditDraw, 86 | NULL, 87 | NULL, 88 | }, 89 | 90 | { 91 | NULL, 92 | NULL, 93 | Screen_StorageAccessDraw, 94 | Screen_StorageAccessBtn, 95 | NULL, 96 | }, 97 | 98 | { 99 | NULL, 100 | NULL, 101 | Screen_SampleLoadDraw, 102 | Screen_SampleLoadBtnCb, 103 | Screen_SampleLoadOffset, 104 | }, 105 | 106 | { 107 | NULL, 108 | NULL, 109 | Screen_SampleRecordDraw, 110 | Screen_SampleRecordBtnCb, 111 | NULL, 112 | }, 113 | 114 | { 115 | NULL, 116 | NULL, 117 | Screen_EffectsDraw, 118 | Screen_EffectsBtnCb, 119 | NULL, 120 | }, 121 | }; 122 | 123 | struct screen_t *currentScreen = &myscreens[0]; 124 | 125 | #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) 126 | 127 | void Screen_Setup() 128 | { 129 | currentScreen->enter(); 130 | 131 | for (int i = 0; i < ARRAY_SIZE(myscreens); i++) 132 | { 133 | if (i > 0) 134 | { 135 | myscreens[i].l_Screen = &myscreens[i - 1]; 136 | } 137 | if (i < ARRAY_SIZE(myscreens) - 1) 138 | { 139 | myscreens[i].r_Screen = &myscreens[i + 1]; 140 | } 141 | } 142 | } 143 | 144 | char line[8][27]; 145 | 146 | void Screen_ClrLines(void) 147 | { 148 | for (int i = 0; i < 8; i++) 149 | { 150 | memset(line[i], ' ', 26); 151 | } 152 | } 153 | 154 | void Screen_SetDisplayLines(void) 155 | { 156 | for (int i = 0; i < 8; i ++) 157 | { 158 | Display_SetLine(i, line[i]); 159 | } 160 | } 161 | 162 | #define AUDIBLE_LIMIT (0.25f/32768.0f) 163 | 164 | void Screen_SampleEditDraw() 165 | { 166 | Screen_ClrLines(); 167 | 168 | Display_SetHeader("SampleEdit"); 169 | 170 | if (lastActiveRec) 171 | { 172 | snprintf(line[0], 26, "attack: %0.3f", log(AUDIBLE_LIMIT) / log(lastActiveRec->attack)); 173 | snprintf(line[1], 26, "decay: %0.3f", log(AUDIBLE_LIMIT) / log(lastActiveRec->decay)); 174 | snprintf(line[2], 26, "sustain: %0.3f", log(AUDIBLE_LIMIT) / log(lastActiveRec->sustain)); 175 | snprintf(line[3], 26, "release: %0.3f", log(AUDIBLE_LIMIT) / log(lastActiveRec->release)); 176 | } 177 | else 178 | { 179 | snprintf(line[0], 26, "attack: N/A"); 180 | snprintf(line[1], 26, "decay: N/A"); 181 | snprintf(line[2], 26, "sustain: N/A"); 182 | snprintf(line[3], 26, "release: N/A"); 183 | } 184 | 185 | Screen_SetDisplayLines(); 186 | Screen_SetLineCnt(4); 187 | 188 | Display_Redraw(); 189 | } 190 | 191 | 192 | void Screen_StorageAccessDraw() 193 | { 194 | Screen_ClrLines(); 195 | 196 | Display_SetHeader("Storage"); 197 | 198 | snprintf(line[0], 26, "fs: %s", (patchManagerDest == patch_dest_sd_mmc) ? "SD_MMC" : "LITTLEFS"); 199 | snprintf(line[1], 26, "Save current sample"); 200 | 201 | Screen_SetLineCnt(2); 202 | 203 | Screen_SetDisplayLines(); 204 | Display_Redraw(); 205 | } 206 | 207 | void Screen_StorageAccessBtn(uint8_t key, uint8_t down) 208 | { 209 | { 210 | Serial.printf("Screen_StorageAccessBtn!\n"); 211 | } 212 | 213 | switch (scrCurSel) 214 | { 215 | case 0: 216 | if (patchManagerDest == patch_dest_sd_mmc) 217 | { 218 | PatchManager_SetDestination(0, 1); 219 | } 220 | else 221 | { 222 | PatchManager_SetDestination(1, 1); 223 | } 224 | break; 225 | case 1: 226 | Sampler_SavePatch(0, 1); 227 | break; 228 | } 229 | Screen_StorageAccessDraw(); 230 | } 231 | 232 | void Screen_EnterSampleLoaderFileInd(char *filename, int offset) 233 | { 234 | if ((offset >= 0) && (offset < 8)) 235 | { 236 | memset(line[offset], ' ', 26); 237 | memcpy(line[offset], filename, min(26, (int)strlen(filename))); 238 | } 239 | } 240 | 241 | 242 | bool sampleSel = true; 243 | int sampleSelOffset = 0; 244 | 245 | void Screen_SampleLoadDraw() 246 | { 247 | Screen_ClrLines(); 248 | 249 | Display_SetHeader("Sample Load"); 250 | 251 | int filecnt = PatchManager_GetFileListExt(Screen_EnterSampleLoaderFileInd, sampleSelOffset) + sampleSelOffset; 252 | 253 | Screen_SetDisplayLines(); 254 | Screen_SetLineCnt(filecnt); 255 | 256 | Display_Redraw(); 257 | sampleSel = true; 258 | } 259 | 260 | void Screen_SampleLoadBtnCb(uint8_t key, uint8_t down) 261 | { 262 | if (sampleSel) 263 | { 264 | { 265 | patch_selectedFileIndex = scrCurSel; 266 | PatchManager_UpdateFilename(); 267 | Sampler_LoadPatch(0, 1); 268 | Screen_ClrLines(); 269 | snprintf(line[0], 27, "File loaded to ch %d", sampleRecordCount); 270 | snprintf(line[1], 27, " %s", lastSelectedFile); 271 | if (lastActiveRec != NULL) 272 | { 273 | snprintf(line[2], 27, "len: %d (%dms)", lastActiveRec->end - lastActiveRec->start, (lastActiveRec->end - lastActiveRec->start) * 1000 / 44100); 274 | snprintf(line[3], 27, "loop: %0.3f - %0.3f", lastActiveRec->loop_start, lastActiveRec->loop_end); 275 | snprintf(line[4], 27, "pitch: %0.3f", lastActiveRec->pitch); 276 | snprintf(line[5], 27, "a: %0.3f, d: %0.3f", lastActiveRec->attack, lastActiveRec->decay); 277 | snprintf(line[6], 27, "s: %0.3f, r: %0.3f", lastActiveRec->sustain, lastActiveRec->release); 278 | snprintf(line[7], 27, "%0.3f %% storage used", (float)sampleStorageInPos * 100.0f / (float)sampleStorageLen); 279 | } 280 | Screen_SetDisplayLines(); 281 | Screen_SetLineCnt(0); 282 | Display_Redraw(); 283 | } 284 | } 285 | else 286 | { 287 | sampleSel = true; 288 | Screen_SampleLoadDraw(); 289 | } 290 | } 291 | 292 | void Screen_SampleLoadOffset(int offset) 293 | { 294 | sampleSelOffset = offset; 295 | Screen_SampleLoadDraw(); 296 | } 297 | 298 | enum recStateE 299 | { 300 | recStateIdle, 301 | recStateWaitPrep, 302 | recStateWait, 303 | recStateDone, 304 | }; 305 | 306 | enum recStateE recState = recStateIdle; 307 | 308 | bool waitingRec = false; 309 | 310 | void Screen_SampleRecordDraw(void) 311 | { 312 | Screen_ClrLines(); 313 | 314 | Display_SetHeader("Sample Record"); 315 | 316 | snprintf(line[0], 27, "Input %s", selSource == acSrcMic ? "LineIn" : "Microphones"); 317 | 318 | switch (recState) 319 | { 320 | case recStateIdle: 321 | snprintf(line[1], 27, "Record wait"); 322 | 323 | Screen_SetLineCnt(2); 324 | break; 325 | case recStateWaitPrep: 326 | snprintf(line[1], 27, "Preparing for recording"); 327 | snprintf(line[2], 27, "Press button to cancel"); 328 | Screen_SetLineCnt(0); 329 | break; 330 | case recStateWait: 331 | snprintf(line[1], 27, "Waiting for signal"); 332 | snprintf(line[2], 27, "Press button to cancel"); 333 | Screen_SetLineCnt(0); 334 | break; 335 | case recStateDone: 336 | snprintf(line[1], 27, "Recording finished"); 337 | if (lastActiveRec != NULL) 338 | { 339 | snprintf(line[2], 27, "len: %d (%dms)", lastActiveRec->end - lastActiveRec->start, (lastActiveRec->end - lastActiveRec->start) * 1000 / 44100); 340 | snprintf(line[3], 27, "loop: %0.3f - %0.3f", lastActiveRec->loop_start, lastActiveRec->loop_end); 341 | snprintf(line[4], 27, "pitch: %0.3f", lastActiveRec->pitch); 342 | snprintf(line[5], 27, "a: %0.3f, d: %0.3f", lastActiveRec->attack, lastActiveRec->decay); 343 | snprintf(line[6], 27, "s: %0.3f, r: %0.3f", lastActiveRec->sustain, lastActiveRec->release); 344 | snprintf(line[7], 27, "%0.3f %% storage used", (float)sampleStorageInPos * 100.0f / (float)sampleStorageLen); 345 | } 346 | Screen_SetLineCnt(0); 347 | break; 348 | } 349 | 350 | Screen_SetDisplayLines(); 351 | 352 | Display_Redraw(); 353 | } 354 | 355 | void Screen_SampleRecordBtnCb(uint8_t key, uint8_t down) 356 | { 357 | if (down > 0) 358 | { 359 | switch (recState) 360 | { 361 | case recStateIdle: 362 | switch (scrCurSel) 363 | { 364 | case 0: 365 | App_ToggleSource(0, 1); 366 | Screen_SampleRecordDraw(); 367 | break; 368 | case 1: 369 | Sampler_SetRecordDoneCallback(Screen_SampleRecordDoneDraw); 370 | recState = recStateWaitPrep; 371 | Screen_SampleRecordDraw(); 372 | delay(3000); 373 | recState = recStateWait; 374 | Screen_SampleRecordDraw(); 375 | Sampler_RecordWait(0, 1); 376 | break; 377 | } 378 | break; 379 | default: 380 | Sampler_Stop(0, 1); 381 | recState = recStateIdle; 382 | Screen_SampleRecordDraw(); 383 | break; 384 | } 385 | } 386 | } 387 | 388 | float rev_level = 0; /* todo interface required */ 389 | 390 | #if 0 /* actually not availble */ 391 | extern float delayToMix; 392 | extern float delayInLvl; 393 | extern uint32_t delayLen; 394 | extern float delayFeedback; 395 | #else 396 | float delayToMix = 0; 397 | float delayInLvl = 0; 398 | uint32_t delayLen = 0; 399 | float delayFeedback = 0; 400 | #endif 401 | 402 | void Screen_EffectsDraw(void) 403 | { 404 | Screen_ClrLines(); 405 | 406 | Display_SetHeader("Effects"); 407 | 408 | snprintf(line[0], 26, "reverb level: %0.3f", rev_level); 409 | snprintf(line[1], 26, "delay level: %0.3f", delayToMix); 410 | snprintf(line[2], 26, "delay input: %0.3f", delayInLvl); 411 | snprintf(line[3], 26, "delay time: %0.3fms", delayLen * (1000.0f / ((float)SAMPLE_RATE))); 412 | snprintf(line[4], 26, "delay feedback: %0.3f", delayFeedback); 413 | 414 | 415 | Screen_SetDisplayLines(); 416 | Screen_SetLineCnt(4); 417 | 418 | Display_Redraw(); 419 | } 420 | 421 | void Screen_EffectsBtnCb(uint8_t key, uint8_t down) 422 | { 423 | 424 | } 425 | 426 | void Screen_SampleRecordDoneDraw(void) 427 | { 428 | recState = recStateDone; 429 | Screen_SampleRecordDraw(); 430 | Sampler_SetRecordDoneCallback(NULL); 431 | } 432 | 433 | #define BTN_D 0 434 | #define BTN_U 2 435 | #define BTN_L 3 436 | #define BTN_R 1 437 | 438 | void Screen_SetLineCnt(int cnt) 439 | { 440 | scrCurMax = cnt; 441 | } 442 | 443 | void Screen_ButtonCallback(uint8_t key, uint8_t down) 444 | { 445 | if (down > 0) 446 | { 447 | switch (key) 448 | { 449 | case BTN_U: 450 | if (scrCurSel > -1) 451 | { 452 | scrCurSel --; 453 | } 454 | scrOffset = max(0, scrCurSel - 7); 455 | Serial.printf("cur: %d, %d\n", scrCurSel, scrOffset); 456 | if (currentScreen->offsetChanged) 457 | { 458 | currentScreen->offsetChanged(scrOffset); 459 | } 460 | 461 | Display_SetCurSel(scrCurSel - scrOffset); 462 | Display_Redraw(); 463 | break; 464 | case BTN_D: 465 | if (scrCurSel < scrCurMax - 1) 466 | { 467 | scrCurSel ++; 468 | } 469 | scrOffset = max(0, scrCurSel - 7); 470 | Serial.printf("cur: %d, %d\n", scrCurSel, scrOffset); 471 | 472 | if (currentScreen->offsetChanged) 473 | { 474 | currentScreen->offsetChanged(scrOffset); 475 | } 476 | 477 | Display_SetCurSel(scrCurSel - scrOffset); 478 | Display_Redraw(); 479 | break; 480 | case BTN_R: 481 | if (scrCurSel == -1) 482 | { 483 | if (currentScreen->r_Screen) 484 | { 485 | currentScreen = currentScreen->r_Screen; 486 | currentScreen->enter(); 487 | } 488 | } 489 | else 490 | { 491 | if (currentScreen->btnCallback) 492 | { 493 | currentScreen->btnCallback(key, down); 494 | } 495 | else 496 | { 497 | Serial.printf("no btn cb!\n"); 498 | } 499 | } 500 | break; 501 | case BTN_L: 502 | if (scrCurSel == -1) 503 | { 504 | if (currentScreen->l_Screen) 505 | { 506 | currentScreen = currentScreen->l_Screen; 507 | currentScreen->enter(); 508 | } 509 | } 510 | else 511 | { 512 | if (currentScreen->btnCallback) 513 | { 514 | currentScreen->btnCallback(key, down); 515 | } 516 | else 517 | { 518 | Serial.printf("no btn cb!\n"); 519 | } 520 | } 521 | break; 522 | } 523 | } 524 | } 525 | 526 | static bool updateScreen = true; 527 | 528 | void Screen_Update() 529 | { 530 | updateScreen = true; 531 | } 532 | 533 | void Screen_Loop() 534 | { 535 | if (updateScreen) 536 | { 537 | currentScreen->enter(); 538 | updateScreen = false; 539 | } 540 | } 541 | 542 | #endif 543 | 544 | -------------------------------------------------------------------------------- /sdcard/samples/readme.txt: -------------------------------------------------------------------------------- 1 | you can store samples / wav files on the sdcard. 2 | sd should be formatted as FAT32 3 | create a folder called "samples" and put the files there -------------------------------------------------------------------------------- /sine.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Marcel Licence 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * Dieses Programm ist Freie Software: Sie können es unter den Bedingungen 18 | * der GNU General Public License, wie von der Free Software Foundation, 19 | * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren 20 | * veröffentlichten Version, weiter verteilen und/oder modifizieren. 21 | * 22 | * Dieses Programm wird in der Hoffnung bereitgestellt, dass es nützlich sein wird, jedoch 23 | * OHNE JEDE GEWÄHR,; sogar ohne die implizite 24 | * Gewähr der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. 25 | * Siehe die GNU General Public License für weitere Einzelheiten. 26 | * 27 | * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem 28 | * Programm erhalten haben. Wenn nicht, siehe . 29 | */ 30 | 31 | /* 32 | * this file is used to pre-calculate a sine 33 | * sine is very slow and this can be used to speed up the execution 34 | * 35 | * Author: Marcel Licence 36 | */ 37 | 38 | #define SINE_BIT 8UL 39 | #define SINE_CNT (1<> (32 - SINE_BIT)) /* & SINE_MSK */ 42 | 43 | 44 | float *sine = NULL; 45 | 46 | void Sine_Init(void) 47 | { 48 | uint32_t memSize = sizeof(float) * SINE_CNT; 49 | sine = (float *)malloc(memSize); 50 | if (sine == NULL) 51 | { 52 | Serial.printf("not enough heap memory for sine %d buffer!\n", memSize); 53 | } 54 | for (int i = 0; i < SINE_CNT; i++) 55 | { 56 | sine[i] = (float)sin(i * 2.0 * PI / SINE_CNT); 57 | } 58 | } 59 | 60 | /* 61 | * returns sin(value * 2.0f * PI) from lookup 62 | */ 63 | float SineNorm(float alpha_div2pi) 64 | { 65 | uint32_t index = ((uint32_t)(alpha_div2pi * ((float)SINE_CNT))) % SINE_CNT; 66 | return sine[index]; 67 | } 68 | 69 | float SineNormU32(uint32_t pos) 70 | { 71 | return sine[SINE_I(pos)]; 72 | } 73 | -------------------------------------------------------------------------------- /status_module.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Marcel Licence 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * Dieses Programm ist Freie Software: Sie können es unter den Bedingungen 18 | * der GNU General Public License, wie von der Free Software Foundation, 19 | * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren 20 | * veröffentlichten Version, weiter verteilen und/oder modifizieren. 21 | * 22 | * Dieses Programm wird in der Hoffnung bereitgestellt, dass es nützlich sein wird, jedoch 23 | * OHNE JEDE GEWÄHR,; sogar ohne die implizite 24 | * Gewähr der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. 25 | * Siehe die GNU General Public License für weitere Einzelheiten. 26 | * 27 | * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem 28 | * Programm erhalten haben. Wenn nicht, siehe . 29 | */ 30 | 31 | /* 32 | * this file contains the implementation of the terminal output 33 | * the output is vt100 compatible and you should use a terminal like teraTerm 34 | * 35 | * source: https://ttssh2.osdn.jp/index.html.en​ 36 | * 37 | * Author: Marcel Licence 38 | */ 39 | 40 | bool triggerTerminalOutput = true; /*!< necessary for usage without vt100 compliant terminal */ 41 | 42 | char statusMsg[128] = "\0"; /*!< buffer for top line message */ 43 | char currentFile[128] = "\0"; /*!< buffer for filename */ 44 | 45 | uint32_t statusMsgShowTimer = 0; /*!< counter for timeout to reset top line */ 46 | 47 | uint32_t currentFileShowTimer = 0; 48 | 49 | 50 | #define VU_MAX 24 /*!< character length of vu meter */ 51 | 52 | float statusVuLookup[VU_MAX]; /*!< precalculated lookup */ 53 | 54 | /* 55 | * prepares the module 56 | */ 57 | void Status_Setup(void) 58 | { 59 | /* 60 | * prepare lookup for log vu meters 61 | */ 62 | float vuValue = 1.0f; 63 | for (int i = VU_MAX; i > 0; i--) 64 | { 65 | int n = i - 1; 66 | 67 | statusVuLookup[n] = vuValue; 68 | 69 | vuValue *= 1.0f / sqrt(2.0f); /* div by 1/sqrt(2) means 3db */ 70 | vuValue *= 1.0f / sqrt(2.0f); 71 | } 72 | } 73 | 74 | /* 75 | * function which prints the vu meter "line" 76 | */ 77 | void Status_PrintVu(float *value, uint8_t vuMax) 78 | { 79 | /* 80 | * first pick a color 81 | */ 82 | if (*value >= 0.7071f) /* -3dB */ 83 | { 84 | Serial.printf("\033[0;31m"); /* red */ 85 | } 86 | else if (*value >= 0.5) /* -6dB */ 87 | { 88 | Serial.printf("\033[0;33m"); /* yellow */ 89 | } 90 | else 91 | { 92 | Serial.printf("\033[0;32m"); /* green */ 93 | } 94 | 95 | for (int i = 0; i < vuMax; i++) 96 | { 97 | 98 | if (statusVuLookup[i + (VU_MAX - vuMax)] <= *value) 99 | { 100 | Serial.printf("#"); 101 | } 102 | else 103 | { 104 | Serial.printf(" "); 105 | } 106 | } 107 | 108 | Serial.printf("\033[0m"); /* switch back to default */ 109 | 110 | *value *= 0.5; /* slowly lower the value */ 111 | } 112 | 113 | 114 | /* 115 | * refresh complete output 116 | * 32 character width 117 | * 14 character height 118 | */ 119 | void Status_PrintAll(void) 120 | { 121 | char emptyLine[] = " "; 122 | #if 0 /* not used */ 123 | char emptyLineMin[] = " "; 124 | #endif 125 | char emptyLineLong[] = " "; 126 | Serial.printf("\033[?25l"); 127 | Serial.printf("\033[%d;%dH", 0, 0); 128 | //Serial.printf("--------------------------------\n"); 129 | Serial.printf("%s%s\n", statusMsg, &emptyLine[strlen(statusMsg)]); 130 | Serial.printf("--------------------------------\n"); 131 | 132 | Serial.printf("inL "); 133 | Status_PrintVu(vuInL, 8); 134 | 135 | Serial.printf(" outL "); 136 | Status_PrintVu(vuOutL, 8); 137 | Serial.printf("\n"); 138 | 139 | Serial.printf("inR "); 140 | Status_PrintVu(vuInR, 8); 141 | Serial.printf(" outR "); 142 | Status_PrintVu(vuOutR, 8); 143 | Serial.printf("\n"); 144 | 145 | 146 | Serial.printf("--------------------------------\n"); 147 | char memoryUsedMsg[64]; 148 | sprintf(memoryUsedMsg, "%d of %d bytes used", sampleStorageInPos, sampleStorageLen); 149 | Serial.printf("%s%s\n", memoryUsedMsg, &emptyLine[strlen(memoryUsedMsg)]); 150 | 151 | float relativeStorageUsage = ((float)sampleStorageInPos) / ((float)sampleStorageLen); 152 | for (int i = 0; i < 32; i++) 153 | { 154 | if (i == (int)(relativeStorageUsage * 32.0f)) 155 | { 156 | Serial.printf("O"); 157 | } 158 | else 159 | { 160 | if (i > (int)(relativeStorageUsage * 32.0f)) 161 | { 162 | Serial.printf("_"); 163 | } 164 | else 165 | { 166 | Serial.printf("="); 167 | } 168 | } 169 | } 170 | Serial.println(); 171 | 172 | Serial.printf("%s%s%s", emptyLine, currentFile, &emptyLineLong[strlen(currentFile)]); 173 | 174 | #ifdef STATUS_SHOW_BUTTON_TEXT 175 | Status_ShowButtonText(); 176 | #endif 177 | } 178 | 179 | uint8_t activeKey = 9; 180 | 181 | static char key[6][9] = {"Key1 ", "Key2 ", "Key3 ", "Key4 ", "Key5 ", "Key6 "}; 182 | 183 | void Status_ShowButtonText() 184 | { 185 | 186 | 187 | Serial.printf("\n--------------------------------\n"); 188 | Serial.printf("%s %s %s #####\n", key[0], key[2], key[4]); 189 | Serial.printf("#### %s %s %s \n", key[1], key[3], key[5]); 190 | } 191 | 192 | void Status_SetKeyText(uint8_t keyId, const char *text) 193 | { 194 | memset(key[keyId], ' ', 8); 195 | memcpy(key[keyId], text, strlen(text)); /* just copy text without trailing zero */ 196 | } 197 | 198 | void Status_Process_Sample(uint32_t inc) 199 | { 200 | statusMsgShowTimer += inc; 201 | if (statusMsgShowTimer > SAMPLE_RATE * 3) 202 | { 203 | statusMsg[0] = 0; 204 | } 205 | 206 | currentFileShowTimer += inc; 207 | if (currentFileShowTimer > SAMPLE_RATE * 3) 208 | { 209 | currentFile[0] = 0; 210 | } 211 | } 212 | 213 | void Status_Process(void) 214 | { 215 | #ifndef VT100_ENABLED 216 | if (triggerTerminalOutput) 217 | #endif 218 | { 219 | Status_PrintAll(); 220 | triggerTerminalOutput = false; 221 | } 222 | 223 | #ifndef VT100_ENABLED 224 | if (triggerTerminalOutput) 225 | #endif 226 | { 227 | Status_PrintAll(); 228 | triggerTerminalOutput = false; 229 | } 230 | } 231 | 232 | /* 233 | * update top line message including a float value 234 | */ 235 | void Status_ValueChangedFloat(const char *group, const char *descr, float value) 236 | { 237 | statusMsgShowTimer = 0; 238 | sprintf(statusMsg, "%s - %s: %0.3f", group, descr, value); 239 | #ifndef AS5600_ENABLED 240 | triggerTerminalOutput = true; 241 | #ifdef SCREEN_ENABLED 242 | Screen_Update(); 243 | #endif 244 | #endif 245 | } 246 | 247 | void Status_ValueChangedFloat(const char *descr, float value) 248 | { 249 | statusMsgShowTimer = 0; 250 | sprintf(statusMsg, "%s: %0.3f", descr, value); 251 | #ifndef AS5600_ENABLED 252 | triggerTerminalOutput = true; 253 | #ifdef SCREEN_ENABLED 254 | Screen_Update(); 255 | #endif 256 | #endif 257 | } 258 | 259 | /* 260 | * update top line message including an integer value 261 | */ 262 | void Status_ValueChangedInt(const char *group, const char *descr, int value) 263 | { 264 | statusMsgShowTimer = 0; 265 | sprintf(statusMsg, "%s - %s: %d", group, descr, value); 266 | #ifndef AS5600_ENABLED 267 | triggerTerminalOutput = true; 268 | #ifdef SCREEN_ENABLED 269 | Screen_Update(); 270 | #endif 271 | #endif 272 | } 273 | 274 | void Status_ValueChangedInt(const char *descr, int value) 275 | { 276 | statusMsgShowTimer = 0; 277 | sprintf(statusMsg, "%s: %d", descr, value); 278 | #ifndef AS5600_ENABLED 279 | triggerTerminalOutput = true; 280 | #ifdef SCREEN_ENABLED 281 | Screen_Update(); 282 | #endif 283 | #endif 284 | } 285 | 286 | /* 287 | * update top line message 288 | */ 289 | void Status_TestMsg(const char *text) 290 | { 291 | statusMsgShowTimer = 0; 292 | sprintf(statusMsg, "%s", text); 293 | triggerTerminalOutput = true; 294 | } 295 | 296 | void Status_LogMessage(const char *text) 297 | { 298 | statusMsgShowTimer = 0; 299 | sprintf(statusMsg, "%s", text); 300 | triggerTerminalOutput = true; 301 | } 302 | 303 | void Status_FileName(char *text) 304 | { 305 | memset(currentFile, 0, sizeof(currentFile)); 306 | strcpy(currentFile, text); 307 | currentFileShowTimer = 0; 308 | } 309 | -------------------------------------------------------------------------------- /targets.csv: -------------------------------------------------------------------------------- 1 | fqbn;version;config 2 | #espressif:esp32:esp32;2.0.2;PSRAM=enabled\nPARTITION_SCHEME=default 3 | #espressif:esp32:esp32;1.0.6;PSRAM=enabled\nPARTITION_SCHEME=default 4 | espressif:esp32:esp32;2.0.13;PSRAM=disabled\nPARTITION_SCHEME=default -------------------------------------------------------------------------------- /vu_meter_module.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 Marcel Licence 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * Dieses Programm ist Freie Software: Sie können es unter den Bedingungen 18 | * der GNU General Public License, wie von der Free Software Foundation, 19 | * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren 20 | * veröffentlichten Version, weiter verteilen und/oder modifizieren. 21 | * 22 | * Dieses Programm wird in der Hoffnung bereitgestellt, dass es nützlich sein wird, jedoch 23 | * OHNE JEDE GEWÄHR,; sogar ohne die implizite 24 | * Gewähr der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. 25 | * Siehe die GNU General Public License für weitere Einzelheiten. 26 | * 27 | * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem 28 | * Programm erhalten haben. Wenn nicht, siehe . 29 | */ 30 | 31 | /** 32 | * @file vu_meter_module.ino 33 | * @author Marcel Licence 34 | * @date 25.04.2021 35 | * 36 | * @brief This module is only to calculate the vu-meters and drive the ws2812 8x8 led module​ 37 | * Credits for HSVtoRGB function is unknown 38 | */ 39 | 40 | 41 | #define VU_METER_COUNT 8 42 | 43 | #define VU_METER_DECREASE_MULTIPLIER 0.98f 44 | 45 | 46 | #ifdef LED_STRIP_PIN 47 | 48 | #include 49 | 50 | uint32_t brightness = 4; /* I am using a low value to keep the LED's dark */ 51 | 52 | /* pixel count of the 8x8 module */ 53 | #define NUMPIXELS (8*8) 54 | 55 | 56 | struct pixel_rgb 57 | { 58 | uint8_t r, g, b, a; 59 | }; 60 | 61 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, LED_STRIP_PIN, 62 | NEO_GRB + NEO_KHZ800); 63 | 64 | struct pixel_rgb pixels[NUMPIXELS] = {0}; 65 | #endif 66 | 67 | /* 2 storages to allow double buffering */ 68 | float vuMeterValueInBf[VU_METER_COUNT]; 69 | float vuMeterValueDisp[VU_METER_COUNT]; 70 | 71 | void VuMeterMatrix_Init(void) 72 | { 73 | #ifdef LED_STRIP_PIN 74 | strip.begin(); 75 | strip.show(); 76 | #endif 77 | VuMeterMatrix_Process(); 78 | } 79 | 80 | /* found this function somewhere in some repo. source cannot be identified anymore */ 81 | void HSVtoRGB(int hue, int sat, int val, uint8_t colors[3]) 82 | { 83 | // hue: 0-359, sat: 0-255, val (lightness): 0-255 84 | int r, g, b, base; 85 | if (sat == 0) 86 | { 87 | // Achromatic color (gray). 88 | colors[0] = val; 89 | colors[1] = val; 90 | colors[2] = val; 91 | } 92 | else 93 | { 94 | base = ((255 - sat) * val) >> 8; 95 | switch (hue / 60) 96 | { 97 | case 0: 98 | r = val; 99 | g = (((val - base) * hue) / 60) + base; 100 | b = base; 101 | break; 102 | case 1: 103 | r = (((val - base) * (60 - (hue % 60))) / 60) + base; 104 | g = val; 105 | b = base; 106 | break; 107 | case 2: 108 | r = base; 109 | g = val; 110 | b = (((val - base) * (hue % 60)) / 60) + base; 111 | break; 112 | case 3: 113 | r = base; 114 | g = (((val - base) * (60 - (hue % 60))) / 60) + base; 115 | b = val; 116 | break; 117 | case 4: 118 | r = (((val - base) * (hue % 60)) / 60) + base; 119 | g = base; 120 | b = val; 121 | break; 122 | case 5: 123 | r = val; 124 | g = base; 125 | b = (((val - base) * (60 - (hue % 60))) / 60) + base; 126 | break; 127 | default: 128 | r = 0; 129 | g = 0; 130 | b = 0; 131 | break; 132 | } 133 | colors[0] = r; 134 | colors[1] = g; 135 | colors[2] = b; 136 | } 137 | } 138 | 139 | void VuMeterMatrix_Process(void) 140 | { 141 | memcpy(vuMeterValueDisp, vuMeterValueInBf, sizeof(vuMeterValueDisp)); 142 | for (int i = 0; i < 8; i++) 143 | { 144 | vuMeterValueInBf[i] *= VU_METER_DECREASE_MULTIPLIER; 145 | } 146 | } 147 | 148 | void VuMeterMatrix_Display(void) 149 | { 150 | #ifdef LED_STRIP_PIN 151 | for (int i = 0; i < NUMPIXELS; i++) 152 | { 153 | int row = (i - (i % 8)) >> 3; 154 | int col = (i % 8); 155 | 156 | float dbVal = 0; 157 | if (vuMeterValueDisp[row] > 1.0f) 158 | { 159 | dbVal = 8.0f; 160 | } 161 | else if (vuMeterValueDisp[row] > 0.0f) /* log would crash if you put in zero */ 162 | { 163 | dbVal = 8 + (3.32f * log10(vuMeterValueDisp[row])); 164 | } 165 | 166 | if (dbVal > col) 167 | { 168 | uint8_t colors[3]; 169 | float hue = 120.0f - ((float)dbVal) * (120.0f / 8.0f); /* from green to red / min to max */ 170 | HSVtoRGB(hue, 255, brightness, colors); 171 | 172 | pixels[i].r = colors[0]; 173 | pixels[i].g = colors[1]; 174 | pixels[i].b = colors[2]; 175 | } 176 | else 177 | { 178 | uint8_t colors[3]; 179 | #if 0 /* may be useful when doing some color effects */ 180 | float hue = 240.0f + ((float)i) * (120.0f / 64.0f); 181 | #endif 182 | HSVtoRGB(240, 255, brightness / 4, colors); 183 | pixels[i].r = colors[0]; 184 | pixels[i].g = colors[1]; 185 | pixels[i].b = colors[2]; 186 | } 187 | 188 | strip.setPixelColor(i, pixels[i].r, pixels[i].g, pixels[i].b); 189 | } 190 | strip.show(); 191 | #endif 192 | } 193 | 194 | float *VuMeterMatrix_GetPtr(uint8_t index) 195 | { 196 | if (index >= VU_METER_COUNT) 197 | { 198 | return NULL; 199 | } 200 | return &vuMeterValueInBf[index]; 201 | } 202 | 203 | void VuMeterMatrix_SetBrighness(uint8_t unused, float value) 204 | { 205 | #ifdef LED_STRIP_PIN 206 | brightness = (value * 255.0f); 207 | #endif 208 | } 209 | 210 | -------------------------------------------------------------------------------- /z_config.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Marcel Licence 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * Dieses Programm ist Freie Software: Sie können es unter den Bedingungen 18 | * der GNU General Public License, wie von der Free Software Foundation, 19 | * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren 20 | * veröffentlichten Version, weiter verteilen und/oder modifizieren. 21 | * 22 | * Dieses Programm wird in der Hoffnung bereitgestellt, dass es nützlich sein wird, jedoch 23 | * OHNE JEDE GEWÄHR,; sogar ohne die implizite 24 | * Gewähr der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. 25 | * Siehe die GNU General Public License für weitere Einzelheiten. 26 | * 27 | * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem 28 | * Programm erhalten haben. Wenn nicht, siehe . 29 | */ 30 | 31 | /* 32 | * z_config.ino 33 | * 34 | * Put all your project configuration here (no defines etc) 35 | * This file will be included at the and can access all 36 | * declarations and type definitions 37 | * 38 | * Created on: 12.05.2021 39 | * Author: Marcel Licence 40 | */ 41 | 42 | #ifdef AUDIO_KIT_BUTTON_ANALOG 43 | #ifdef AS5600_ENABLED 44 | audioKitButtonCb audioKitButtonCallback = App_ButtonCbPlaySample; 45 | #else 46 | #ifdef SCREEN_ENABLED 47 | audioKitButtonCb audioKitButtonCallback = Screen_ButtonCallback; 48 | #else 49 | audioKitButtonCb audioKitButtonCallback = App_ButtonCb; 50 | #endif 51 | #endif 52 | #endif 53 | 54 | /* 55 | * this mapping is used for the edirol pcr-800 56 | * this should be changed when using another controller 57 | */ 58 | struct midiControllerMapping edirolMapping[] = 59 | { 60 | /* transport buttons */ 61 | { 0x8, 0x52, "back", NULL, Sampler_RecordWait, 0}, 62 | { 0xD, 0x52, "stop", NULL, Sampler_Stop, 0}, 63 | { 0xe, 0x52, "start", NULL, Sampler_Play, 0}, 64 | { 0xa, 0x52, "rec", NULL, Sampler_Record, 0}, 65 | 66 | /* upper row of buttons */ 67 | { 0x0, 0x50, "A1", NULL, Sampler_LoopUnlock, 0}, 68 | { 0x1, 0x50, "A2", NULL, Sampler_LoopAll, 1}, 69 | #ifdef AS5600_ENABLED 70 | { 0x2, 0x50, "A3", NULL, Sampler_ScratchFader, 2}, 71 | { 0x3, 0x50, "A4", NULL, Sampler_SetScratchSample, 0xFF}, 72 | #else 73 | #ifdef MIDI_STREAM_PLAYER_ENABLED 74 | { 0x2, 0x50, "A3", NULL, MidiStreamPlayerCtrl, MIDI_STREAM_PLAYER_CTRL_PAUSE}, 75 | { 0x3, 0x50, "A4", NULL, MidiStreamPlayerCtrl, MIDI_STREAM_PLAYER_CTRL_START}, 76 | #else 77 | { 0x2, 0x50, "A3", NULL, NULL, 2}, 78 | { 0x3, 0x50, "A4", NULL, NULL, 3}, 79 | #endif 80 | #endif 81 | 82 | { 0x4, 0x50, "A5", NULL, PatchManager_SetDestination, 0}, 83 | { 0x5, 0x50, "A6", NULL, PatchManager_SetDestination, 1}, 84 | { 0x6, 0x50, "A7", NULL, Sampler_RemoveActiveRecording, 2}, 85 | { 0x7, 0x50, "A8", NULL, Sampler_LoadPatch, 0}, 86 | 87 | { 0x0, 0x53, "A9", NULL, Sampler_SavePatch, 0}, 88 | 89 | /* lower row of buttons */ 90 | { 0x0, 0x51, "B1", NULL, Sampler_LoopLock, 0}, 91 | { 0x1, 0x51, "B2", NULL, Sampler_LoopRemove, 1}, 92 | { 0x2, 0x51, "B3", NULL, Sampler_Panic, 2}, 93 | { 0x3, 0x51, "B4", NULL, Sampler_NormalizeActiveRecording, 3}, 94 | 95 | { 0x4, 0x51, "B5", NULL, PatchManager_FileIdxInc, 0}, 96 | { 0x5, 0x51, "B6", NULL, PatchManager_FileIdxDec, 1}, 97 | { 0x6, 0x51, "B7", NULL, Sampler_Panic, 2}, 98 | { 0x7, 0x51, "B8", NULL, Sampler_MeasureThreshold, 3}, 99 | 100 | { 0x1, 0x53, "B9", NULL, App_ToggleSource, 0}, 101 | 102 | /* pedal */ 103 | { 0x0, 0x0b, "VolumePedal", NULL, NULL, 0}, 104 | 105 | /* slider */ 106 | { 0x0, 0x11, "S1", NULL, Sampler_SetADSR_Attack, 0}, 107 | { 0x1, 0x11, "S2", NULL, Sampler_SetADSR_Decay, 1}, 108 | { 0x2, 0x11, "S3", NULL, Sampler_SetADSR_Sustain, 2}, 109 | { 0x3, 0x11, "S4", NULL, Sampler_SetADSR_Release, 3}, 110 | 111 | { 0x4, 0x11, "S5", NULL, Delay_SetInputLevel, 0}, 112 | { 0x5, 0x11, "S6", NULL, Delay_SetFeedback, 0}, 113 | { 0x6, 0x11, "S7", NULL, Delay_SetOutputLevel, 0}, 114 | { 0x7, 0x11, "S8", NULL, Delay_SetLength, 0}, 115 | 116 | { 0x1, 0x12, "S9", NULL, App_SetOutputLevel, 0}, 117 | 118 | /* rotary */ 119 | { 0x0, 0x10, "R1", NULL, Sampler_LoopStartC, 0}, 120 | { 0x1, 0x10, "R2", NULL, Sampler_LoopStartF, 1}, 121 | { 0x2, 0x10, "R3", NULL, Sampler_LoopEndC, 2}, 122 | { 0x3, 0x10, "R4", NULL, Sampler_LoopEndF, 3}, 123 | 124 | { 0x4, 0x10, "R5", NULL, Sampler_SetLoopEndMultiplier, 0}, 125 | { 0x5, 0x10, "R6", NULL, Sampler_SetPitch, 0}, 126 | { 0x6, 0x10, "R7", NULL, Sampler_ModulationSpeed, 0}, 127 | { 0x7, 0x10, "R8", NULL, Sampler_ModulationPitch, 0}, 128 | 129 | { 0x0, 0x12, "R9", NULL, Reverb_SetLevel, 0}, 130 | 131 | /* Central slider */ 132 | #ifndef AS5600_ENABLED 133 | { 0x0, 0x13, "H1", NULL, App_SetBrightness, 0}, 134 | #else 135 | { 0x0, 0x13, "H1", NULL, Sampler_ScratchFader, 0}, 136 | #endif 137 | }; 138 | 139 | struct midiMapping_s midiMapping = 140 | { 141 | NULL, 142 | Sampler_NoteOn, 143 | Sampler_NoteOff, 144 | Sampler_PitchBend, 145 | Sampler_ModulationWheel, 146 | NULL, /* assign program change callback here! */ 147 | Synth_RealTimeMsg, 148 | Synth_SongPosition, 149 | edirolMapping, 150 | sizeof(edirolMapping) / sizeof(edirolMapping[0]), 151 | }; 152 | 153 | #ifdef MIDI_VIA_USB_ENABLED 154 | struct usbMidiMappingEntry_s usbMidiMappingEntries[] = 155 | { 156 | { 157 | NULL, 158 | App_UsbMidiShortMsgReceived, 159 | NULL, 160 | NULL, 161 | 0xFF, 162 | }, 163 | }; 164 | 165 | struct usbMidiMapping_s usbMidiMapping = 166 | { 167 | NULL, 168 | NULL, 169 | usbMidiMappingEntries, 170 | sizeof(usbMidiMappingEntries) / sizeof(usbMidiMappingEntries[0]), 171 | }; 172 | #endif /* MIDI_VIA_USB_ENABLED */ 173 | --------------------------------------------------------------------------------