diff --git a/docs/verification/2026-11-07-rytm-improved-envelopes.md b/docs/verification/2026-11-07-rytm-improved-envelopes.md new file mode 100644 index 000000000..a88b58801 --- /dev/null +++ b/docs/verification/2026-11-07-rytm-improved-envelopes.md @@ -0,0 +1,9 @@ +# Analog Rytm MKII: Improved Envelopes 0.1.0-experimental — 7 November 2026 + +Nathaniel Pavel's (@npp1993) Improved Envelopes enters the library (id `envelopes`). It runs AMP HLD from no hold to AUTO, gives ATK, DEC, REL and HLD fine steps at short times, and shows every envelope knob's time. ENV RESET AMP stays with Env Reset Amp, which installs beside it. Its source-only OS 1.74 package is pinned in `sdk/imports/elekloader-envelopes-2026-11-07.json` (commit `2c53a220`). + +- **Build:** `scripts/build-elemod-isolated.sh` in `modwerk-source-tools` (image `39efd52a…`) from a clean checkout of `ed0cac20`. Exported with `package-elemod-recipe.mjs`, which reproduces it with `--check`; `verify-elemod-release-exports.mjs` reproduces the 49 current exports. +- **Package:** `envelopes-0.1.0-experimental-os1.74.elemod` `3c5016ae…dc30`. Linked on the plain base `ba530f45`: ramSpare 113,776 alone (EN02, 5,228 bytes for the module) and beside Env Reset Amp 1.0.0 (EN03). +- **Beside Env Reset Amp:** no shared bytes; the HOLD rule holds after its drop record, read in OS 1.74's state machine (module `upstream/README.md`). +- **Emulator:** rytm-emu `2ac0f1f`, EN02 (the plain base + this package, `c8835018…`): boots; the AMP page's DEC and HLD read their times (`media/capture.json`). +- **On a unit:** the author's Analog Rytm MKII over USB, in combined sets of up to 16 modules (EV04 to ED02): AMP DEC within 0.7 % of its text from 5 ms, AMP HLD within 0.1 ms, FILTER ATK within 0.3 %, FILTER DEC and REL 1.5 % (method). As reported; details in the module's TESTING.md. This package: not yet run on a unit. diff --git a/sdk/analog-rytm-mkii/modules/envelopes/LICENSE b/sdk/analog-rytm-mkii/modules/envelopes/LICENSE new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/sdk/analog-rytm-mkii/modules/envelopes/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sdk/analog-rytm-mkii/modules/envelopes/README.md b/sdk/analog-rytm-mkii/modules/envelopes/README.md new file mode 100644 index 000000000..6ec5820e6 --- /dev/null +++ b/sdk/analog-rytm-mkii/modules/envelopes/README.md @@ -0,0 +1,114 @@ +# Improved Envelopes + +## Overview + +Module for the Analog Rytm MKII, OS 1.74. It reworks the AMP envelope and shows times on every +envelope knob: AMP HLD runs from no hold to AUTO without cutting the attack, ATK, DEC, REL and HLD get +fine steps at short times (0.2 ms up to 5 ms). It installs beside Env Reset Amp (ENV RESET AMP), which +it began from. + +## Controls + +| Control | Stock | With Envelopes | +|---|---|---| +| AMP HLD | 0 = AUTO, 1..127 = 15.6 ms .. 16 s from the trig, 7.8 ms steps to 250 ms | 0 = no hold, 1..126 = 0.67 ms steps to 10 ms (15), 1.3 ms to 18 ms (21), 3.3 ms to 51 ms (31), 6.7 ms to 151 ms (46), 13 ms to 298 ms (57), then about 6 % a step to 15.5 s, 127 = AUTO. The decay never starts before the attack reaches the top. 0.67 ms is a block, the OS's smallest hold step | +| AMP DEC, FILTER DEC, FILTER REL | stock's curve: 7.9 ms at 1, about 8 ms steps at the low end, 300 ms at 23, 45 s at 127 | 0 instant, 0.2 ms steps to 5 ms (25), 0.5 ms to 15 ms (45), 1 ms to 25 ms (55), 2.5 ms to 50 ms (65), 5 ms to 100 ms (75), 10 ms to 200 ms (85), 25 ms to 300 ms (89), then about 14 % a step to 39.4 s (AMP 126) or 45 s (FILTER 127). AMP DEC 127 stays INF | +| AMP ATK | 0 instant, 1 = 5.3 ms, about 5 ms steps at the low end, 127 = 30 s | 0 instant, 0.2 ms steps to 5 ms (25), 0.5 ms steps to 15 ms (45), then about 10 % a step to 30 s (measured on the unit) | +| FILTER ATK | stock's curve (as AMP's) | the same new curve as AMP ATK, built for the filter's speed measured on the unit (1.00x the OS model); played on the unit in EV14, 7.5..384 ms within 0.3 % of target | +| FUNC + turn on HLD | jumps to AUTO | steps 0, 124.7 ms (a 16th at 120 BPM, HLD 42), AUTO | +| AMP ATK, HLD, DEC, FILTER ATK, DEC, REL | 0..127 | times: an attack's full ramp, a hold's length, a decay's or release's time to -60 dB; AMP DEC 127 = INF | + +## Usage + +Shape short, clicky transients and tight holds by ear and by number: every envelope knob reads its +time, and the short end has the resolution drums need. + +### Quick tutorial + +1. Press AMP. Turn DEC down: the knob shows its time to -60 dB, in 0.2 ms steps below 5 ms. +2. Turn HLD to 0: the decay starts as soon as the attack peaks. Turn it up for a hold in ms, or to the top for AUTO (decay from note-off). +3. Hold FUNC and turn HLD: it jumps between 0, 125 ms and AUTO. +4. On the FILTER page, ATK, DEC and REL show their times on the same fine curves. + +## How it works + +- `src/amp-write.s` takes the amp record write in `amp_env_commands` (0x40109c24, every voice every + block) and calls `envelopes_voice`, except for a voice whose record repeats the last one it took when + that one changed nothing (an idle voice, a decay at its end): an idle unit makes no C call there. +- `envelopes_voice` (`src/envelopes.c`) follows each voice's amp level from the records sent (the level + lives in the voice hardware) and sets the state stock moves to: after a trig 1 (gate, AUTO) or 2 + (timed); in the timed state it ends the hold when the hold time has passed and the level is at the + top. A one-pole decay is followed slightly fast, so the attack is never thought done early. +- ATK's rates come from `atk_rate` (`tools/atk_table.py`, 16-bit packed): `envelopes_voice` writes + them into the attack's records (the trig's and each held block's) as they are sent. +- FILTER ATK's rates come from `envelopes_flt_atk` (same script, 32-bit words): a `ptr` site in + `build.json` points stock's lea at 0x4010d2a8 (the filter attack table, read for the attack record + and the state counter alike) at it. +- DEC, REL and HLD: `tools/dec_table.py` builds three tables (`envelopes_amp_dec`, `envelopes_flt_dec`, + `envelopes_hold`, 128 words each) and `ptr` sites in `build.json` point stock's leas at them: the amp + decay record (0x40109bee), the filter decay and release records (0x4010d342, 0x4010d3b2, 0x4010d3da) + and the hold compare (0x40109bc2). Stock's tables stay where they are, unread by the OS. The AMP table is built + for the VCA's 0.918x decay speed, the filter's for 1.00x. The hold counter moves a block (16 ticks, + 0.67 ms) at a time, so every hold is a whole number of blocks. +- Times on the knobs come from core-ar2's `core_params` text for ids 50..58; HLD's FUNC jump is its + parameter record's jump function (id 57, +0x4c/+0x50). + +## Changes to stock flows + +| Change | For whom | Why | Neighbouring flows | +| --- | --- | --- | --- | +| AMP HLD 0 is no hold and 127 is AUTO (stock: 0 is AUTO, 127 is 16 s); HLD, ATK, DEC and REL follow new curves. | Anyone with the module installed, including projects made on stock. | A hold that starts at zero and grows with the knob, and resolution at the short end, need the knob's values to mean new times. | Saved sounds and projects play their stored bytes on the new curves (no migration); MIDI CC follows the knob. | +| FUNC + turn on HLD steps 0, 124.7 ms, AUTO (stock: jumps to AUTO). | Anyone who uses the FUNC jump on HLD. | Quick access to the three most used holds. | Other knobs' FUNC jumps are unchanged. | + +Saved values keep their bytes and are read the new way: a project made on stock plays HLD 0 as no hold +(was AUTO) and HLD 127 as AUTO (was 16 s), HLD 1..126, DEC and REL on the new curves (the same byte is a different time, mostly shorter at the low end), and AMP and FILTER ATK on the new curve. MIDI CC 79 (HLD) and the ATK CC +follow the knob. There is no migration. + +The ramps stay as stock makes them: the attack is a straight line in amplitude, decay and release are +exponential (straight lines in dB). A choke or stop still fades over stock's fixed ~43 ms. + +## Compatibility and limitations + +It patches the amp record write in `amp_env_commands` (0x40109c24) and points stock's envelope table +reads at its own tables (`ptr` sites, listed under How it works), and subscribes to `ev_tick`. Claims: +FUNC + turn on AMP HLD, the value text of AMP ATK/HLD/DEC and FILTER ATK/DEC/REL, +the AMP page picture and the amp and filter envelope tables. It needs no other module. + +- AMP ATK and DEC times are calibrated to one Rytm MKII (EV06, 6 Nov 2026): its VCA ran attacks at + 0.957x and decays at 0.918x the OS's timing model, so the ATK table is built for 0.957 and the + AMP DEC text is scaled by 0.918. The filter envelope, measured through the self-oscillating + filter's pitch, runs at the OS model's speed (attack 3 ms..390 ms, decay and release 47 ms..1.2 s + time constants), so FILTER times need no factor; attacks under 3 ms were not resolved. + Another unit may differ slightly. HLD is counted by the OS and matched within 2 ms. +- AMP DEC under about 5 ms is blurred by the VCA's own slew (~0.8 ms): DEC 1..8 all measure 1-2 ms. + FILTER DEC and REL under 15 ms were not resolved on the unit (TESTING.md). +- A module that times a note by AMP DEC's table must read the table address the OS code holds at + 0x40109bf0, not stock's 0x401ee61c: with this module that address points at the new table. +- On stock firmware a sound saved here plays the stock way (HLD 0 = AUTO, the old ATK curve). +- Beside Env Reset Amp: with AMP in a track's ENV RESET, that module replaces the trig's attack + record with a drop to silence. Envelopes then keeps its HOLD rule from the next block's attack + record (read in the OS code: [upstream/README.md](upstream/README.md#beside-env-reset-amp)); the + pair has not yet run together on a unit. +- Unit results: TESTING.md. + +## Tests and measurements + +Measured on the author's Analog Rytm MKII over USB (builds EV04 to ED02, in combined sets of up to 16 +modules), with rytm-emu for the screens ([TESTING.md](TESTING.md)): AMP DEC within 0.7 % of its text +from 5 ms, AMP HLD within 0.1 ms, FILTER ATK within 0.3 %, FILTER DEC and REL 1.5 % (the method's). +Memory: 5,228 bytes of the shared mod area (the plain base alone leaves 119,004, with this module 113,776). +Load: the amp record write runs every block for every voice; an idle voice makes no C call there. + +## Authorship and licences + +Nathaniel Pavel (@npp1993): Envelopes, the curves and the knob times. MrScruff and Iced-Cream: Env +Reset Amp (rytm-mods 0015 and its elemod port), whose reading of the amp envelope's commands this +module started from; none of its code is carried. irpina: Elekloader and core-ar2. GPL-2.0-only +([LICENSE](LICENSE)). The source names firmware addresses only; no Elektron firmware is included. + +## Screens and audio + +Screenshots are rytm-emu captures of build EN02, this package linked alone on the Modwerk base +([media/capture.json](media/capture.json)). No audio preview. + +![AMP DEC reading 2.2ms](media/decay-time.png) ![AMP HLD reading 4.0ms](media/hold-time.png) diff --git a/sdk/analog-rytm-mkii/modules/envelopes/TESTING.md b/sdk/analog-rytm-mkii/modules/envelopes/TESTING.md new file mode 100644 index 000000000..d5d9efe38 --- /dev/null +++ b/sdk/analog-rytm-mkii/modules/envelopes/TESTING.md @@ -0,0 +1,214 @@ +# Improved Envelopes: testing + +## Unit, EV15 / ED02 (DEC, REL and HLD curves), 6 Nov 2026 + +Build ED02 (unit pass): RC31's set and no-send base b530a147 with Improved Envelopes (head de6be7d02: +idle skip fcdf99a0, the tables from `tools/dec_table.py`, HLD texts in whole ms below 2 s). sha256 6060d48f640ae90ed2306a0cbbfff7dca8b9a275e6636e3fa70fb830fba55f81, +lock RC31's NS01 (unchanged), ramSpare 15,708. Independent review of ED01: no bugs; its HLD text finding +(79/80 and 81/82 read alike) is fixed in ED02. rytm-emu 2ac0f1f boots it ("cpu exceptions: 32:..." +only) and the five `ptr` sites hold the tables' addresses. + +On the unit (track 1 as it is, the looped sine; MIDI CC 78-80 from the rig, takes recorded from MAIN OUT +over USB; envelope read at 0.083 ms steps, -3 dB to -23 dB slope, t60 = 60 dB x ms per dB): + +| AMP DEC | design t60 | measured | +|---|---|---| +| 1 / 2 / 3 / 5 / 8 | 0.2 / 0.4 / 0.6 / 1.0 / 1.6 ms | 1.0 / 1.0 / 1.25 / 1.5 / 1.75 ms (the VCA's slew floors the shortest) | +| 12 / 15 / 20 | 2.4 / 3.0 / 4.0 ms | 2.5 / 3.25 / 4.25 ms | +| 25 / 30 / 35 / 45 / 55 | 5 / 7.5 / 10 / 15 / 25 ms | 5.0 / 7.5 / 10.0 / 15.0 / 25.0 ms | +| 65 / 75 / 85 / 89 | 50 / 100 / 200 / 300 ms | 50.3 / 100.8 / 201.3 / 301.5 ms | +| 95 / 100 / 105 / 110 | 0.66 / 1.28 / 2.47 / 4.78 s | 0.665 / 1.284 / 2.476 / 4.763 s | +| 113 / 115 / 117 / 120 | 7.10 / 9.25 / 12.04 / 17.88 s | 7.04 / 9.19 / 12.00 / 17.69 s | + +From 5 ms up the AMP decay is within 0.7 % of the text; below 5 ms the VCA slew (~0.8 ms) sets the floor, +so DEC 1..8 differ by less than a millisecond. + +| AMP HLD (ATK 0, DEC 25) | design | measured decay start, minus HLD 0 (3.3 ms) | +|---|---|---| +| 1 / 2 / 3 / 5 / 8 / 10 / 15 | 0.67 / 1.33 / 2.0 / 3.33 / 5.33 / 6.67 / 10.0 ms | 0.58 / 1.33 / 1.92 / 3.25 / 5.25 / 6.58 / 9.92 ms | +| 21 / 31 / 42 / 46 / 57 | 18.0 / 51.3 / 124.7 / 151.3 / 298 ms | 17.9 / 51.25 / 124.6 / 151.2 / 297.9 ms | +| 70 / 85 / 90 / 100 | 627 / 1481 / 1972 / 3497 ms | 627.3 / 1481.2 / 1972.0 / 3496.7 ms | +| 110 / 126 | 6.20 / 15.5 s | 6.20 / 15.50 s | + +FILTER DEC and REL (self-oscillating tone, FRQ 86, ENV +6, level through the map of a linear ATK 64, +log-linear fit from 80 % to 10 %, two takes each): time constant against the text's t60 / ln 1000. + +| k | DEC | REL | +|---|---|---| +| 45 / 55 / 65 (15 / 25 / 50 ms) | 1.017 / 1.016 / 1.015 | not measurable (too short to track) | +| 75 / 89 / 100 (100 ms / 300 ms / 1.28 s) | 1.014 / 1.015 / 1.012 | 1.015 / 1.015 / 1.015 | + +DEC 30 (7.5 ms) was too short to track. The 1.5 % is the method's, as on EV14. + +Some takes of the HLD sweep came back with a one-off USB capture gap (a stretch of digital silence); +those were repeated and the clean take is the one above. + +On the unit's screen (ED02, the author): AMP HLD 90 reads 1972ms and fits the field. + +Not tested: FILTER DEC/REL below 15 ms and AMP DEC below 5 ms to better than the VCA's slew, the other knob texts on the unit's screen (host-checked: all 128 values of each knob distinct, at most 6 characters), FUNC jump to HLD 42, saving and reloading. + +## Idle CPU, LR03 / LP01 (6 November 2026) + +The amp writer called `envelopes_voice` for every voice every block. Now (8028baf2) a voice skips the C call when its rate and level words equal the last +ones it took and that call left the level follower as it was. The follower is a pure function of its +state and the record outside the attack words, so the skipped call would have changed nothing. Stock +(read in amp_env_commands) sends rate 0 / level 0 to an idle voice every block, and the same one-pole +record in the decay state, which a voice stays in until its next trig. + +- Measured on the author's MKII with the private lag-telemetry base, RC31's set, scratch project, + sequencer stopped, no USB audio, 50 one-second readings: LR01 (RC31) 70.84 % of the audio block, + LR03 (RC31 with this source) 70.37 %. Pad mash (LR02, with two other idle changes): 81.7 % against + 82.7 %, ~10 % fewer presses. +- Independent code review: no findings (registers, ColdFire encodings, skip equivalence). +- rytm-emu: LP01 boots, TRAP #0 only. Rig re-run on the unit: pending. + +## Unit, EV14 (current), 6 Nov 2026 + +Build EV14: EV13's set with Improved Envelopes at 4efb6fbe (FILTER ATK table for the filter's 1.00x +speed, FILTER text unscaled; 18a15a2e adds only docs); sha256 +61163e6089ca427505e2c25fcd3401e13dd828a23094cd3d5be856bf5d83cc9f, lock NS01, ramSpare 17,788. +Flashed by the author. FILTER ATK by the ~1.3 kHz method of the EV13 section (FRQ 86, ENV +6, ends +extrapolated, four takes each; mean / spread): + +| FILTER ATK | Target | Measured | Ratio | +|---|---|---|---| +| 10 / 15 / 20 / 25 | 2 / 3 / 4 / 5 ms | 1.88 / 3.01 / 3.83 / 4.87 ms (spread 0.04-0.21) | 0.94 / 1.00 / 0.96 / 0.97 | +| 30 / 35 / 40 / 45 | 7.5 / 10 / 12.5 / 15 ms | 7.50 / 9.99 / 12.50 / 15.01 ms (spread <= 0.008) | 1.000 | +| 50 / 55 / 64 / 72 / 80 | 23.8 / 37.9 / 87.2 / 183 / 384 ms | 23.86 / 37.96 / 87.40 / 183.46 / 385.03 ms | 1.001-1.003 | + +Below 3 ms one tone cycle (~0.5 ms) is a large part of the ramp, so 2 ms and shorter are within the +method's error. Everything else of EV13 carries over (AMP code unchanged). + +## Unit, EV13, 6 Nov 2026 + +Build EV13: EV09's set and base (no-send b530a147, lock NS01) with Improved Envelopes at a6df46ff +(FILTER ATK table, rounded knob times); sha256 +6c7598110742557956310a49906aa44d20978273f31d66a394eb8fa4de9c3cbb, ramSpare 17,708. Flashed by the +author; scratch project, track 1 a looped sine. MAIN OUT over USB audio, settings by USB MIDI CC. + +EV13 and EV14 also carried ENV RESET AMP with a pre-fade; it left this module before 0.1.0 (Env Reset +Amp keeps ENV RESET AMP), so those runs are not recorded here. + +**FILTER ATK on the new table** (method as EV09 below: LP2, RES 127, FRQ 40, LEV 0, ENV +24, AMP ATK 0 +/ HLD AUTO / DEC INF, FILTER SUS 127 / DEC 0, single MIDI notes; rise start to top, by zero crossings). +Target = the curve; this build's table carried the 0.984 speed (superseded below). + +| ATK | Target | Measured | Resolution (two periods) | +|---|---|---|---| +| 64 / 72 / 80 / 96 | 87.2 / 183 / 384 / 1691 ms | 88.7 / 183.7 / 381.9 / 1710 ms | 4-7 ms | +| 45 / 55 | 15 / 37.9 ms | 16.1 / 42.3 ms | 4-5 ms | +| 1 / 5 / 10 / 25 / 35 | 0.2 / 1 / 2 / 5 / 10 ms | 1.6 / 1.9 / 1.5 / 11.3 / 11.5 ms | 1.5-5 ms | + +The long end matches within 2 %. Below 15 ms the filter tone is too low for this method to time the +ramp (a few cycles across it), so short FILTER ATK times were not measured this way (see the ~1.3 kHz runs below). A run at FRQ 70 put the top +of the sweep past the tracker's 12 kHz range and was discarded. + +**Filter envelope re-measured at ~1.3 kHz** (same EV13 unit, same day). The low tone above (139 Hz, +7 ms a cycle) blurs short segments, and timing the rise between thresholds cut its ends off. Here: +FRQ 86, ENV +6 (a 12-semitone sweep from ~1.3 kHz), each segment's ends extrapolated from line fits +over 10-30 % and 70-90 % of the sweep, four takes per attack value, two per decay/release value +(medians; three single takes where a fit failed are left out). + +| FILTER ATK | Target (curve) | Median measured | Ratio | +|---|---|---|---| +| 15 / 20 / 25 | 3 / 4 / 5 ms | 2.96 / 3.90 / 4.94 ms | 0.99 / 0.97 / 0.99 | +| 30 / 35 / 40 / 45 | 7.5 / 10 / 12.5 / 15 ms | 7.61 / 10.15 / 12.71 / 15.25 ms | 1.015-1.017 | +| 50 / 55 / 64 / 72 / 80 | 23.8 / 37.9 / 87.2 / 183 / 384 ms | 24.26 / 38.57 / 88.82 / 186.42 / 391.19 ms | 1.017-1.019 (spread <= 0.06 ms) | +| 5 / 10 | 1 / 2 ms | 0.82 / 1.66 ms | below the method's ~0.5 ms cycle | + +The table then carried 0.984, so the filter ran at 0.984 x 1.018 = 1.00x the OS model. + +| FILTER (time constant) | 24 | 40 | 56 | 72 | 88 | +|---|---|---|---|---|---| +| DEC, model ms | 47.2 | 120.7 | 268.5 | 566.0 | 1164.4 | +| DEC, measured / model | 1.013 | 1.011 | 1.008* | 1.002 | 0.987 | +| REL, measured / model | 1.014 | 1.016* | 1.015* | 1.013 | 1.014 | + +(* one of two takes; the other's fit failed.) Level read back through a linear attack's pitch map, +log-linear fit between 80 % and 10 %. So the filter envelope runs at the model's speed: 4efb6fbe +builds the FILTER ATK table for 1.0 and shows FILTER times unscaled (EV14). EV14's table is not yet +played on the unit. + +Not tested on the unit in EV13: knob texts (rytm-emu: FILTER ATK reads 1.0ms, 4.2ms, 11.0ms, 16.5ms, +2.2s on the curve), FUNC jumps, choke, saving and reloading, CPU cost. + +## Unit, EV09, 6 Nov 2026 + +Build EV09: RC30's set with envelopes in place of env-reset-amp, on the private no-send base b530a147, +lock NS01; sha256 6cccc5e5c91bd24e89c1220ecddef3c569386aed9b44e9f3d79c590999c3f908 (source b6727c48; +the display-name commit 0764febb is in it). Flashed by the author; track 1 as the author set it (a +looped ~1 kHz sine), only AMP ATK/HLD/DEC sent over USB MIDI (CC 78/79/80), MAIN OUT over USB audio. + +| Check | Target | Measured | +|---|---|---| +| ATK peak 10 / 25 / 35 / 45 / 55 / 64 / 72 / 80 | 2 / 5 / 10 / 15 / 37.9 / 87.2 / 183 / 384 ms | 2.5 / 5.0 / 10.25 / 15.5 / 38.25 / 87.5 / 183.5 / 383.75 ms | +| ATK 0 / 1 / 5 (0 / 0.2 / 1 ms) | | 0.75 / 1.5 / 1.25 ms (the VCA's own slew) | +| Decay start HLD 0 / 1 / 7 / 15 / 31 / 47 | 0 / 15.6 / 62.5 / 125 / 250 / 500 ms | 2.5 / 17.5 / 64.2 / 126.8 / 251.5 / 501.5 ms | +| AUTO, note 100 / 300 ms | 100 / 300 ms | 102.5 / 301.5 ms | +| Never cut, ATK 87 ms: HLD 0 / 2 / 7 (62.5 ms) | peak at 87 ms | 87.5 / 87.5 / 87.5-87.8 ms (HLD 7 re-run 3x: one sweep take had a late USB note) | +| Second MIDI note on a decaying voice (ATK 64/72, DEC 100/110) | attack continues from the current level to the first peak | -14.27 vs -14.28 dB, all three; the second peak came 23 / 24.5 / 52 ms after the note, from 0.9 / 1.2 / 2.0 dB below the peak | +| AMP DEC slope, DEC 6..126 | 0.918 x model (text) | 0.90-0.925 x model (DEC 108/114 captures failed) | + +Decay start = the 3 dB point minus 3 x the measured dB slope. The VCA ran attacks at 0.957x and decays +at 0.918x the OS timing model on EV06; EV07 on builds the ATK table and the AMP ATK/DEC text for it. + +### Filter envelope timing (EV09, same day) + +Track 1's filter self-oscillating (LP2, RES 127, FRQ 40, sample LEV 0, ENV depth +24, AMP ATK 0 / HLD +AUTO / DEC INF), its pitch tracked by zero crossings: the envelope's level was read back through the +pitch-to-level map of a linear attack (FILTER ATK 48), so the cutoff curve does not bias the fit. + +| Segment | Values | Measured / model | +|---|---|---| +| Attack (rise start to top) | ATK 16 / 32 / 48 / 56 / 64 / 72 / 80 | 0.956 / 0.979 / 0.983 / 0.984 / 0.984 / 0.985 / 0.986 | +| Decay (time constant, SUS 0) | DEC 24 / 40 / 56 / 72 / 88 | 0.905 / 0.902 / 0.895 / 0.887 / 0.873 | +| Release (time constant, SUS 127) | REL 24 / 40 / 56 / 72 / 88 | 0.906 / 0.904 / 0.902 / 0.902 / 0.901 | + +FILTER ATK text scaled by 0.984, DEC and REL by 0.90 (EV10..EV13). Superseded: the ~1.3 kHz runs in +the EV13 section show both were this method's bias; from 4efb6fbe the filter has no factor. + +These runs used stock's FILTER ATK table (118 ms..3.7 s attacks). The new FILTER ATK table +(`envelopes_flt_atk`, site 0x4010d2aa, EV11 on) is checked in rytm-emu only (knob text); its times on +the unit, and the filter's speed below 118 ms, were not tested then (now measured: EV13 section). + +## Unit (Analog Rytm MKII, OS 1.74), 6 Nov 2026 + +Build EV04: RC30's set with Envelopes in place of Env Reset Amp, base 8b0cafbc, sha256 +374157b56041b15bff1d9795371cc60c6a7f0388c7075378e1365287a549d393 (source 818f7b08). Flashed by the +author; scratch project. + +Automated: track 1 (BD) on BD ANALOG with its DEC 127, filter open, no FX, AMP DEC 40, played over USB +MIDI (channel 1, note 0, velocity 100), MAIN OUT recorded over USB audio; script envtest.py (private +rig, not in the repo). BD ANALOG's own body falls about 3 dB over the first 150 ms and it has an onset +click, so hold and AUTO are judged against the HLD 47 take (held to 500 ms), not by an absolute peak. + +| Check | Result | +|---|---| +| HLD 0, ATK 0 | falls from the hit (below the held take by 20 ms) | +| HLD 1 / 7 / 15 / 31 (15.6 / 62.5 / 125 / 250 ms) | leaves the held take after about 20 / 80 / 150 / 300 ms (sample points 10-50 ms apart) | +| AUTO, note 100 ms / 300 ms | leaves the held take right after note-off (125 / 400 ms points) | +| AUTO, ATK 64, note 50 ms | decays from the note-off, mid-attack (AUTO is outside the never-cut rule) | +| Never cut: ATK 64 (87 ms) with HLD 0, 2 (24 ms), 7 (62.5 ms) | identical takes; peak 87 ms, decay from 85 ms | +| ATK peak: 55 / 64 / 72 / 80 (37.9 / 87.2 / 183 / 384 ms) | 36.8 / 87.3 / 171.5 / 348 ms (long ones peak early on BD ANALOG's falling body) | +| ATK rise 10-90 %: 0 / 1 / 5 / 10 / 25 (0 / 0.2 / 1 / 2 / 5 ms) | 1.0 / 1.0 / 1.25 / 1.75 / 3.75 ms (hardware slew ~0.8 ms floors the shortest) | +| ATK 64 rise 10-90 % | 64.8 ms = 0.74 of the 87.2 ms ramp, as earlier probe measurements | + +Not tested on the unit: knob texts and the AMP picture (rytm-emu only), FUNC jumps, filter +DEC/REL times, retrigs, choke, saving and reloading, CPU cost. + +## rytm-emu (2ac0f1f), EV02-EV04 + +- Boots to the main screen; CPU exceptions only TRAP #0, as RC30. +- HLD text: 24ms (2), 31ms (3), 125ms (15), 500ms (47), 10.5s (116), 13s (120), AUTO (127). +- AMP DEC text: 63ms (7), 3.3s (68), 39.5s (124). (EV02 showed 0 / 1667s: fixed in EV03.) +- ATK text: 6.5ms (28), 38ms (55). +- FUNC + turn on HLD: 0 -> 15 -> 127 and back (stock's debounce paces it). +- AMP picture: HLD 0 attack straight into decay, AUTO with the note at 127. +- State traces (voice 0): HLD 0 + ATK 0 -> decay one block after the trig; HLD 0/2 + ATK 38 ms -> + decay at 57 blocks (38 ms); HLD 15 -> 3024 ticks (125 ms); AUTO -> state 1 (gate); HLD turned to + AUTO during a timed hold -> state 1. + +## Review + +Independent code review (two rounds): findings fixed in 5437e197 (MAC-mode -60 dB times, follower +start at silence, AUTO turned mid-hold, slow-rate floor); second round found no new bugs. diff --git a/sdk/analog-rytm-mkii/modules/envelopes/build.json b/sdk/analog-rytm-mkii/modules/envelopes/build.json new file mode 100644 index 000000000..deeeadc95 --- /dev/null +++ b/sdk/analog-rytm-mkii/modules/envelopes/build.json @@ -0,0 +1,111 @@ +{ + "schemaVersion": 1, + "sources": [ + "src/amp-write.s", + "src/envelopes.c" + ], + "defsym": {}, + "cflags": [], + "weak": [], + "subscribe": [ + { + "event": "ev_tick", + "fn": "envelopes_tick", + "order": 50 + } + ], + "contribute": [ + { + "to": "core_params", + "order": 50, + "data": "00000000", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_params", + 0 + ] + ] + } + ], + "collections": {}, + "copied": [], + "regions": [], + "claims": [ + "ui:FUNC + turn of AMP HLD", + "ui:value text of AMP ATK/HLD/DEC and FILTER ATK/DEC/REL", + "ui:AMP page envelope picture HLD", + "audio:amp envelope record write", + "audio:filter attack rate table", + "audio:amp hold table", + "audio:amp decay table", + "audio:filter decay and release table" + ], + "requires": [], + "derive": null, + "releases": { + "1.74": { + "defsym": {}, + "cflags": [], + "sites": [ + { + "addr": "0x40109c24", + "len": 8, + "stockSha256": "3df81daa6f0b5b044b3c5bbf7c45a8d96b97caa686b60bf262eeda8b7dc87dc5", + "op": "jmp", + "target": "envelopes_write" + }, + { + "addr": "0x400d3302", + "len": 6, + "stockSha256": "704bb7e62b7dc84ac22bb9ec1242ae79e917ce47a7b6a4aa06ec60b72654b2c0", + "op": "jsr", + "target": "envelopes_amp_graph" + }, + { + "addr": "0x4010d2aa", + "len": 4, + "stockSha256": "bb101f17b7e23130f5e92c2f8e616cf858c9db923af32f0756f4cd9af27591d0", + "op": "ptr", + "target": "envelopes_flt_atk" + }, + { + "addr": "0x40109bc4", + "len": 4, + "stockSha256": "a93a6f0c7e9387b5bf90a027d5678b11c90d555082736705d44faca3b73d46b6", + "op": "ptr", + "target": "envelopes_hold" + }, + { + "addr": "0x40109bf0", + "len": 4, + "stockSha256": "8879f37f45bb49679a30176c392dc823a5215ba1fdbecc9406684fec9adbc2aa", + "op": "ptr", + "target": "envelopes_amp_dec" + }, + { + "addr": "0x4010d344", + "len": 4, + "stockSha256": "e752dd02ad509e30bf4eab19cb5a7ef8760906dfdfa6d410e8a42e3635a2ef39", + "op": "ptr", + "target": "envelopes_flt_dec" + }, + { + "addr": "0x4010d3b4", + "len": 4, + "stockSha256": "e752dd02ad509e30bf4eab19cb5a7ef8760906dfdfa6d410e8a42e3635a2ef39", + "op": "ptr", + "target": "envelopes_flt_dec" + }, + { + "addr": "0x4010d3dc", + "len": 4, + "stockSha256": "e752dd02ad509e30bf4eab19cb5a7ef8760906dfdfa6d410e8a42e3635a2ef39", + "op": "ptr", + "target": "envelopes_flt_dec" + } + ] + } + } +} diff --git a/sdk/analog-rytm-mkii/modules/envelopes/media/LICENSE.md b/sdk/analog-rytm-mkii/modules/envelopes/media/LICENSE.md new file mode 100644 index 000000000..aaca18e4b --- /dev/null +++ b/sdk/analog-rytm-mkii/modules/envelopes/media/LICENSE.md @@ -0,0 +1,7 @@ +# Rytm UI documentation captures + +The Modwerk contributor dedicates their original capture contribution to the public domain under CC0-1.0. This declaration covers the act of capture and accompanying original captions, not the depicted firmware interface. + +Elektron retains rights in its underlying interface and firmware. Module interface additions retain their author's terms in [../LICENSE](../LICENSE). No firmware, extracted stock code, raw memory/framebuffer data, card images or audio samples are included. The images are unmodified firmware-rendered pixels at integer scale. + +Manifest identifier: `LicenseRef-Rytm-UI-Documentation`. Capture identity and inputs: [capture.json](capture.json). diff --git a/sdk/analog-rytm-mkii/modules/envelopes/media/capture.json b/sdk/analog-rytm-mkii/modules/envelopes/media/capture.json new file mode 100644 index 000000000..3b0aea089 --- /dev/null +++ b/sdk/analog-rytm-mkii/modules/envelopes/media/capture.json @@ -0,0 +1,31 @@ +{ + "schemaVersion": 1, + "machine": "analog-rytm-mkii", + "moduleId": "envelopes", + "moduleVersion": "0.1.0-experimental", + "firmware": "1.74", + "capturedAt": "2026-11-07T15:51:00Z", + "imageSha256": "c8835018e591328648e6f5c08fe11e9c929098753234211537cba49c78da4c2e", + "build": "Modwerk's linker: Modwerk base for core-ar2 0.7 (plain variant ba530f45) + this folder's Improved Envelopes 0.1.0-experimental, materialised against stock OS 1.74, --version EN02", + "emulator": { + "name": "rytm-emu", + "repository": "https://github.com/trigcondition/rytm-emu", + "binary": "dtboot", + "settings": "revision 2ac0f1f; no card (starts formatted); JIT on; --every 1000M; DTBOOT_TURN_PNG after each turn" + }, + "conditions": "Cold boot; eight NO presses from 7003M, 300M apart, close the calibration notices. AMP at 11003M (12M). 64 turns of DEC (encoder 2) by -6, 20M apart from 11303M; then 6 turns of HLD (encoder 1) by +6, 20M apart from 12803M. Frames are dtboot's firmware-rendered 128x64 screen at 3x, unmodified.", + "captures": [ + { + "path": "media/decay-time.png", + "frame": "turn64.png", + "plan": "DTBOOT_TURN_PNG=t3 dtboot Rytm-EN02-os1.74.syx --instr 13400M --every 1000M --press NO@7003M:12M --press NO@7303M:12M --press NO@7603M:12M --press NO@7903M:12M --press NO@8203M:12M --press NO@8503M:12M --press NO@8803M:12M --press NO@9103M:12M --press AMP@11003M:12M --turn 2:-6@11303Mx64/20M --turn 1:6@12803Mx6/20M", + "sha256": "5166a566f68a2dbd427c05469c589464d1691c67e39b671566615fc00d154dc9" + }, + { + "path": "media/hold-time.png", + "frame": "turn70.png", + "plan": "DTBOOT_TURN_PNG=t3 dtboot Rytm-EN02-os1.74.syx --instr 13400M --every 1000M --press NO@7003M:12M --press NO@7303M:12M --press NO@7603M:12M --press NO@7903M:12M --press NO@8203M:12M --press NO@8503M:12M --press NO@8803M:12M --press NO@9103M:12M --press AMP@11003M:12M --turn 2:-6@11303Mx64/20M --turn 1:6@12803Mx6/20M", + "sha256": "869cde2e56143f53fb755ee383d59f1bb0c575d371d0de9d285f372a77b12cd5" + } + ] +} diff --git a/sdk/analog-rytm-mkii/modules/envelopes/media/decay-time.png b/sdk/analog-rytm-mkii/modules/envelopes/media/decay-time.png new file mode 100644 index 000000000..2bfc70535 Binary files /dev/null and b/sdk/analog-rytm-mkii/modules/envelopes/media/decay-time.png differ diff --git a/sdk/analog-rytm-mkii/modules/envelopes/media/hold-time.png b/sdk/analog-rytm-mkii/modules/envelopes/media/hold-time.png new file mode 100644 index 000000000..c1dae17cb Binary files /dev/null and b/sdk/analog-rytm-mkii/modules/envelopes/media/hold-time.png differ diff --git a/sdk/analog-rytm-mkii/modules/envelopes/media/thumbnail.svg b/sdk/analog-rytm-mkii/modules/envelopes/media/thumbnail.svg new file mode 100644 index 000000000..657a69227 --- /dev/null +++ b/sdk/analog-rytm-mkii/modules/envelopes/media/thumbnail.svg @@ -0,0 +1,17 @@ + +Improved Envelopes +An amp envelope: an attack, a hold that runs from none to AUTO, and an exponential decay, over a time ruler whose steps are finest at the short end. Original conceptual illustration, not an instrument screen or a measured result. + + + + + + + + + +ATK HLD DEC +0.2ms +0..AUTO + + diff --git a/sdk/analog-rytm-mkii/modules/envelopes/modwerk.module.json b/sdk/analog-rytm-mkii/modules/envelopes/modwerk.module.json new file mode 100644 index 000000000..5579c97e6 --- /dev/null +++ b/sdk/analog-rytm-mkii/modules/envelopes/modwerk.module.json @@ -0,0 +1,191 @@ +{ + "schemaVersion": 3, + "id": "envelopes", + "name": "Improved Envelopes", + "version": "0.1.0-experimental", + "machine": "analog-rytm-mkii", + "category": "machines", + "exclusive": false, + "author": { + "github": "npp1993", + "name": "Nathaniel Pavel", + "credits": [ + "Nathaniel Pavel — Envelopes: HOLD order and attack-aware hold, the ATK, DEC, REL and HLD curves, and envelope times on the knobs.", + "MrScruff and Iced-Cream — Env Reset Amp (rytm-mods 0015 and its elemod port), whose reading of the amp envelope's commands this module started from; none of its code is carried.", + "irpina — Elekloader and core-ar2, the core it links with." + ] + }, + "maintainers": [ + "npp1993" + ], + "presentation": { + "label": "IMPROVED ENVELOPES", + "family": "Sound", + "summary": "The AMP envelope reworked: HOLD runs from no hold to AUTO, ATK, HOLD and DEC/REL have fine steps from 0.2 ms, and the envelope knobs show times.", + "overview": "Envelopes reworks the AMP envelope. HOLD runs from 0 (no hold: the decay starts the moment the attack reaches the top) up to AUTO at the top of the knob (hold while the note lasts); a hold never cuts the attack short. AMP ATK and FILTER ATK step 0.2 ms at a time up to 5 ms, 0.5 ms up to 15 ms, then about 10 % a step up to 30 s; AMP DEC and FILTER DEC and REL step 0.2 ms up to 5 ms and stay finer than stock up to 300 ms. AMP ATK, HLD and DEC and FILTER ATK, DEC and REL show times instead of 0..127.", + "highlights": [ + "HOLD: 0 = no hold, top = AUTO; never cuts the attack.", + "AMP and FILTER ATK: 0.2 ms steps at the low end.", + "AMP DEC, FILTER DEC and REL: 0.2 ms steps up to 5 ms.", + "Envelope knobs show ms and s." + ], + "usage": [ + "Press AMP. Turn DEC down: the knob shows its time to -60 dB, in 0.2 ms steps below 5 ms.", + "Turn HLD to 0: the decay starts as soon as the attack peaks. Turn it up for a hold in ms, or to the top for AUTO (decay from note-off).", + "Hold FUNC and turn HLD: it jumps between 0, 125 ms and AUTO.", + "On the FILTER page, ATK, DEC and REL show their times on the same fine curves." + ] + }, + "access": { + "location": "AMP page (ATK, HLD, DEC); FILTER page (ATK, DEC, REL).", + "steps": [ + "Open the AMP page.", + "Turn HLD or ATK; FUNC + turn on HLD jumps between 0, 125 ms and AUTO." + ], + "screenshots": [ + "media/decay-time.png", + "media/hold-time.png" + ] + }, + "controls": [ + { + "name": "AMP > HLD", + "default": null, + "doc": "0 no hold, then 0.67 ms steps to 10 ms, 1.3 ms to 18 ms, 3.3 ms to 51 ms, 6.7 ms to 151 ms, 13 ms to 298 ms, about 6 % a step to 15.5 s, AUTO at the top. The decay never starts before the attack peaks.", + "labels": null + }, + { + "name": "AMP > ATK", + "default": null, + "doc": "0 instant, 0.2 ms steps to 5 ms, 0.5 ms steps to 15 ms, then about 10 % a step to 30 s.", + "labels": null + }, + { + "name": "FILTER > ATK", + "default": null, + "doc": "The same curve as AMP ATK: 0 instant, 0.2 ms steps to 5 ms, 0.5 ms steps to 15 ms, then about 10 % a step to 30 s.", + "labels": null + }, + { + "name": "AMP > DEC, FILTER > DEC and REL", + "default": null, + "doc": "0 instant, 0.2 ms steps to 5 ms, 0.5 ms to 15 ms, 1 ms to 25 ms, 2.5 ms to 50 ms, 5 ms to 100 ms, 10 ms to 200 ms, 25 ms to 300 ms, then about 14 % a step to 40-45 s. Times are to -60 dB. AMP DEC at the top stays INF.", + "labels": null + } + ], + "compatibility": { + "releases": [ + "1.74" + ], + "requires": [], + "conflicts": [], + "limitations": [ + "Built for OS 1.74 only.", + "Saved HLD, ATK, DEC and REL values read differently from stock: HLD 0 is no hold instead of AUTO, 127 is AUTO instead of 16 s, and HLD, AMP and FILTER ATK, DEC and REL follow the new curves.", + "Times shown are calibrated to one Rytm MKII (AMP attack 0.957x, decay 0.918x; the filter envelope runs at the OS model's speed); another unit may differ slightly." + ] + }, + "platform": { + "kind": "elemod", + "core": "0.3", + "build": "build.json", + "events": [ + "ev_tick" + ], + "claims": [ + "ui:FUNC + turn of AMP HLD", + "ui:value text of AMP ATK/HLD/DEC and FILTER ATK/DEC/REL", + "ui:AMP page envelope picture HLD", + "audio:amp envelope record write", + "audio:filter attack rate table", + "audio:amp hold table", + "audio:amp decay table", + "audio:filter decay and release table" + ] + }, + "resources": { + "memoryBytes": 5228, + "fastBytes": null, + "load": { + "display": "Small: one compare per voice each block, and a short C call per voice only while its amp envelope moves", + "method": "static", + "conditions": "Read in the code: the amp record write skips a voice whose record repeats and changed nothing, so an idle voice makes no C call. On the author's MKII an earlier build in a 16-module set (LR03, private telemetry base, sequencer stopped) read 70.37 % of the audio block idle, against 70.84 % before that skip (LR01); the module's own share was not separated." + } + }, + "evidence": { + "tier": "none", + "moduleVersion": "0.1.0-experimental", + "reports": [], + "hardware": null, + "verifiedBy": null, + "sourceSha256": null + }, + "tests": { + "report": "TESTING.md", + "summary": "Emulator captures, and the author's measurements on an Analog Rytm MKII over USB (ED02 and earlier builds).", + "documentation": { + "tutorial": { + "title": "Quick tutorial", + "steps": [ + "Press AMP. Turn DEC down: the knob shows its time to -60 dB, in 0.2 ms steps below 5 ms.", + "Turn HLD to 0: the decay starts as soon as the attack peaks. Turn it up for a hold in ms, or to the top for AUTO (decay from note-off).", + "Hold FUNC and turn HLD: it jumps between 0, 125 ms and AUTO.", + "On the FILTER page, ATK, DEC and REL show their times on the same fine curves." + ] + }, + "screenshots": [ + "media/decay-time.png", + "media/hold-time.png" + ], + "captureRecord": "media/capture.json" + } + }, + "license": { + "spdx": "GPL-2.0-only", + "file": "LICENSE", + "declaration": "Nathaniel Pavel's code, under GPL-2.0-only. The source names firmware addresses only; the patch preconditions are addresses, lengths and SHA-256s." + }, + "media": [ + { + "path": "media/thumbnail.svg", + "kind": "thumbnail", + "caption": "Improved Envelopes", + "alt": "Original illustration of an amp envelope (attack, hold, decay) over a time ruler with fine steps at the short end", + "credit": "Nathaniel Pavel", + "license": "GPL-2.0-only", + "source": "Original vector illustration; not a device screenshot." + }, + { + "path": "media/decay-time.png", + "kind": "screenshot", + "caption": "AMP page, DEC turned down: DECAY TIME, 2.2ms.", + "alt": "AMP page titled DECAY TIME with the DEC knob reading 2.2ms and a short decay drawn in the envelope picture.", + "credit": "Nathaniel Pavel documentation capture; Elektron interface", + "license": "LicenseRef-Rytm-UI-Documentation", + "source": "original", + "capture": { + "type": "emulator", + "release": "1.74", + "moduleVersion": "0.1.0-experimental", + "imageSha256": "c8835018e591328648e6f5c08fe11e9c929098753234211537cba49c78da4c2e", + "setup": "Headless rytm-emu (dtboot) running EN02: the Modwerk base for core-ar2 0.7 (plain variant ba530f45) and this folder's Improved Envelopes 0.1.0-experimental, linked by Modwerk's linker on stock OS 1.74, the default kit and pattern A01. Firmware-rendered 128x64 screen at 3x. UI documentation only." + } + }, + { + "path": "media/hold-time.png", + "kind": "screenshot", + "caption": "AMP page, HLD turned up: HOLD TIME, 4.0ms, beside DEC at 1.8ms.", + "alt": "AMP page titled HOLD TIME with the HLD knob reading 4.0ms and DEC reading 1.8ms.", + "credit": "Nathaniel Pavel documentation capture; Elektron interface", + "license": "LicenseRef-Rytm-UI-Documentation", + "source": "original", + "capture": { + "type": "emulator", + "release": "1.74", + "moduleVersion": "0.1.0-experimental", + "imageSha256": "c8835018e591328648e6f5c08fe11e9c929098753234211537cba49c78da4c2e", + "setup": "Headless rytm-emu (dtboot) running EN02: the Modwerk base for core-ar2 0.7 (plain variant ba530f45) and this folder's Improved Envelopes 0.1.0-experimental, linked by Modwerk's linker on stock OS 1.74, the default kit and pattern A01. Firmware-rendered 128x64 screen at 3x. UI documentation only." + } + } + ] +} diff --git a/sdk/analog-rytm-mkii/modules/envelopes/src/amp-write.s b/sdk/analog-rytm-mkii/modules/envelopes/src/amp-write.s new file mode 100644 index 000000000..ced8fe172 --- /dev/null +++ b/sdk/analog-rytm-mkii/modules/envelopes/src/amp-write.s @@ -0,0 +1,79 @@ + | SPDX-License-Identifier: GPL-2.0-only + | Envelopes - the amp envelope record write (OS 1.74), Nathaniel Pavel. + | + | Every voice's record goes to envelopes_voice (envelopes.c), which puts + | this module's ATK rate into the attack's records, follows the voice's + | amp level and decides when the hold ends (HOLD 0 = no hold, 127 = + | AUTO, never before the attack's top). + + .equ WRITE_RESUME, 0x40109c2c | moveq #8,%d0, then the level word + + .text + + | ------------------------------------------------------------------ + | envelopes_write: over the two `move.l %d0` stores of the voice's rate + | word at 0x40109c24 (amp_env_commands 0x40109940), every block for each + | voice, in every state, after stock's state machine. + | + | It calls envelopes_voice unless the voice's record is the one it last + | took and that one left the voice as it was (envelopes_gate, + | envelopes.c): an idle voice (rate and level words 0) and a decay that + | has reached its end get the same record every block, so an idle unit + | makes no C call. + | + | d0 the rate word, d2 the level word, d3 the voice, a2 its state (-1 + | idle; +4 the hold counter), a3 the frame at the voice's record, a4 the + | track's sound slice (+0 ATK, +2 HLD, +4 DEC). d1 and a0 are written + | before they are read on the next pass, so they are free. + | ------------------------------------------------------------------ + .globl envelopes_write + .align 2 +envelopes_write: + move.l envelopes_gate,%d1 | the voices left as they were + btst %d3,%d1 + beq.s 5f + lea envelopes_gate+16,%a0 + cmp.l (%a0,%d3.l*4),%d0 | the same rate word + bne.s 5f + cmp.l 32(%a0,%d3.l*4),%d2 | and level word: nothing changes + beq.s 8f +5: lea -16(%sp),%sp + movem.l %d0-%d1/%a0-%a1,(%sp) + move.l %d2,-(%sp) | level word + move.l %d0,-(%sp) | rate word + move.l %a4,-(%sp) | sound slice + move.l %a2,-(%sp) | state, counter + move.l %d3,-(%sp) | voice + jsr envelopes_voice + move.l %d0,20(%sp) | the rate word it returns + lea 20(%sp),%sp + movem.l (%sp),%d0-%d1/%a0-%a1 + lea 16(%sp),%sp +8: move.l %d0,172(%a3) | displaced + move.l %d0,236(%a3) + jmp WRITE_RESUME + + | ------------------------------------------------------------------ + | envelopes_amp_graph: over the AMP page's call of stock's envelope + | picture at 0x400d3302, `jsr 0x40038bce` (SoundPageView's draw). The + | picture takes (view, bmp, 6, 0, 0, ATK, HLD, DEC) in 8.8 and draws HLD + | 0 as AUTO (the note and a full-width hold). Here HLD 127 is AUTO and 0 + | is no hold, so 127 goes in as 0 and 0 as 1/256 (no width, no note). + | At entry the arguments sit above the return address: HLD at 28(%sp). + | d0 is free: the caller drops it. + | ------------------------------------------------------------------ + .equ AMP_PICTURE, 0x40038bce + + .globl envelopes_amp_graph + .align 2 +envelopes_amp_graph: + move.l 28(%sp),%d0 + cmp.l #0x7f00,%d0 + blt.s 1f + clr.l 28(%sp) | AUTO + bra.s 2f +1: tst.l %d0 + bgt.s 2f + moveq #1,%d0 | no hold + move.l %d0,28(%sp) +2: jmp AMP_PICTURE diff --git a/sdk/analog-rytm-mkii/modules/envelopes/src/envelopes.c b/sdk/analog-rytm-mkii/modules/envelopes/src/envelopes.c new file mode 100644 index 000000000..d6b80edaf --- /dev/null +++ b/sdk/analog-rytm-mkii/modules/envelopes/src/envelopes.c @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Envelopes (Nathaniel Pavel): the AMP envelope's HOLD order and ATK curve, and times on the +// envelope knobs, for the Analog Rytm MKII OS 1.74. +// +// HOLD (AMP page, id 57) keeps its stored byte, read as: 0 no hold, 1..126 the new hold +// times (counted from the trig), 127 AUTO (hold while the note is on, stock's 0). The decay +// never starts before the attack reaches the top, so every value but AUTO is at least a +// plain attack-decay. Stock's state machine (amp_env_commands 0x40109940) still runs; at +// its record write, envelopes_voice sets the state it moves to: 1 (gate) or 2 (timed) +// after a trig, and in the timed state 2 or 3 (decay) by its own rule. To know when the +// attack is at the top, it follows each voice's level from the records sent (the level +// itself lives in the voice hardware and is not read back). +// +// ATK (id 56): envelopes_voice puts the rate from atk_rate (tools/atk_table.py) into the +// attack's records: 0 instant, 0.2 ms steps to 5 ms, 0.5 ms steps to 15 ms, then about 10 % a +// step to stock's 30 s. +// +// DEC and REL (AMP DEC id 58, FILTER DEC id 51, FILTER REL id 53) and HLD (id 57) have fine steps +// at the short end too (tools/dec_table.py): their stock tables are replaced by our own, through +// ptr sites in build.json, so stock's state machine and record writers read them unchanged. +// +// FILTER ATK (id 50) has the same curve, in its own table envelopes_flt_atk: build.json's ptr +// site points stock's lea at 0x4010d2a8 (the filter envelope's attack table, read for the +// attack record and the state counter alike) at it. +// +// The AMP ATK, HLD and DEC and FILTER ATK, DEC and REL knobs show times: an attack's full +// ramp, a decay's or release's time to -60 dB, a hold's length. + +#include +#include "analog-rytm-mk2/pages.h" + +typedef uint32_t word_t; + +// ---- stock OS 1.74 ---- +// (The stock tables of AMP HLD 0x401ee81c, AMP DEC 0x401ee61c and FILTER DEC/REL 0x401ef030 are +// replaced by envelopes_hold, envelopes_amp_dec and envelopes_flt_dec below, through ptr sites.) +#define HOLD_TICKS envelopes_hold +#define AMP_DECAY envelopes_amp_dec +#define FLT_DECAY envelopes_flt_dec +#define REC(id) (0x419a4888 + 84 * (id)) // a parameter's RAM record +#define JUMP_MANAGER(id) (*(volatile word_t *)(REC(id) + 0x4c)) +#define JUMP_INVOKER(id) (*(volatile word_t *)(REC(id) + 0x50)) +#define STOCK_MANAGER 0x400fe584 // a stateless lambda's manager + +#define ID_F_ATK 50 +#define ID_F_DEC 51 +#define ID_F_REL 53 +#define ID_A_ATK 56 +#define ID_A_HLD 57 +#define ID_A_DEC 58 + +// The unit's VCA ramps run faster than the OS's timing model: attack 0.957x, decay 0.918x +// (Rytm MKII, EV06, 6 Nov 2026: a 1 kHz tone, ATK 10..80 and DEC 6..126). The ATK table is +// built for it; the times shown are scaled to it, in 1/1024. +#define HW_ATTACK 980 +#define HW_DECAY 940 +// The filter envelope runs at the model's speed (same unit, 6 Nov 2026, through the +// self-oscillating filter's pitch at ~1.3 kHz: attack 1.00x over 3..390 ms, decay and release +// 0.99-1.01x over 47 ms..1.2 s time constants), so FILTER text needs no factor. (An earlier +// low-pitch method, which cut the ends off each segment, had read 0.98x (0.984 used) and 0.90x.) + +#define HLD_AUTO 127 +#define LEVEL_ATTACK 0x4000007fu // the trig's attack record (state 0): ramp to 127 +#define LEVEL_HOLD 0x0000007fu // states 1 and 2: the attack continued +#define TOP ((127 << 16) - (1 << 15)) // within half a level of the top + +// Generated by tools/atk_table.py: AMP ATK knob -> linear rate coefficient, packed. +static const uint16_t atk_rate[128] = { + 0x0000, 0x203f, 0x33c3, 0x3dd8, 0x453d, 0x4ad0, 0x4e76, 0x5215, + 0x55ea, 0x58e0, 0x5b3c, 0x5d28, 0x5ec0, 0x6032, 0x627f, 0x647d, + 0x663a, 0x67c3, 0x691f, 0x6a56, 0x6b6d, 0x6c6a, 0x6d50, 0x6e21, + 0x6ee1, 0x6f92, 0x7224, 0x74a4, 0x76c1, 0x7890, 0x7a21, 0x7b80, + 0x7cb5, 0x7dc8, 0x7ebd, 0x7f9a, 0x80c5, 0x8230, 0x837c, 0x84ab, + 0x85c3, 0x86c5, 0x87b4, 0x8891, 0x8960, 0x8a20, 0x8c0e, 0x8dd0, + 0x8f6a, 0x91bf, 0x9468, 0x96d4, 0x9909, 0x9b0d, 0x9ce2, 0x9e8e, + 0xa028, 0xa2ee, 0xa576, 0xa7c4, 0xa9de, 0xabc9, 0xad87, 0xaf1f, + 0xb124, 0xb3c9, 0xb631, 0xb863, 0xba64, 0xbc37, 0xbde0, 0xbf64, + 0xc18c, 0xc411, 0xc65c, 0xc874, 0xca5c, 0xcc19, 0xcdaf, 0xcf20, + 0xd0e3, 0xd349, 0xd579, 0xd777, 0xd948, 0xdaf0, 0xdc73, 0xddd3, + 0xdf14, 0xe072, 0xe288, 0xe46e, 0xe629, 0xe7bd, 0xe92e, 0xea7d, + 0xebaf, 0xecc6, 0xedc5, 0xeeac, 0xef80, 0xf081, 0xf1e0, 0xf320, + 0xf443, 0xf54d, 0xf640, 0xf71c, 0xf7e6, 0xf89d, 0xf945, 0xf9dd, + 0xfa68, 0xfae7, 0xfb5a, 0xfbc4, 0xfc24, 0xfc7b, 0xfccb, 0xfd13, + 0xfd56, 0xfd92, 0xfdc9, 0xfdfb, 0xfe29, 0xfe53, 0xfe79, 0xfe9b, +}; + +// The same curve for FILTER ATK (0 -> 127 levels), read by stock through the lea at +// 0x4010d2a8 (attack record and the state counter alike), so full 32-bit words. +const uint32_t envelopes_flt_atk[128] __attribute__((aligned(4))) = { + 0x00000, 0x2f499, 0x42751, 0x4d097, 0x54128, 0x59e5b, 0x5db60, 0x60cd9, + 0x64cd8, 0x67e49, 0x6a5a0, 0x6c5ad, 0x6e04a, 0x6f6be, 0x713e1, 0x73515, + 0x75214, 0x76ba0, 0x7824c, 0x7968e, 0x7a8c4, 0x7b93a, 0x7c82d, 0x7d5d0, + 0x7e24e, 0x7edca, 0x80da2, 0x83749, 0x85a7d, 0x878a2, 0x892bc, 0x8a98f, + 0x8bdaf, 0x8cf90, 0x8df8c, 0x8edee, 0x8faf0, 0x90d85, 0x92319, 0x936de, + 0x9490e, 0x959d6, 0x9695f, 0x977cc, 0x98538, 0x991bf, 0x9b1e2, 0x9cf29, + 0x9e9d6, 0xa044a, 0xa3095, 0xa58f2, 0xa7dba, 0xa9f3e, 0xabdc9, 0xad99f, + 0xaf2fc, 0xb1435, 0xb3e58, 0xb64bf, 0xb87be, 0xba7a4, 0xbc4b6, 0xbdf36, + 0xbf75c, 0xc1abe, 0xc42dd, 0xc676f, 0xc88c4, 0xca725, 0xcc2d6, 0xcdc13, + 0xcf315, 0xd101a, 0xd3653, 0xd592a, 0xd78ec, 0xd95de, 0xdb040, 0xdc84d, + 0xdde38, 0xdf233, 0xe08d4, 0xe2a07, 0xe484a, 0xe63df, 0xe7d04, 0xe93ef, + 0xea8d2, 0xebbdd, 0xecd38, 0xedd0a, 0xeeb75, 0xef89a, 0xf092b, 0xf1f03, + 0xf32eb, 0xf4511, 0xf559b, 0xf64af, 0xf726d, 0xf7ef5, 0xf8a5f, 0xf94c9, + 0xf9e45, 0xfa6eb, 0xfaecd, 0xfb5fb, 0xfbc89, 0xfc281, 0xfc7f1, 0xfcce7, + 0xfd16d, 0xfd58b, 0xfd94d, 0xfdcb9, 0xfdfd9, 0xfe2b1, 0xfe549, 0xfe7a7, + 0xfe9cd, 0xfebc5, 0xfed8f, 0xfef31, 0xff0ad, 0xff209, 0xff345, 0xff465, +}; + +// AMP DEC knob -> one-pole coefficient (time to -60 dB, for the VCA at 0.918x), read by stock +// through the lea at 0x40109bee. +const uint32_t envelopes_amp_dec[128] __attribute__((aligned(4))) = { + 0x00000, 0x0889d, 0x110f2, 0x1933b, 0x1dff5, 0x2246e, 0x26b39, 0x29fbb, + 0x2c836, 0x2e85d, 0x30508, 0x33065, 0x354ef, 0x37423, 0x38f18, 0x3a69d, + 0x3bb51, 0x3cdae, 0x3de14, 0x3eccf, 0x3fa1d, 0x40c64, 0x4226b, 0x43696, + 0x4491f, 0x45a36, 0x47fa1, 0x49ef3, 0x4b98d, 0x4d06c, 0x4e44c, 0x4f5bb, + 0x50a4a, 0x525bd, 0x53e57, 0x55484, 0x5689b, 0x57ae4, 0x58b9b, 0x59af1, + 0x5a910, 0x5b61c, 0x5c233, 0x5cd6f, 0x5d7e6, 0x5e1ad, 0x5f368, 0x60624, + 0x62203, 0x63af8, 0x65172, 0x665cb, 0x6784e, 0x68937, 0x698ba, 0x6a700, + 0x6c62b, 0x6e02a, 0x6f62d, 0x7121e, 0x732dc, 0x74f86, 0x768d3, 0x77f53, + 0x79376, 0x7a597, 0x7c4eb, 0x7df08, 0x7f523, 0x81030, 0x8310d, 0x84dd0, + 0x86732, 0x87dc3, 0x891f5, 0x8a422, 0x8c38a, 0x8ddb7, 0x8f3de, 0x90db9, + 0x92ea5, 0x94b74, 0x964e1, 0x97b7b, 0x98fb4, 0x9a1e8, 0x9c857, 0x9e716, + 0xa0080, 0xa2a71, 0xa6370, 0xa9566, 0xac131, 0xae793, 0xb1272, 0xb4d6e, + 0xb8121, 0xbae71, 0xbd629, 0xbf8f9, 0xc2ef9, 0xc6477, 0xc9358, 0xcbc70, + 0xce074, 0xd000b, 0xd3761, 0xd67e1, 0xd9263, 0xdb7a5, 0xdd84d, 0xdf4ec, + 0xe1c05, 0xe47ff, 0xe6e8a, 0xe9052, 0xeaded, 0xec7e1, 0xede9f, 0xef28f, + 0xf0811, 0xf26b1, 0xf4189, 0xf5911, 0xf6daf, 0xf7fc3, 0xf8f9b, 0xf9d7d, +}; + +// FILTER DEC and REL knob -> coefficient (the filter runs at the OS model's speed), read by +// stock through the leas at 0x4010d342, 0x4010d3b2 and 0x4010d3da. +const uint32_t envelopes_flt_dec[128] __attribute__((aligned(4))) = { + 0x00000, 0x07968, 0x0f951, 0x179ce, 0x1ca90, 0x1ffe0, 0x24b38, 0x28362, + 0x2aeca, 0x2d154, 0x2ed76, 0x309a3, 0x33108, 0x352b3, 0x36fcf, 0x38939, + 0x39f9b, 0x3b377, 0x3c535, 0x3d527, 0x3e38f, 0x3f0a4, 0x3fc93, 0x40f01, + 0x42318, 0x4359f, 0x45e33, 0x4802d, 0x49d0b, 0x4b5de, 0x4cb73, 0x4de62, + 0x4ef20, 0x4fe09, 0x516ce, 0x52ee7, 0x544bc, 0x5589c, 0x56ac7, 0x57b73, + 0x58acd, 0x598fd, 0x5a622, 0x5b25a, 0x5bdbc, 0x5c85e, 0x5dba7, 0x5ecb0, + 0x5fbd9, 0x612da, 0x62b4e, 0x64173, 0x65597, 0x667fe, 0x678df, 0x68868, + 0x6aa54, 0x6c69f, 0x6de95, 0x6f322, 0x709eb, 0x7291f, 0x744ab, 0x75d2a, + 0x77316, 0x786d4, 0x7a8ef, 0x7c55f, 0x7dd71, 0x7f215, 0x807f4, 0x82747, + 0x842eb, 0x85b7f, 0x8717d, 0x88549, 0x8a77d, 0x8c3ff, 0x8dc1f, 0x8f0ce, + 0x90579, 0x924da, 0x9408b, 0x95929, 0x96f30, 0x98304, 0x9ace0, 0x9ce5e, + 0x9e9c6, 0xa013b, 0xa3f51, 0xa75bc, 0xaa570, 0xacf40, 0xaf3e6, 0xb280c, + 0xb605d, 0xb91b9, 0xbbcfd, 0xbe2e9, 0xc084d, 0xc4291, 0xc75a2, 0xca265, + 0xcc9a1, 0xcec04, 0xd144f, 0xd4924, 0xd7772, 0xda008, 0xdc39b, 0xde2c9, + 0xdfe1c, 0xe2c20, 0xe561d, 0xe7ae9, 0xe9b2a, 0xeb76f, 0xed035, 0xee5ed, + 0xef8f5, 0xf1349, 0xf3087, 0xf4a27, 0xf609f, 0xf744f, 0xf8591, 0xf94b1, +}; + +// AMP HLD knob -> hold in ticks of 2 samples, whole blocks of 16, read by stock through the lea +// at 0x40109bc2 and by envelopes_voice. 0 is no hold and 127 AUTO (not read). +const int32_t envelopes_hold[128] __attribute__((aligned(4))) = { + 0, 16, 32, 48, 64, 80, 96, 112, + 128, 144, 160, 176, 192, 208, 224, 240, + 272, 304, 336, 368, 400, 432, 512, 592, + 672, 752, 832, 912, 992, 1072, 1152, 1232, + 1392, 1552, 1712, 1872, 2032, 2192, 2352, 2512, + 2672, 2832, 2992, 3152, 3312, 3472, 3632, 3952, + 4272, 4592, 4912, 5232, 5552, 5872, 6192, 6512, + 6832, 7152, 7568, 8016, 8496, 8992, 9520, 10080, + 10672, 11312, 11968, 12688, 13424, 14224, 15056, 15952, + 16880, 17872, 18928, 20048, 21232, 22480, 23808, 25216, + 26704, 28272, 29936, 31696, 33568, 35552, 37648, 39856, + 42208, 44704, 47328, 50128, 53072, 56208, 59520, 63024, + 66736, 70672, 74848, 79248, 83920, 88864, 94112, 99664, + 105536, 111744, 118336, 125312, 132704, 140512, 148800, 157568, + 166864, 176688, 187104, 198128, 209808, 222176, 235280, 249136, + 263824, 279376, 295840, 313280, 331744, 351296, 372000, 372000, +}; + +// ---- the voice's amp level, followed from the records ---- +// +// A record is a level word (W, start S, target T as signed bytes) and a rate word: mode +// [31:28], sub-block nibble, flags [23:20] (0 linear, 1 one-pole) and a 20-bit +// coefficient. Modes as measured on the unit: B and 3 head for T from the current level, +// F (and 7) load S first, 9 changes the rate only, A and 2 freeze, 0 and 8 send nothing +// (the running segment goes on). Per tick (2 samples) a linear segment moves step / 2^23 +// levels; a one-pole one keeps (2^31 - step) / 2^31 of its distance to T. +// +// Levels are kept in 1/65536, from silence (-128) at boot: an idle voice gets no records. +// The follower errs low: a one-pole block takes 16 x step / 2^31 of the distance at the unit's +// measured decay speed (HW_DECAY), slightly more than the hardware's 1 - (1 - x)^16, so a +// falling level is followed at or below the real one; the attack is followed at the model's +// speed, slower than the unit's (HW_ATTACK). So the attack is never thought done early. +// (Stock's amp sends no rising one-pole segment.) +struct amp { + int32_t level, target; + uint32_t step; + uint8_t one_pole, frozen; +}; +#define SILENT (-128 * 65536) +static struct amp amps[8] = { + { SILENT, SILENT, 0, 0, 0 }, { SILENT, SILENT, 0, 0, 0 }, { SILENT, SILENT, 0, 0, 0 }, + { SILENT, SILENT, 0, 0, 0 }, { SILENT, SILENT, 0, 0, 0 }, { SILENT, SILENT, 0, 0, 0 }, + { SILENT, SILENT, 0, 0, 0 }, { SILENT, SILENT, 0, 0, 0 }, +}; + +static uint32_t atk_coeff(int k) +{ + uint32_t u = atk_rate[k < 0 ? 0 : k > 127 ? 127 : k]; + return (u >> 12) << 16 | (u & 0xfff) << 4; +} + +static uint32_t rate_step(uint32_t c) +{ + uint32_t e = c >> 16 & 15, m = c & 0xffff; + return ((0x7fffffffu - (m << 14)) >> e) - 32768u; +} + +__attribute__((noinline)) static void follow(struct amp *a, uint32_t rate, uint32_t lw) +{ + unsigned mode = rate >> 28; + int32_t d; + uint32_t ad, move; + + if (mode == 0xa || mode == 0x2) { + a->frozen = 1; + } else if (mode && mode != 8) { + if (mode & 4) + a->level = (int32_t)(int8_t)(lw >> 8) * 65536; + if (mode != 9) + a->target = (int32_t)(int8_t)lw * 65536; + a->step = rate_step(rate & 0xfffff); + a->one_pole = rate >> 20 & 1; + a->frozen = 0; + } + if (a->frozen) + return; + d = a->target - a->level; + ad = d < 0 ? (uint32_t)-d : (uint32_t)d; + if (!ad) + return; + if (!a->one_pole) { + move = a->step >> 3; // 16 ticks: step x 16 x 65536 / 2^23 + if (!move) + move = 1; // a rate this slow still ends + if (move >= ad) { + a->level = a->target; + return; + } + ad -= move; + } else { + // 16 ticks: 16 x step / 2^31, in 1/65536, at the unit's decay speed (1 / 0.918), + // rounded up (step >> 6 keeps the fraction for slow decays); 2^27 and up is instant. + move = a->step >= (1u << 27) ? 65536 : (((a->step >> 6) * 1116) >> 15) + 1; + if (move >= 65536) { + a->level = a->target; + return; + } + ad -= (ad >> 8) * move >> 8; + } + a->level = d < 0 ? a->target + (int32_t)ad : a->target - (int32_t)ad; +} + +// The writer's skip state (amp-write.s reads it for every voice every block), kept together so a +// voice with nothing to do touches three 16-byte lines: [0] the voices whose last record left the +// level follower unchanged, [4 + v] and [12 + v] that record's rate and level words. +volatile uint32_t envelopes_gate[20] __attribute__((aligned(16))); +#define GATE_STILL envelopes_gate[0] +#define GATE_RATE(v) envelopes_gate[4 + (v)] +#define GATE_LW(v) envelopes_gate[12 + (v)] + +// Called by envelopes_write (amp-write.s) for every voice every block, after stock's state +// machine and before the record is stored; returns the rate word to store. st[0] is the +// voice's state, st[1] its hold counter (cleared at the trig, +16 a block in state 2, already +// added this block); snd[0] is the track's ATK, snd[2] its HLD. +uint32_t envelopes_voice(int32_t v, volatile int32_t *st, const volatile int8_t *snd, uint32_t rate, + uint32_t lw) +{ + struct amp *a = &s[v & 7], was = *a; + int h = snd[2]; + uint32_t bit = 1u << (v & 7); + + // The attack's records (the trig's, then each held block's): this module's ATK curve. + if (lw == LEVEL_ATTACK || lw == LEVEL_HOLD) + rate = (rate & 0xfff00000u) | atk_coeff(snd[0]); + + if (lw == LEVEL_ATTACK) { + st[0] = h == HLD_AUTO ? 1 : 2; + } else if (lw == LEVEL_HOLD && h == HLD_AUTO && st[0] == 2) { + st[0] = 1; // turned to AUTO while held: the gate (set at the trig) ends it + } else if (lw == LEVEL_HOLD && h != HLD_AUTO && (uint32_t)(st[0] - 1) < 3u) { + // States 1 and 2 send the continued attack; 3 here is 2 that stock just ended. + int32_t hold = h > 0 ? HOLD_TICKS[h] : 0; + st[0] = st[1] - 16 >= hold && a->level >= TOP ? 3 : 2; + } + follow(a, rate, lw); + // A record that is neither attack word and left the follower as it was does the same next + // time: until the record changes, the writer skips this call (amp-write.s). Idle voices and + // a decay that has reached its end get the same record every block. + if (lw != LEVEL_ATTACK && lw != LEVEL_HOLD && a->level == was.level && a->target == was.target && + a->step == was.step && a->one_pole == was.one_pole && a->frozen == was.frozen) { + GATE_RATE(v & 7) = rate; + GATE_LW(v & 7) = lw; + GATE_STILL |= bit; + } else { + GATE_STILL &= ~bit; + } + return rate; +} + +// ---- FUNC + turn on HOLD: 0, 125 ms (a 16th at 120 BPM), AUTO ---- +static int32_t hold_jump(const void *fn, int32_t value, int32_t delta, int32_t min, int32_t max, + int32_t def) +{ + static const int8_t pos[] = { 0, 42, HLD_AUTO }; + int v = value >> 8, i; + + (void)fn, (void)min, (void)max, (void)def; + if (delta > 0) { + for (i = 0; i < 3; i++) + if (pos[i] > v) + return pos[i] << 8; + } else { + for (i = 2; i >= 0; i--) + if (pos[i] < v) + return pos[i] << 8; + } + return v << 8; +} + +// The records are built at boot; the jump goes in once and is checked at the UI's rate. +void envelopes_tick(void *ctrl) +{ + (void)ctrl; + if (JUMP_INVOKER(ID_A_HLD) != (word_t)hold_jump) { + JUMP_MANAGER(ID_A_HLD) = STOCK_MANAGER; + JUMP_INVOKER(ID_A_HLD) = (word_t)hold_jump; + } +} + +// ---- times on the knobs ---- +__attribute__((noinline)) static char *put_uint(char *p, uint32_t v) +{ + char t[10]; + int n = 0; + do + t[n++] = (char)('0' + v % 10); + while ((v /= 10) && n < 10); + while (n) + *p++ = t[--n]; + return p; +} + +__attribute__((noinline)) static void put_text(char *p, const char *s) +{ + while ((*p++ = *s++)) + ; +} + +// ticks of 2 samples: "0", "0.2ms", "9.8ms", "87ms", "1234ms", "2.1s", "15.5s", "100s". +__attribute__((noinline)) static void put_time(char *p, uint32_t ticks) +{ + uint32_t ms10, ms, s10; + + if (ticks > 40000000u) + ticks = 40000000u; + ms10 = (ticks * 10 + 12) / 24; + if (!ms10) { + put_text(p, "0"); + return; + } + if (ms10 < 200) { // tenths up to 20 ms: ATK's 0.5 ms steps stay visible + p = put_uint(p, ms10 / 10); + *p++ = '.'; + p = put_uint(p, ms10 % 10); + put_text(p, "ms"); + return; + } + // Each unit rounds from the ticks, not from the unit below it (31.46 ms shows 31ms). + ms = (ticks + 12) / 24; + if (ms < 2000) { // whole ms up to 2 s: HLD's 6 % steps stay apart + put_text(put_uint(p, ms), "ms"); + return; + } + s10 = (ticks + 1200) / 2400; + if (s10 < 1000) { + p = put_uint(p, s10 / 10); + *p++ = '.'; + p = put_uint(p, s10 % 10); + put_text(p, "s"); + return; + } + put_text(put_uint(p, (ticks + 12000) / 24000), "s"); +} + +// log2(y) x 65536, y > 0. +__attribute__((noinline)) static int32_t log2_q16(uint32_t y) +{ + int32_t n = 31, bit, f = 0; + uint32_t m; + + while (!(y & 0x80000000u)) { + y <<= 1; + n--; + } + m = y >> 16; + for (bit = 15; bit >= 0; bit--) { + m = (m * m) >> 15; + if (m >= 65536) { + m >>= 1; + f |= 1 << bit; + } + } + return (n << 16) + f; +} + +// A one-pole segment's ticks to fall 60 dB: ln 1000 / -ln p, p = 1 - step / 2^31 a tick. +// The OS's own (0x4011e85c) runs on the MAC in fractional mode, which only the audio +// interrupt sets, so it reads wrong from the UI. Small steps: -ln p = x (1 + x / 2), x = +// step / 2^31 (the next term is under 0.01 %); larger: log2, 1/65536 resolution. +__attribute__((noinline)) static uint32_t t60_ticks(uint32_t c) +{ + uint32_t s = rate_step(c), d; + int32_t q; + + if (s < 16) + return 0x7fffffffu; + if (s < (1u << 25)) { + d = (s >> 4) + (s >> 18) * (s >> 18); // x (1 + x / 2), in 2^-27 + return 927143219u / d; // ln 1000 x 2^27 + } + if (s >= 0x7fff0000u) + return 0; + q = (31 << 16) - log2_q16(0x7fffffffu - s); + return q > 0 ? 653118u / (uint32_t)q : 0x7fffffffu; // log2 1000 x 65536 +} + +// A linear segment's ticks over levels, as the OS counts them (0x4011e7d6). +__attribute__((noinline)) static uint32_t linear_ticks(uint32_t levels, uint32_t c) +{ + uint32_t s = rate_step(c); + return s ? (levels << 23) / s + 1 : 0x7fffffffu; +} + +// ticks x f / 1024, rounded, without overflow (ticks up to 0x7fffffff). Rounded, not +// floored, so short attacks (a few dozen ticks) read their nearest tenth. +static uint32_t scaled(uint32_t ticks, uint32_t f) +{ + return (ticks >> 10) * f + (((ticks & 1023) * f + 512) >> 10); +} + +static int envelopes_text(struct core_text *t) +{ + int v = t->value >> 8; + + if (v < 0) + v = 0; + if (v > 127) + v = 127; + switch (t->id) { + case ID_A_ATK: + put_time(t->buf, scaled(linear_ticks(255, atk_coeff(v)), HW_ATTACK)); + break; + case ID_A_HLD: + if (v == HLD_AUTO) + put_text(t->buf, "AUTO"); + else + put_time(t->buf, v ? (uint32_t)HOLD_TICKS[v] : 0); + break; + case ID_A_DEC: + if (v == 127) + put_text(t->buf, "INF"); + else + put_time(t->buf, scaled(t60_ticks(AMP_DECAY[v] & 0xfffff), HW_DECAY)); + break; + case ID_F_ATK: + put_time(t->buf, linear_ticks(127, envelopes_flt_atk[v])); + break; + case ID_F_DEC: + case ID_F_REL: + put_time(t->buf, t60_ticks(FLT_DECAY[v] & 0xfffff)); + break; + default: + return CORE_TEXT_PASS; + } + return CORE_TEXT_DONE; +} + +const struct core_param envelopes_params = { ID_F_ATK, ID_A_DEC, 0, 0, envelopes_text }; diff --git a/sdk/analog-rytm-mkii/modules/envelopes/tools/atk_table.py b/sdk/analog-rytm-mkii/modules/envelopes/tools/atk_table.py new file mode 100644 index 000000000..f44887130 --- /dev/null +++ b/sdk/analog-rytm-mkii/modules/envelopes/tools/atk_table.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Envelopes' AMP ATK curve: knob value -> amp envelope rate word (20-bit coefficient). + +The amp attack is a linear ramp from silence (-128) to the top (127), 255 levels. A +coefficient c (e = c[19:16], m = c[15:0]) moves the level step / 2^23 levels per tick +(2 samples at 48 kHz), step = ((0x7FFFFFFF - (m << 14)) >> e) - 32768, as the OS times its +linear segments. The curve: + + 0 instant (c = 0) + 1..25 0.2 ms steps, 0.2 .. 5 ms + 26..45 0.5 ms steps, 5.5 .. 15 ms + 46..127 x r a step, 15 ms .. 29.89 s (stock's ATK 127) + +Stored packed in 16 bits, e << 12 | m >> 4 (m rounded to a multiple of 16: at most 2.3e-4 off in +step); unpacked as (u >> 12) << 16 | (u & 0xfff) << 4. Prints the C table for src/envelopes.c; +--check prints each value's time back from the packed table. +""" +import sys + +TICKS_PER_MS = 24.0 # 48 kHz, 2 samples a tick +HW_SPEED = 0.957 # the unit's amp attack runs 0.957x the OS timing model (EV06, 6 Nov 2026) +F_LEVELS, F_HW_SPEED = 127, 1.0 # the filter attack: 0 -> 127 levels, at the model's speed (EV13: 1.00x, 3 ms..390 ms) +TOP_MS = 29889.0 # stock ATK 127's full ramp +LEVELS = 255 + + +def curve_ms(): + t = [0.0] + for k in range(1, 26): + t.append(0.2 * k) + for k in range(26, 46): + t.append(5.0 + 0.5 * (k - 25)) + r = (TOP_MS / t[45]) ** (1.0 / (127 - 45)) + for k in range(46, 128): + t.append(t[45] * r ** (k - 45)) + return t + + +def step_of(c): + e, m = (c >> 16) & 15, c & 0xFFFF + return ((0x7FFFFFFF - (m << 14)) >> e) - 32768 + + +def encode(ms, levels=LEVELS, hw=HW_SPEED, packed=True): + if ms <= 0: + return 0 + ticks = ms / hw * TICKS_PER_MS - 1 # model ticks the unit plays in ms; +1 as the OS counts + s = int(round(levels * 2 ** 23 / ticks)) + 32768 + n = 32 - s.bit_length() - 1 # clz(s) - 1: s << n has bit 30 as its top bit + m = (0x7FFFFFFF - (s << n)) >> 14 + if packed: + m = min((m + 8) >> 4, 0xFFF) << 4 # packed: the low 4 bits of m are dropped + assert 0 <= n <= 15 and 0 <= m <= 0xFFFF, (ms, s, n, m) + return (n << 16) | m + + +def pack(c): + return (c >> 16) << 12 | (c & 0xFFFF) >> 4 + + +def ramp_ms(c, levels=LEVELS, hw=HW_SPEED): + return ((levels << 23) / step_of(c) + 1) / TICKS_PER_MS * hw + + +def main(): + t = curve_ms() + table = [encode(x) for x in t] + ftable = [encode(x, F_LEVELS, F_HW_SPEED, False) for x in t] + if '--check' in sys.argv: + for k, (want, c) in enumerate(zip(t, table)): + got = ramp_ms(c) + err = 0 if want == 0 else (got - want) / want * 100 + print(f'{k:3d} want {want:10.3f} ms c {c:05x} got {got:10.3f} ms {err:+.2f} %') + for k, (want, c) in enumerate(zip(t, ftable)): + got = ramp_ms(c, F_LEVELS, F_HW_SPEED) + err = 0 if want == 0 else (got - want) / want * 100 + print(f'{k:3d} want {want:10.3f} ms c {c:05x} got {got:10.3f} ms {err:+.2f} %') + return + print('// Generated by tools/atk_table.py: AMP ATK knob -> linear rate coefficient, packed.') + print('static const uint16_t atk_rate[128] = {') + for i in range(0, 128, 8): + print(' ' + ', '.join(f'0x{pack(c):04x}' for c in table[i:i + 8]) + ',') + print('};') + print() + print('// The same curve for FILTER ATK (0 -> 127 levels), read by stock through the lea at') + print('// 0x4010d2a8 (attack record and the state counter alike), so full 32-bit words.') + print('const uint32_t envelopes_flt_atk[128] __attribute__((aligned(4))) = {') + for i in range(0, 128, 8): + print(' ' + ', '.join(f'0x{c:05x}' for c in ftable[i:i + 8]) + ',') + print('};') + + +if __name__ == '__main__': + main() diff --git a/sdk/analog-rytm-mkii/modules/envelopes/tools/dec_table.py b/sdk/analog-rytm-mkii/modules/envelopes/tools/dec_table.py new file mode 100644 index 000000000..43edf8c3f --- /dev/null +++ b/sdk/analog-rytm-mkii/modules/envelopes/tools/dec_table.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Envelopes' DEC/REL and HLD curves: knob value -> table word for the stock readers. + +DEC/REL (AMP DEC, FILTER DEC and REL; the time to fall 60 dB, shown as the knob text): + 0 instant + 1..25 0.2 ms steps, 0.2 .. 5 ms + 26..45 0.5 ms steps, 5.5 .. 15 ms + 46..55 1 ms steps, 16 .. 25 ms + 56..65 2.5 ms steps, 27.5 .. 50 ms + 66..75 5 ms steps, 55 .. 100 ms + 76..85 10 ms steps, 110 .. 200 ms + 86..89 25 ms steps, 225 .. 300 ms + 90..127 x r a step, to 45 s (AMP 127 stays INF, which the OS never reads) +A word is a 20-bit coefficient (e = c[19:16], m = c[15:0]) of a one-pole segment: per tick +(2 samples) the level keeps p = 1 - step / 2^31 of its distance, step = ((0x7FFFFFFF - (m << 14)) +>> e) - 32768, as stock's tables (0x401ee61c, 0x401ef030). The AMP table is built for the unit's +VCA, which decays at 0.918x the OS model (EV06); the filter runs at the model's speed (EV13). + +HLD (AMP hold, 0 = no hold, 127 = AUTO): ticks of 2 samples, whole blocks of 16 ticks (0.667 ms), +as the OS counts the hold: + 1..15 1 block, 0.67 .. 10 ms + 16..21 2 blocks, 11.3 .. 18 ms + 22..31 5 blocks, 21.3 .. 51.3 ms + 32..42 10 blocks, 58 .. 124.7 ms (42 is the FUNC jump's 16th at 120 BPM) + 43..46 10 blocks, 131 .. 151 ms + 47..57 20 blocks, 164 .. 298 ms + 58..126 x r a step, to 15.5 s + +Prints the C tables for src/envelopes.c; --check prints each value back from the table. +""" +import math +import sys + +TICKS_PER_MS = 24.0 +HW_AMP, HW_FLT = 0.918, 1.0 # decay speed against the OS model (EV06, EV13) +DEC_TOP_MS = 45000.0 # stock DEC 127's time to -60 dB +HLD_TOP_MS = 15500.0 # stock HLD 126 +BLOCK = 16 # ticks the hold counter moves a block + + +def dec_ms(): + segs = [(0.2, 5.0), (0.5, 15.0), (1.0, 25.0), (2.5, 50.0), (5.0, 100.0), (10.0, 200.0), (25.0, 300.0)] + t = [0.0] + for step, end in segs: + n = round((end - t[-1]) / step) + base = t[-1] + for i in range(1, n + 1): + t.append(base + step * i) + k0 = len(t) - 1 + r = (DEC_TOP_MS / t[k0]) ** (1.0 / (127 - k0)) + for k in range(k0 + 1, 128): + t.append(t[k0] * r ** (k - k0)) + assert len(t) == 128 + return t + + +def hld_blocks(): + b = [0] + for step, count in [(1, 15), (2, 6), (5, 10), (10, 11), (10, 4), (20, 11)]: + for _ in range(count): + b.append(b[-1] + step) + k0 = len(b) - 1 + top = HLD_TOP_MS * TICKS_PER_MS / BLOCK + r = (top / b[k0]) ** (1.0 / (126 - k0)) + for k in range(k0 + 1, 127): + b.append(max(b[-1] + 1, round(b[k0] * r ** (k - k0)))) + b.append(b[-1]) # 127 is AUTO: never read + assert len(b) == 128 + return b + + +def encode(ms, hw): + """20-bit coefficient whose one-pole falls 60 dB in ms (the unit's time, at speed hw).""" + if ms <= 0: + return 0 + ticks = ms / hw * TICKS_PER_MS + p = math.exp(-math.log(1000.0) / ticks) + s = int(round((1.0 - p) * 2 ** 31)) + 32768 + n = 32 - s.bit_length() - 1 + m = (0x7FFFFFFF - (s << n)) >> 14 + assert 0 <= n <= 15 and 0 <= m <= 0xFFFF, (ms, s, n, m) + return (n << 16) | m + + +def t60_ms(c, hw): + e, m = (c >> 16) & 15, c & 0xFFFF + s = ((0x7FFFFFFF - (m << 14)) >> e) - 32768 + p = 1.0 - s / 2 ** 31 + return math.log(1000.0) / -math.log(p) / TICKS_PER_MS * hw if 0 < p < 1 else 0.0 + + +def emit(name, words, decl, comment, fmt): + print(comment) + print(f'{decl} {name}[128] __attribute__((aligned(4))) = {{') + for i in range(0, 128, 8): + print(' ' + ', '.join(fmt(w) for w in words[i:i + 8]) + ',') + print('};') + print() + + +def main(): + t = dec_ms() + amp = [encode(x, HW_AMP) for x in t] + flt = [encode(x, HW_FLT) for x in t] + hold = [b * BLOCK for b in hld_blocks()] + if '--check' in sys.argv: + for k in range(128): + ga, gf = t60_ms(amp[k], HW_AMP), t60_ms(flt[k], HW_FLT) + ea = 0 if t[k] == 0 else (ga - t[k]) / t[k] * 100 + ef = 0 if t[k] == 0 else (gf - t[k]) / t[k] * 100 + print(f'{k:3d} DEC want {t[k]:9.2f} ms amp {amp[k]:05x} {ga:9.2f} ({ea:+.2f} %) ' + f'flt {flt[k]:05x} {gf:9.2f} ({ef:+.2f} %) HLD {hold[k] / TICKS_PER_MS:8.2f} ms') + return + print('// Generated by tools/dec_table.py.') + emit('envelopes_amp_dec', amp, 'const uint32_t', + '// AMP DEC knob -> one-pole coefficient (time to -60 dB, for the VCA at 0.918x), read by stock\n' + '// through the lea at 0x40109bee.', lambda w: f'0x{w:05x}') + emit('envelopes_flt_dec', flt, 'const uint32_t', + '// FILTER DEC and REL knob -> coefficient (the filter runs at the OS model\'s speed), read by\n' + '// stock through the leas at 0x4010d342, 0x4010d3b2 and 0x4010d3da.', lambda w: f'0x{w:05x}') + emit('envelopes_hold', hold, 'const int32_t', + '// AMP HLD knob -> hold in ticks of 2 samples, whole blocks of 16, read by stock through the lea\n' + '// at 0x40109bc2 and by envelopes_voice. 0 is no hold and 127 AUTO (not read).', lambda w: f'{w}') + + +if __name__ == '__main__': + main() diff --git a/sdk/analog-rytm-mkii/modules/envelopes/upstream/README.md b/sdk/analog-rytm-mkii/modules/envelopes/upstream/README.md new file mode 100644 index 000000000..fd60fee80 --- /dev/null +++ b/sdk/analog-rytm-mkii/modules/envelopes/upstream/README.md @@ -0,0 +1,33 @@ +# Improved Envelopes: origin + +Improved Envelopes is Nathaniel Pavel's (@npp1993) own module, written for Modwerk; it is not a port +and has no upstream release. It began as an extension of Iced-Cream's Env Reset Amp (MrScruff's +rytm-mods 0015, ported to elemod), whose reading of the amp envelope's commands it builds on. From +0.1.0 it carries none of that module's code: ENV RESET AMP is Env Reset Amp's alone +(`../../env-reset-amp/`), and the two install side by side. + +## Sites (OS 1.74) + +| addr | len | op | what | +|---|---|---|---| +| 0x40109c24 | 8 | jmp `envelopes_write` | the amp record write in `amp_env_commands`, every voice every block | +| 0x400d3302 | 6 | jsr `envelopes_amp_graph` | the AMP page's envelope picture (HLD 127 drawn as AUTO, 0 as no hold) | +| 0x4010d2aa | 4 | ptr `envelopes_flt_atk` | FILTER ATK rate table | +| 0x40109bc4 | 4 | ptr `envelopes_hold` | AMP HLD compare | +| 0x40109bf0 | 4 | ptr `envelopes_amp_dec` | AMP DEC record | +| 0x4010d344, 0x4010d3b4, 0x4010d3dc | 4 each | ptr `envelopes_flt_dec` | FILTER DEC and REL records | + +Addresses, lengths and SHA-256 guards are in `../build.json`; no stock bytes are included. + +## Beside Env Reset Amp + +Env Reset Amp takes state 0's `ori.l` at 0x40109b70 and, with AMP in a track's ENV RESET, sends a +drop to silence (rate 0xb0100000, level 0x80) in place of the trig's attack record (level +0x4000007f). Envelopes then never sees that record, so stock picks the state after the trig as it +always does (0x40109b5a: HLD byte 0 gives state 1, the gate; any other value state 2, timed), with +the hold counter cleared. From the next block the voice sends the continued attack (level 0x7f), +which Envelopes rewrites to its ATK curve and on which it applies its HOLD rule: HLD 127 turns +state 2 into the gate, any other value sets 2 or 3 by the hold time and the attack's top. Its level +follower takes the drop as an instant one-pole fall to -128. So the attack still runs from silence +on Envelopes' curve, the hold still counts from the trig, and the decay never starts before the +top. Read in OS 1.74's code, not yet run on the unit. diff --git a/sdk/imports/elekloader-envelopes-2026-11-07.json b/sdk/imports/elekloader-envelopes-2026-11-07.json new file mode 100644 index 000000000..5ebfb63ce --- /dev/null +++ b/sdk/imports/elekloader-envelopes-2026-11-07.json @@ -0,0 +1,34 @@ +{ + "schema": 1, + "kind": "elekloader-catalog", + "revision": "envelopes-2026-11-07", + "about": "Modwerk source-only envelopes package for Analog Rytm MKII OS 1.74, compiled from sdk/analog-rytm-mkii/modules/envelopes.", + "cores": [], + "mods": [ + { + "file": "envelopes-0.1.0-experimental-os1.74.elemod", + "sha256": "3c5016aebe69610e3406a0d6839fdb91a1a97caf6b3538907c2e16d76046dc30", + "id": "envelopes", + "version": "0.1.0-experimental", + "device": "analog-rytm-mk2", + "os": "1.74", + "license": "GPL-2.0-only", + "source": { + "repo": "repeat98/modwerk", + "commit": "2c53a2207051e8258e4a37e4cc5dba10adb211fa", + "path": "sdk/imports/packages/envelopes/envelopes-0.1.0-experimental-os1.74.elemod" + }, + "title": "Improved Envelopes", + "requires": [ + "core" + ], + "conflicts": [], + "summary": "The AMP envelope reworked: HOLD runs from no hold to AUTO, ATK, HOLD and DEC/REL have fine steps from 0.2 ms, and the envelope knobs show times.", + "description": "The AMP envelope reworked: HOLD runs from no hold to AUTO, ATK, HOLD and DEC/REL have fine steps from 0.2 ms, and the envelope knobs show times.", + "category": "Synthesis", + "author": "npp1993", + "needs_core": "0.7", + "on_unit": "This source's curves ran on the author's Analog Rytm MKII on OS 1.74 in a 16-module combined image (ED02, measured over USB: AMP DEC, AMP HLD, FILTER DEC and REL); this package has not yet run on a unit." + } + ] +} diff --git a/sdk/imports/envelopes-2026-11-07.json b/sdk/imports/envelopes-2026-11-07.json new file mode 100644 index 000000000..4992f1920 --- /dev/null +++ b/sdk/imports/envelopes-2026-11-07.json @@ -0,0 +1,20 @@ +{ + "schemaVersion": 1, + "module": "analog-rytm-mkii/envelopes", + "version": "0.1.0-experimental", + "date": "2026-11-07", + "changes": [ + "First release. The AMP envelope reworked: AMP HLD runs from no hold (0) to AUTO (127) without cutting the attack; ATK, AMP DEC, FILTER DEC and REL and AMP HLD get fine steps at short times (0.2 ms to 5 ms); every envelope knob shows its time; FUNC + turn on HLD steps 0, 124.7 ms, AUTO. Installs beside Env Reset Amp, which keeps ENV RESET AMP." + ], + "runtimeSourceSha256": "bc9eb9aa91bbe8c8db6e0c3ad171e6b1edeefda50d2c46561711a695e4bc2474", + "packages": [ + { + "os": "1.74", + "path": "sdk/imports/packages/envelopes/envelopes-0.1.0-experimental-os1.74.elemod", + "sha256": "3c5016aebe69610e3406a0d6839fdb91a1a97caf6b3538907c2e16d76046dc30" + } + ], + "stockPolicy": "No firmware or stock-derived code/tables are present. Addresses, lengths and SHA-256 guards are metadata; exported instructions and sections are authored module code. Local materialized recipes and firmware are never committed.", + "evidence": "sdk/analog-rytm-mkii/modules/envelopes/TESTING.md", + "requires": [] +} diff --git a/sdk/imports/packages/envelopes/envelopes-0.1.0-experimental-os1.74.elemod b/sdk/imports/packages/envelopes/envelopes-0.1.0-experimental-os1.74.elemod new file mode 100644 index 000000000..77f987e78 --- /dev/null +++ b/sdk/imports/packages/envelopes/envelopes-0.1.0-experimental-os1.74.elemod @@ -0,0 +1,513 @@ +{ + "elemod": 2, + "id": "envelopes", + "version": "0.1.0-experimental", + "title": "Improved Envelopes", + "description": "The AMP envelope reworked: HOLD runs from no hold to AUTO, ATK, HOLD and DEC/REL have fine steps from 0.2 ms, and the envelope knobs show times.", + "category": "machines", + "author": "npp1993", + "license": "GPL-2.0-only", + "target": { + "device": "analog-rytm-mk2", + "product": "Analog Rytm MKII", + "os": "1.74", + "syx_sha256": "cb0f99d620edde994fb860c0e79cc2464e427d06882828cfdfb00fc53700e123", + "section3_sha256": "34cd0c4da8e4b810d1b25284475a99ea53821e2ab7d30a978bf15dc578251f3b", + "section3_len": 3005432 + }, + "sections": { + ".run": { + "align": 4, + "len": 5136, + "parts": [ + [ + "hex", + "2239000000000701671241f900000000b0b03c006606b4b03c2067284feffff048d703032f022f002f0c2f0a2f034eb9000000002f4000144fef00144cd703034fef0010274000ac274000ec4ef940109c2c202f001c0c8000007f006d0642af001c600a4a806e0670012f40001c4ef940038bce598f2f03701c2f027602222f00142401e0aa70f7206f0010c082b680670000b84a806700009c44c266000106202f0018e088710048404240224020802401760ee7aa7614712f001b484042402f4200082401e6a942030282000fffff214000041143000d7601c283262f00084242484202833fffc0001141000c223c7fffffff9283e4a90681ffff80002141000824009489b3c0672222424a826d7c222800084a28000c663ee6896778b3c1630893c14a826d7690892080241f261f588f4e754a28000d66f220280004240022509489b3c066c260e270011140000d241f261f588f4e750c8107ffffff62caec89263c0000045c4c031800760fe6a952810c810001000064b02609e08b4c031800e08993c14a826c9e60122202448122416000ff7c7201b3c16286608cd0892080608822507609b6826600ff042401760ee7aa7614202800042f4200082401e6a942030282000fffff1143000d7601c283262f00084242484202833fffc0001141000c223c7fffffff9283e4a90681ffff8000214100086000ff082f02202f000c2200e0814aaf00106f1a4a816d267429b4816c30747eb4816c200280ffffff00241f4e75747fb4816d10742ab4816d144a816fe6241f42804e75241f203c00007f004e75241f203c00002a004e754fefffe448d7401c222f0024780a2401260f4c4420004c4420024def001206830000001c204e43e8000106800000003010807009b08164262202b68967202401780a4c4420004c442002204943e8000106800000003010807009b08165da7001908e226f0020d08841f6080012e0bdc866fa4cd7401cd0af00204fef001c4e75226f0008206f0004101910c066fa4e75518f2f02202f0014206f00100c8002625a006306203c02625a00720a74174c00180006810000000cb481645c0c81000012bf630000a8220006810000000c0c810000bb7f630000de22000681000004b00c8100249eff6346068000002ee0243c00005dc04c4200002f002f084ebaff02508f223c000000002f4100142f400010241f508f6000ff6a203c000000002f4000142f480010241f508f6000ff54243c000009604c42100143fafec6700a4c4010024c4010012f012f082f49000c4e91204010fc002e2f022f08226f00144e914fef0010223c00000000609c74184c42100143fafe8c700a4c4010024c4010012f012f082f49000c4e91204010fc002e2f022f08226f00144e914fef0010223c000000002f4100142f400010241f508f6000fece74184c4210012f012f084ebafe40508f223c000000002f4100142f400010241f508f6000fea84fefffec48d7007c242f00182202d2816afcd48242414841307c001e4284700f04c291c2340176017c0fe1ab5380c4c12202eca94a826c08220242414841888374ffb48066de200848404240d0844cd7007c4fef00144e752f02740e222f00082001e5a8740f4241484102803fffc0002040203c7fffffff9088e2a822000681ffff8000b48164560c8101ffffff632c0c817ffeffff6250203c7fffffff90812f004ebaff5c721f4841588f9280203c0009f73e4c410000241f4e757412e888e4a93401c4c122000681fffff800203c37431533d2824c410000241f4e75241f203c7fffffff4e75241f42804e752f02740e202f000c2200e5a94240484002813fffc0002041720fc081223c7fffffff9288e0a90681ffff80004a816712202f00087417e5a84c410000241f52804e75241f203c7fffffff4e75518f2f0a727f2f02206f001420280008e0802240b2806c30327c007f2228000474080681ffffffceb4816532323b1a0848c14efb180200f2003200280032002800280120006600904a806cd02228000493c90681ffffffce7408b48164ce241f4280245f508f4e7545f90000000020329c000280000fffff2f002f48000c4ebafe9e206f000c2e802f28000c4ebafcee508f7001241f245f508f4e752228000c747eb4806d00013c4a806f00011241f90000000020309c002f002f014ebafcbe508f700160ce2068000c727e2f48000cb2806d0000fa41f90000000024309c000282000fffff2f02740a4ebafe32588f2200e4a90280000003ff243c000003ac4c021800740ac0fc03ac068000000200e4a82040487018002f2f00104ebafc5e508f70016000ff6e45f9000000002f329c004878007f2f4800104ebafe78588f206f000c2e802f28000c4ebafc30508f70016000ff4045f900000000740c71f29a002200e4a8e989740a4840424073c180812f00487800ff2f4800104ebafe36508f2200e4a90280000003ff243c000003d44c021800740ac0fc03d4068000000200e4a8224048711800206f000c2f28000c4ebafbc8508f70016000fed842802f002f014ebafbb6508f6000fef64879000000002f084ebafb94508f70016000feb44879000000002f014ebafb80508f70016000fea04fefffc8700748d7041cc0af003c206f00447401262f004c73280002e1aa2f41001e3200c2fc000e0681000000000c834000007f67000158760e4c00380043f900000000283138002f440022787fb8af004c6700008a41f1380028280008246800042f4400261828000c2f4a001a45e800041f4400321828000d1f4400332f2f004c2f2f004c2f4a00262808508445e8000c41e8000d2f4a00362f48003c2f4400322f012f40001e2f4900224ebaf86c4fef000c226f0016222f0022202f0012b2b13800670001184682c5b900000000202f00484cd7041c4fef00384e75282f004871100284fff0000020444a806d00018643f900000000780c71f10a002600e8a8e98b787f4840424077c3808326088083b8af001e67000188226f0040760220515388b688650001824aaf001e6f000196282f001e41f90000000022704c00246f0040206a000441e8fff1b3c86c00019c262f00220c83007e7fff6f00018e307c00034878007f2f00226f004822882f012f40001e4ebaf7a24fef000c202f00124682c5b9000000006000ff48262f004871100283fff0000020434a806d00009e43f900000000780c71f10a002600e8a8e98b787f4840424077c3808326088083b8af001e670000a0307c00022f3c4000007f2f00226f004822886092222f001a246f001eb2926600fede226f0026206f002ab3d06600fed0246f002e73af00327792b6816600fec0206f003473af00337790b2836600feb0222f004841f900000000262f004c21810c1021830c30859020014cd7041c4fef00384e75428043f900000000780c71f10a002600e8a8e98b787f4840424077c3808326088083b8af001e6600ff64307c00012f3c4000007f6000ff60428043f900000000780c71f10a002600e8a8e98b787f4840424077c3808326088083b8af001e6600fe7c206f00407602b69067224878007f2f002f012f40001e4ebaf6624fef000c202f00126000febe93c96000fe764878007f76012f0020832f012f40001e4ebaf63c4fef000c202f00126000fe98307c00024878007f6000fe72203c00000000b0b9419a5b8c6718203c400fe58423c0419a5b88203c0000000023c0419a5b8c4e750000ff800000ff800000000000000000ff800000ff800000000000000000ff800000ff800000000000000000ff800000ff800000000000000000ff800000ff800000000000000000ff800000ff800000000000000000ff800000ff800000000000000000ff800000ff800000000000000000000000320000003a00000000000000000000000000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f00000011000000130000001500000017000000190000001b00000020000000250000002a0000002f00000034000000390000003e00000043000000480000004d00000057000000610000006b000000750000007f00000089000000930000009d000000a7000000b1000000bb000000c5000000cf000000d9000000e3000000f70000010b0000011f00000133000001470000015b0000016f0000018300000197000001ab000001bf000001d9000001f5000002130000023200000253000002760000029b000002c3000002ec000003190000034700000379000003ad000003e50000041f0000045d0000049f000004e50000052f0000057d000005d00000062800000685000006e70000074f000007bd00000832000008ae00000931000009bb00000a4e00000aea00000b8e00000c3d00000cf500000db900000e8800000f630000104b0000114100001246000013590000147d000015b2000016fa00001855000019c400001b4800001ce400001e98000020660000224e0000245400002678000028bd00002b2300002dae0000305f000033390000363e0000397100003cd300004069000044350000483a00004c7c000050fe000055c400005ad200005ad2000000000000079680000f951000179ce0001ca900001ffe000024b38000283620002aeca0002d1540002ed76000309a300033108000352b300036fcf0003893900039f9b0003b3770003c5350003d5270003e38f0003f0a40003fc9300040f01000423180004359f00045e330004802d00049d0b0004b5de0004cb730004de620004ef200004fe09000516ce00052ee7000544bc0005589c00056ac700057b7300058acd000598fd0005a6220005b25a0005bdbc0005c85e0005dba70005ecb00005fbd9000612da00062b4e0006417300065597000667fe000678df000688680006aa540006c69f0006de950006f322000709eb0007291f000744ab00075d2a00077316000786d40007a8ef0007c55f0007dd710007f215000807f400082747000842eb00085b7f0008717d000885490008a77d0008c3ff0008dc1f0008f0ce00090579000924da0009408b0009592900096f30000983040009ace00009ce5e0009e9c6000a013b000a3f51000a75bc000aa570000acf40000af3e6000b280c000b605d000b91b9000bbcfd000be2e9000c084d000c4291000c75a2000ca265000cc9a1000cec04000d144f000d4924000d7772000da008000dc39b000de2c9000dfe1c000e2c20000e561d000e7ae9000e9b2a000eb76f000ed035000ee5ed000ef8f5000f1349000f3087000f4a27000f609f000f744f000f8591000f94b1000000000000889d000110f20001933b0001dff50002246e00026b3900029fbb0002c8360002e85d0003050800033065000354ef0003742300038f180003a69d0003bb510003cdae0003de140003eccf0003fa1d00040c640004226b000436960004491f00045a3600047fa100049ef30004b98d0004d06c0004e44c0004f5bb00050a4a000525bd00053e57000554840005689b00057ae400058b9b00059af10005a9100005b61c0005c2330005cd6f0005d7e60005e1ad0005f368000606240006220300063af800065172000665cb0006784e00068937000698ba0006a7000006c62b0006e02a0006f62d0007121e000732dc00074f86000768d300077f53000793760007a5970007c4eb0007df080007f523000810300008310d00084dd00008673200087dc3000891f50008a4220008c38a0008ddb70008f3de00090db900092ea500094b74000964e100097b7b00098fb40009a1e80009c8570009e716000a0080000a2a71000a6370000a9566000ac131000ae793000b1272000b4d6e000b8121000bae71000bd629000bf8f9000c2ef9000c6477000c9358000cbc70000ce074000d000b000d3761000d67e1000d9263000db7a5000dd84d000df4ec000e1c05000e47ff000e6e8a000e9052000eaded000ec7e1000ede9f000ef28f000f0811000f26b1000f4189000f5911000f6daf000f7fc3000f8f9b000f9d7d000000000002f499000427510004d0970005412800059e5b0005db6000060cd900064cd800067e490006a5a00006c5ad0006e04a0006f6be000713e1000735150007521400076ba00007824c0007968e0007a8c40007b93a0007c82d0007d5d00007e24e0007edca00080da20008374900085a7d000878a2000892bc0008a98f0008bdaf0008cf900008df8c0008edee0008faf000090d8500092319000936de0009490e000959d60009695f000977cc00098538000991bf0009b1e20009cf290009e9d6000a044a000a3095000a58f2000a7dba000a9f3e000abdc9000ad99f000af2fc000b1435000b3e58000b64bf000b87be000ba7a4000bc4b6000bdf36000bf75c000c1abe000c42dd000c676f000c88c4000ca725000cc2d6000cdc13000cf315000d101a000d3653000d592a000d78ec000d95de000db040000dc84d000dde38000df233000e08d4000e2a07000e484a000e63df000e7d04000e93ef000ea8d2000ebbdd000ecd38000edd0a000eeb75000ef89a000f092b000f1f03000f32eb000f4511000f559b000f64af000f726d000f7ef5000f8a5f000f94c9000f9e45000fa6eb000faecd000fb5fb000fbc89000fc281000fc7f1000fcce7000fd16d000fd58b000fd94d000fdcb9000fdfd9000fe2b1000fe549000fe7a7000fe9cd000febc5000fed8f000fef31000ff0ad000ff209000ff345000ff4650000203f33c33dd8453d4ad04e76521555ea58e05b3c5d285ec06032627f647d663a67c3691f6a566b6d6c6a6d506e216ee16f92722474a476c178907a217b807cb57dc87ebd7f9a80c58230837c84ab85c386c587b4889189608a208c0e8dd08f6a91bf946896d499099b0d9ce29e8ea028a2eea576a7c4a9deabc9ad87af1fb124b3c9b631b863ba64bc37bde0bf64c18cc411c65cc874ca5ccc19cdafcf20d0e3d349d579d777d948daf0dc73ddd3df14e072e288e46ee629e7bde92eea7debafecc6edc5eeacef80f081f1e0f320f443f54df640f71cf7e6f89df945f9ddfa68fae7fb5afbc4fc24fc7bfccbfd13fd56fd92fdc9fdfbfe29fe53fe79fe9b30006d730073004155544f00494e4600" + ] + ] + }, + ".bss": { + "align": 16, + "size": 80 + } + }, + "symbols": { + "WRITE_RESUME": [ + "abs", + 1074830380 + ], + "AMP_PICTURE": [ + "abs", + 1073974222 + ], + "follow": [ + ".run", + 116 + ], + "hold_jump": [ + ".run", + 508 + ], + "put_uint": [ + ".run", + 592 + ], + "put_text": [ + ".run", + 720 + ], + "put_time": [ + ".run", + 736 + ], + "log2_q16": [ + ".run", + 1066 + ], + "t60_ticks": [ + ".run", + 1154 + ], + "linear_ticks": [ + ".run", + 1304 + ], + "envelopes_text": [ + ".run", + 1380 + ], + "atk_rate": [ + ".run", + 4864 + ], + "amps": [ + ".run", + 2684 + ], + "envelopes_amp_graph": [ + ".run", + 82 + ], + "envelopes_flt_dec": [ + ".run", + 3328 + ], + "envelopes_flt_atk": [ + ".run", + 4352 + ], + "envelopes_gate": [ + ".bss", + 0 + ], + "envelopes_voice": [ + ".run", + 1882 + ], + "envelopes_amp_dec": [ + ".run", + 3840 + ], + "envelopes_write": [ + ".run", + 0 + ], + "envelopes_hold": [ + ".run", + 2816 + ], + "envelopes_params": [ + ".run", + 2796 + ], + "envelopes_tick": [ + ".run", + 2642 + ] + }, + "exports": [ + "envelopes_amp_dec", + "envelopes_amp_graph", + "envelopes_flt_atk", + "envelopes_flt_dec", + "envelopes_gate", + "envelopes_hold", + "envelopes_params", + "envelopes_tick", + "envelopes_voice", + "envelopes_write" + ], + "imports": [], + "weak": [], + "relocs": [ + [ + ".run", + 2, + "abs32", + "sec:.bss", + 0 + ], + [ + ".run", + 12, + "abs32", + "sec:.bss", + 16 + ], + [ + ".run", + 48, + "abs32", + "sec:.run", + 1882 + ], + [ + ".run", + 852, + "abs32", + "sec:.run", + 5125 + ], + [ + ".run", + 874, + "abs32", + "sec:.run", + 5120 + ], + [ + ".run", + 950, + "abs32", + "sec:.run", + 5125 + ], + [ + ".run", + 1008, + "abs32", + "sec:.run", + 5122 + ], + [ + ".run", + 1046, + "abs32", + "sec:.run", + 5122 + ], + [ + ".run", + 1486, + "abs32", + "sec:.run", + 3328 + ], + [ + ".run", + 1556, + "abs32", + "sec:.run", + 2816 + ], + [ + ".run", + 1596, + "abs32", + "sec:.run", + 3840 + ], + [ + ".run", + 1678, + "abs32", + "sec:.run", + 4352 + ], + [ + ".run", + 1724, + "abs32", + "sec:.run", + 4864 + ], + [ + ".run", + 1844, + "abs32", + "sec:.run", + 5132 + ], + [ + ".run", + 1864, + "abs32", + "sec:.run", + 5127 + ], + [ + ".run", + 1924, + "abs32", + "sec:.run", + 2684 + ], + [ + ".run", + 1946, + "abs32", + "sec:.run", + 2684 + ], + [ + ".run", + 2086, + "abs32", + "sec:.bss", + 0 + ], + [ + ".run", + 2126, + "abs32", + "sec:.run", + 4864 + ], + [ + ".run", + 2194, + "abs32", + "sec:.run", + 2816 + ], + [ + ".run", + 2272, + "abs32", + "sec:.bss", + 0 + ], + [ + ".run", + 2302, + "abs32", + "sec:.run", + 4864 + ], + [ + ".run", + 2426, + "abs32", + "sec:.bss", + 0 + ], + [ + ".run", + 2460, + "abs32", + "sec:.run", + 4864 + ], + [ + ".run", + 2516, + "abs32", + "sec:.run", + 4864 + ], + [ + ".run", + 2644, + "abs32", + "sec:.run", + 508 + ], + [ + ".run", + 2670, + "abs32", + "sec:.run", + 508 + ], + [ + ".run", + 2812, + "abs32", + "sec:.run", + 1380 + ] + ], + "sites": [ + { + "addr": "0x40109c24", + "len": 8, + "stock_sha256": "3df81daa6f0b5b044b3c5bbf7c45a8d96b97caa686b60bf262eeda8b7dc87dc5", + "new": "4ef9000000004e71", + "kind": "code", + "relocs": [ + [ + 2, + "abs32", + "sym:envelopes_write", + 0 + ] + ] + }, + { + "addr": "0x400d3302", + "len": 6, + "stock_sha256": "704bb7e62b7dc84ac22bb9ec1242ae79e917ce47a7b6a4aa06ec60b72654b2c0", + "new": "4eb900000000", + "kind": "code", + "relocs": [ + [ + 2, + "abs32", + "sym:envelopes_amp_graph", + 0 + ] + ] + }, + { + "addr": "0x4010d2aa", + "len": 4, + "stock_sha256": "bb101f17b7e23130f5e92c2f8e616cf858c9db923af32f0756f4cd9af27591d0", + "new": "00000000", + "kind": "data", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_flt_atk", + 0 + ] + ] + }, + { + "addr": "0x40109bc4", + "len": 4, + "stock_sha256": "a93a6f0c7e9387b5bf90a027d5678b11c90d555082736705d44faca3b73d46b6", + "new": "00000000", + "kind": "data", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_hold", + 0 + ] + ] + }, + { + "addr": "0x40109bf0", + "len": 4, + "stock_sha256": "8879f37f45bb49679a30176c392dc823a5215ba1fdbecc9406684fec9adbc2aa", + "new": "00000000", + "kind": "data", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_amp_dec", + 0 + ] + ] + }, + { + "addr": "0x4010d344", + "len": 4, + "stock_sha256": "e752dd02ad509e30bf4eab19cb5a7ef8760906dfdfa6d410e8a42e3635a2ef39", + "new": "00000000", + "kind": "data", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_flt_dec", + 0 + ] + ] + }, + { + "addr": "0x4010d3b4", + "len": 4, + "stock_sha256": "e752dd02ad509e30bf4eab19cb5a7ef8760906dfdfa6d410e8a42e3635a2ef39", + "new": "00000000", + "kind": "data", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_flt_dec", + 0 + ] + ] + }, + { + "addr": "0x4010d3dc", + "len": 4, + "stock_sha256": "e752dd02ad509e30bf4eab19cb5a7ef8760906dfdfa6d410e8a42e3635a2ef39", + "new": "00000000", + "kind": "data", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_flt_dec", + 0 + ] + ] + } + ], + "collections": {}, + "contribute": [ + { + "to": "ev_tick", + "order": 50, + "data": "00000000", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_tick", + 0 + ] + ], + "claims": [] + }, + { + "to": "core_params", + "order": 50, + "data": "00000000", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_params", + 0 + ] + ], + "claims": [] + } + ], + "copied": [], + "resources": { + "regions": [], + "names": [ + "ui:FUNC + turn of AMP HLD", + "ui:value text of AMP ATK/HLD/DEC and FILTER ATK/DEC/REL", + "ui:AMP page envelope picture HLD", + "audio:amp envelope record write", + "audio:filter attack rate table", + "audio:amp hold table", + "audio:amp decay table", + "audio:filter decay and release table" + ], + "core": "0.3" + }, + "requires": [ + "core" + ], + "conflicts": [], + "signature": null +} diff --git a/src/community/module-changelogs/analog-rytm-mkii-envelopes.json b/src/community/module-changelogs/analog-rytm-mkii-envelopes.json new file mode 100644 index 000000000..24b19dbfe --- /dev/null +++ b/src/community/module-changelogs/analog-rytm-mkii-envelopes.json @@ -0,0 +1,13 @@ +[ + { + "version": "0.1.0-experimental", + "date": "2026-11-07", + "changes": [ + "First release, for Analog Rytm MKII OS 1.74: the AMP envelope reworked, and every envelope knob shows its time.", + "AMP HLD runs from no hold (0) to AUTO (127) and never cuts the attack; FUNC + turn jumps 0, 125 ms, AUTO.", + "ATK, AMP DEC and FILTER DEC/REL step 0.2 ms up to 5 ms, then finer than stock up to 300 ms; HLD steps 0.67 ms.", + "Installs beside Env Reset Amp, which keeps ENV RESET AMP.", + "Saved projects play their HLD, ATK, DEC and REL on the new curves." + ] + } +] diff --git a/vendor/elekloader/catalog/envelopes-0.1.0-experimental-os1.74.elemod b/vendor/elekloader/catalog/envelopes-0.1.0-experimental-os1.74.elemod new file mode 100644 index 000000000..77f987e78 --- /dev/null +++ b/vendor/elekloader/catalog/envelopes-0.1.0-experimental-os1.74.elemod @@ -0,0 +1,513 @@ +{ + "elemod": 2, + "id": "envelopes", + "version": "0.1.0-experimental", + "title": "Improved Envelopes", + "description": "The AMP envelope reworked: HOLD runs from no hold to AUTO, ATK, HOLD and DEC/REL have fine steps from 0.2 ms, and the envelope knobs show times.", + "category": "machines", + "author": "npp1993", + "license": "GPL-2.0-only", + "target": { + "device": "analog-rytm-mk2", + "product": "Analog Rytm MKII", + "os": "1.74", + "syx_sha256": "cb0f99d620edde994fb860c0e79cc2464e427d06882828cfdfb00fc53700e123", + "section3_sha256": "34cd0c4da8e4b810d1b25284475a99ea53821e2ab7d30a978bf15dc578251f3b", + "section3_len": 3005432 + }, + "sections": { + ".run": { + "align": 4, + "len": 5136, + "parts": [ + [ + "hex", + "2239000000000701671241f900000000b0b03c006606b4b03c2067284feffff048d703032f022f002f0c2f0a2f034eb9000000002f4000144fef00144cd703034fef0010274000ac274000ec4ef940109c2c202f001c0c8000007f006d0642af001c600a4a806e0670012f40001c4ef940038bce598f2f03701c2f027602222f00142401e0aa70f7206f0010c082b680670000b84a806700009c44c266000106202f0018e088710048404240224020802401760ee7aa7614712f001b484042402f4200082401e6a942030282000fffff214000041143000d7601c283262f00084242484202833fffc0001141000c223c7fffffff9283e4a90681ffff80002141000824009489b3c0672222424a826d7c222800084a28000c663ee6896778b3c1630893c14a826d7690892080241f261f588f4e754a28000d66f220280004240022509489b3c066c260e270011140000d241f261f588f4e750c8107ffffff62caec89263c0000045c4c031800760fe6a952810c810001000064b02609e08b4c031800e08993c14a826c9e60122202448122416000ff7c7201b3c16286608cd0892080608822507609b6826600ff042401760ee7aa7614202800042f4200082401e6a942030282000fffff1143000d7601c283262f00084242484202833fffc0001141000c223c7fffffff9283e4a90681ffff8000214100086000ff082f02202f000c2200e0814aaf00106f1a4a816d267429b4816c30747eb4816c200280ffffff00241f4e75747fb4816d10742ab4816d144a816fe6241f42804e75241f203c00007f004e75241f203c00002a004e754fefffe448d7401c222f0024780a2401260f4c4420004c4420024def001206830000001c204e43e8000106800000003010807009b08164262202b68967202401780a4c4420004c442002204943e8000106800000003010807009b08165da7001908e226f0020d08841f6080012e0bdc866fa4cd7401cd0af00204fef001c4e75226f0008206f0004101910c066fa4e75518f2f02202f0014206f00100c8002625a006306203c02625a00720a74174c00180006810000000cb481645c0c81000012bf630000a8220006810000000c0c810000bb7f630000de22000681000004b00c8100249eff6346068000002ee0243c00005dc04c4200002f002f084ebaff02508f223c000000002f4100142f400010241f508f6000ff6a203c000000002f4000142f480010241f508f6000ff54243c000009604c42100143fafec6700a4c4010024c4010012f012f082f49000c4e91204010fc002e2f022f08226f00144e914fef0010223c00000000609c74184c42100143fafe8c700a4c4010024c4010012f012f082f49000c4e91204010fc002e2f022f08226f00144e914fef0010223c000000002f4100142f400010241f508f6000fece74184c4210012f012f084ebafe40508f223c000000002f4100142f400010241f508f6000fea84fefffec48d7007c242f00182202d2816afcd48242414841307c001e4284700f04c291c2340176017c0fe1ab5380c4c12202eca94a826c08220242414841888374ffb48066de200848404240d0844cd7007c4fef00144e752f02740e222f00082001e5a8740f4241484102803fffc0002040203c7fffffff9088e2a822000681ffff8000b48164560c8101ffffff632c0c817ffeffff6250203c7fffffff90812f004ebaff5c721f4841588f9280203c0009f73e4c410000241f4e757412e888e4a93401c4c122000681fffff800203c37431533d2824c410000241f4e75241f203c7fffffff4e75241f42804e752f02740e202f000c2200e5a94240484002813fffc0002041720fc081223c7fffffff9288e0a90681ffff80004a816712202f00087417e5a84c410000241f52804e75241f203c7fffffff4e75518f2f0a727f2f02206f001420280008e0802240b2806c30327c007f2228000474080681ffffffceb4816532323b1a0848c14efb180200f2003200280032002800280120006600904a806cd02228000493c90681ffffffce7408b48164ce241f4280245f508f4e7545f90000000020329c000280000fffff2f002f48000c4ebafe9e206f000c2e802f28000c4ebafcee508f7001241f245f508f4e752228000c747eb4806d00013c4a806f00011241f90000000020309c002f002f014ebafcbe508f700160ce2068000c727e2f48000cb2806d0000fa41f90000000024309c000282000fffff2f02740a4ebafe32588f2200e4a90280000003ff243c000003ac4c021800740ac0fc03ac068000000200e4a82040487018002f2f00104ebafc5e508f70016000ff6e45f9000000002f329c004878007f2f4800104ebafe78588f206f000c2e802f28000c4ebafc30508f70016000ff4045f900000000740c71f29a002200e4a8e989740a4840424073c180812f00487800ff2f4800104ebafe36508f2200e4a90280000003ff243c000003d44c021800740ac0fc03d4068000000200e4a8224048711800206f000c2f28000c4ebafbc8508f70016000fed842802f002f014ebafbb6508f6000fef64879000000002f084ebafb94508f70016000feb44879000000002f014ebafb80508f70016000fea04fefffc8700748d7041cc0af003c206f00447401262f004c73280002e1aa2f41001e3200c2fc000e0681000000000c834000007f67000158760e4c00380043f900000000283138002f440022787fb8af004c6700008a41f1380028280008246800042f4400261828000c2f4a001a45e800041f4400321828000d1f4400332f2f004c2f2f004c2f4a00262808508445e8000c41e8000d2f4a00362f48003c2f4400322f012f40001e2f4900224ebaf86c4fef000c226f0016222f0022202f0012b2b13800670001184682c5b900000000202f00484cd7041c4fef00384e75282f004871100284fff0000020444a806d00018643f900000000780c71f10a002600e8a8e98b787f4840424077c3808326088083b8af001e67000188226f0040760220515388b688650001824aaf001e6f000196282f001e41f90000000022704c00246f0040206a000441e8fff1b3c86c00019c262f00220c83007e7fff6f00018e307c00034878007f2f00226f004822882f012f40001e4ebaf7a24fef000c202f00124682c5b9000000006000ff48262f004871100283fff0000020434a806d00009e43f900000000780c71f10a002600e8a8e98b787f4840424077c3808326088083b8af001e670000a0307c00022f3c4000007f2f00226f004822886092222f001a246f001eb2926600fede226f0026206f002ab3d06600fed0246f002e73af00327792b6816600fec0206f003473af00337790b2836600feb0222f004841f900000000262f004c21810c1021830c30859020014cd7041c4fef00384e75428043f900000000780c71f10a002600e8a8e98b787f4840424077c3808326088083b8af001e6600ff64307c00012f3c4000007f6000ff60428043f900000000780c71f10a002600e8a8e98b787f4840424077c3808326088083b8af001e6600fe7c206f00407602b69067224878007f2f002f012f40001e4ebaf6624fef000c202f00126000febe93c96000fe764878007f76012f0020832f012f40001e4ebaf63c4fef000c202f00126000fe98307c00024878007f6000fe72203c00000000b0b9419a5b8c6718203c400fe58423c0419a5b88203c0000000023c0419a5b8c4e750000ff800000ff800000000000000000ff800000ff800000000000000000ff800000ff800000000000000000ff800000ff800000000000000000ff800000ff800000000000000000ff800000ff800000000000000000ff800000ff800000000000000000ff800000ff800000000000000000000000320000003a00000000000000000000000000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f00000011000000130000001500000017000000190000001b00000020000000250000002a0000002f00000034000000390000003e00000043000000480000004d00000057000000610000006b000000750000007f00000089000000930000009d000000a7000000b1000000bb000000c5000000cf000000d9000000e3000000f70000010b0000011f00000133000001470000015b0000016f0000018300000197000001ab000001bf000001d9000001f5000002130000023200000253000002760000029b000002c3000002ec000003190000034700000379000003ad000003e50000041f0000045d0000049f000004e50000052f0000057d000005d00000062800000685000006e70000074f000007bd00000832000008ae00000931000009bb00000a4e00000aea00000b8e00000c3d00000cf500000db900000e8800000f630000104b0000114100001246000013590000147d000015b2000016fa00001855000019c400001b4800001ce400001e98000020660000224e0000245400002678000028bd00002b2300002dae0000305f000033390000363e0000397100003cd300004069000044350000483a00004c7c000050fe000055c400005ad200005ad2000000000000079680000f951000179ce0001ca900001ffe000024b38000283620002aeca0002d1540002ed76000309a300033108000352b300036fcf0003893900039f9b0003b3770003c5350003d5270003e38f0003f0a40003fc9300040f01000423180004359f00045e330004802d00049d0b0004b5de0004cb730004de620004ef200004fe09000516ce00052ee7000544bc0005589c00056ac700057b7300058acd000598fd0005a6220005b25a0005bdbc0005c85e0005dba70005ecb00005fbd9000612da00062b4e0006417300065597000667fe000678df000688680006aa540006c69f0006de950006f322000709eb0007291f000744ab00075d2a00077316000786d40007a8ef0007c55f0007dd710007f215000807f400082747000842eb00085b7f0008717d000885490008a77d0008c3ff0008dc1f0008f0ce00090579000924da0009408b0009592900096f30000983040009ace00009ce5e0009e9c6000a013b000a3f51000a75bc000aa570000acf40000af3e6000b280c000b605d000b91b9000bbcfd000be2e9000c084d000c4291000c75a2000ca265000cc9a1000cec04000d144f000d4924000d7772000da008000dc39b000de2c9000dfe1c000e2c20000e561d000e7ae9000e9b2a000eb76f000ed035000ee5ed000ef8f5000f1349000f3087000f4a27000f609f000f744f000f8591000f94b1000000000000889d000110f20001933b0001dff50002246e00026b3900029fbb0002c8360002e85d0003050800033065000354ef0003742300038f180003a69d0003bb510003cdae0003de140003eccf0003fa1d00040c640004226b000436960004491f00045a3600047fa100049ef30004b98d0004d06c0004e44c0004f5bb00050a4a000525bd00053e57000554840005689b00057ae400058b9b00059af10005a9100005b61c0005c2330005cd6f0005d7e60005e1ad0005f368000606240006220300063af800065172000665cb0006784e00068937000698ba0006a7000006c62b0006e02a0006f62d0007121e000732dc00074f86000768d300077f53000793760007a5970007c4eb0007df080007f523000810300008310d00084dd00008673200087dc3000891f50008a4220008c38a0008ddb70008f3de00090db900092ea500094b74000964e100097b7b00098fb40009a1e80009c8570009e716000a0080000a2a71000a6370000a9566000ac131000ae793000b1272000b4d6e000b8121000bae71000bd629000bf8f9000c2ef9000c6477000c9358000cbc70000ce074000d000b000d3761000d67e1000d9263000db7a5000dd84d000df4ec000e1c05000e47ff000e6e8a000e9052000eaded000ec7e1000ede9f000ef28f000f0811000f26b1000f4189000f5911000f6daf000f7fc3000f8f9b000f9d7d000000000002f499000427510004d0970005412800059e5b0005db6000060cd900064cd800067e490006a5a00006c5ad0006e04a0006f6be000713e1000735150007521400076ba00007824c0007968e0007a8c40007b93a0007c82d0007d5d00007e24e0007edca00080da20008374900085a7d000878a2000892bc0008a98f0008bdaf0008cf900008df8c0008edee0008faf000090d8500092319000936de0009490e000959d60009695f000977cc00098538000991bf0009b1e20009cf290009e9d6000a044a000a3095000a58f2000a7dba000a9f3e000abdc9000ad99f000af2fc000b1435000b3e58000b64bf000b87be000ba7a4000bc4b6000bdf36000bf75c000c1abe000c42dd000c676f000c88c4000ca725000cc2d6000cdc13000cf315000d101a000d3653000d592a000d78ec000d95de000db040000dc84d000dde38000df233000e08d4000e2a07000e484a000e63df000e7d04000e93ef000ea8d2000ebbdd000ecd38000edd0a000eeb75000ef89a000f092b000f1f03000f32eb000f4511000f559b000f64af000f726d000f7ef5000f8a5f000f94c9000f9e45000fa6eb000faecd000fb5fb000fbc89000fc281000fc7f1000fcce7000fd16d000fd58b000fd94d000fdcb9000fdfd9000fe2b1000fe549000fe7a7000fe9cd000febc5000fed8f000fef31000ff0ad000ff209000ff345000ff4650000203f33c33dd8453d4ad04e76521555ea58e05b3c5d285ec06032627f647d663a67c3691f6a566b6d6c6a6d506e216ee16f92722474a476c178907a217b807cb57dc87ebd7f9a80c58230837c84ab85c386c587b4889189608a208c0e8dd08f6a91bf946896d499099b0d9ce29e8ea028a2eea576a7c4a9deabc9ad87af1fb124b3c9b631b863ba64bc37bde0bf64c18cc411c65cc874ca5ccc19cdafcf20d0e3d349d579d777d948daf0dc73ddd3df14e072e288e46ee629e7bde92eea7debafecc6edc5eeacef80f081f1e0f320f443f54df640f71cf7e6f89df945f9ddfa68fae7fb5afbc4fc24fc7bfccbfd13fd56fd92fdc9fdfbfe29fe53fe79fe9b30006d730073004155544f00494e4600" + ] + ] + }, + ".bss": { + "align": 16, + "size": 80 + } + }, + "symbols": { + "WRITE_RESUME": [ + "abs", + 1074830380 + ], + "AMP_PICTURE": [ + "abs", + 1073974222 + ], + "follow": [ + ".run", + 116 + ], + "hold_jump": [ + ".run", + 508 + ], + "put_uint": [ + ".run", + 592 + ], + "put_text": [ + ".run", + 720 + ], + "put_time": [ + ".run", + 736 + ], + "log2_q16": [ + ".run", + 1066 + ], + "t60_ticks": [ + ".run", + 1154 + ], + "linear_ticks": [ + ".run", + 1304 + ], + "envelopes_text": [ + ".run", + 1380 + ], + "atk_rate": [ + ".run", + 4864 + ], + "amps": [ + ".run", + 2684 + ], + "envelopes_amp_graph": [ + ".run", + 82 + ], + "envelopes_flt_dec": [ + ".run", + 3328 + ], + "envelopes_flt_atk": [ + ".run", + 4352 + ], + "envelopes_gate": [ + ".bss", + 0 + ], + "envelopes_voice": [ + ".run", + 1882 + ], + "envelopes_amp_dec": [ + ".run", + 3840 + ], + "envelopes_write": [ + ".run", + 0 + ], + "envelopes_hold": [ + ".run", + 2816 + ], + "envelopes_params": [ + ".run", + 2796 + ], + "envelopes_tick": [ + ".run", + 2642 + ] + }, + "exports": [ + "envelopes_amp_dec", + "envelopes_amp_graph", + "envelopes_flt_atk", + "envelopes_flt_dec", + "envelopes_gate", + "envelopes_hold", + "envelopes_params", + "envelopes_tick", + "envelopes_voice", + "envelopes_write" + ], + "imports": [], + "weak": [], + "relocs": [ + [ + ".run", + 2, + "abs32", + "sec:.bss", + 0 + ], + [ + ".run", + 12, + "abs32", + "sec:.bss", + 16 + ], + [ + ".run", + 48, + "abs32", + "sec:.run", + 1882 + ], + [ + ".run", + 852, + "abs32", + "sec:.run", + 5125 + ], + [ + ".run", + 874, + "abs32", + "sec:.run", + 5120 + ], + [ + ".run", + 950, + "abs32", + "sec:.run", + 5125 + ], + [ + ".run", + 1008, + "abs32", + "sec:.run", + 5122 + ], + [ + ".run", + 1046, + "abs32", + "sec:.run", + 5122 + ], + [ + ".run", + 1486, + "abs32", + "sec:.run", + 3328 + ], + [ + ".run", + 1556, + "abs32", + "sec:.run", + 2816 + ], + [ + ".run", + 1596, + "abs32", + "sec:.run", + 3840 + ], + [ + ".run", + 1678, + "abs32", + "sec:.run", + 4352 + ], + [ + ".run", + 1724, + "abs32", + "sec:.run", + 4864 + ], + [ + ".run", + 1844, + "abs32", + "sec:.run", + 5132 + ], + [ + ".run", + 1864, + "abs32", + "sec:.run", + 5127 + ], + [ + ".run", + 1924, + "abs32", + "sec:.run", + 2684 + ], + [ + ".run", + 1946, + "abs32", + "sec:.run", + 2684 + ], + [ + ".run", + 2086, + "abs32", + "sec:.bss", + 0 + ], + [ + ".run", + 2126, + "abs32", + "sec:.run", + 4864 + ], + [ + ".run", + 2194, + "abs32", + "sec:.run", + 2816 + ], + [ + ".run", + 2272, + "abs32", + "sec:.bss", + 0 + ], + [ + ".run", + 2302, + "abs32", + "sec:.run", + 4864 + ], + [ + ".run", + 2426, + "abs32", + "sec:.bss", + 0 + ], + [ + ".run", + 2460, + "abs32", + "sec:.run", + 4864 + ], + [ + ".run", + 2516, + "abs32", + "sec:.run", + 4864 + ], + [ + ".run", + 2644, + "abs32", + "sec:.run", + 508 + ], + [ + ".run", + 2670, + "abs32", + "sec:.run", + 508 + ], + [ + ".run", + 2812, + "abs32", + "sec:.run", + 1380 + ] + ], + "sites": [ + { + "addr": "0x40109c24", + "len": 8, + "stock_sha256": "3df81daa6f0b5b044b3c5bbf7c45a8d96b97caa686b60bf262eeda8b7dc87dc5", + "new": "4ef9000000004e71", + "kind": "code", + "relocs": [ + [ + 2, + "abs32", + "sym:envelopes_write", + 0 + ] + ] + }, + { + "addr": "0x400d3302", + "len": 6, + "stock_sha256": "704bb7e62b7dc84ac22bb9ec1242ae79e917ce47a7b6a4aa06ec60b72654b2c0", + "new": "4eb900000000", + "kind": "code", + "relocs": [ + [ + 2, + "abs32", + "sym:envelopes_amp_graph", + 0 + ] + ] + }, + { + "addr": "0x4010d2aa", + "len": 4, + "stock_sha256": "bb101f17b7e23130f5e92c2f8e616cf858c9db923af32f0756f4cd9af27591d0", + "new": "00000000", + "kind": "data", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_flt_atk", + 0 + ] + ] + }, + { + "addr": "0x40109bc4", + "len": 4, + "stock_sha256": "a93a6f0c7e9387b5bf90a027d5678b11c90d555082736705d44faca3b73d46b6", + "new": "00000000", + "kind": "data", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_hold", + 0 + ] + ] + }, + { + "addr": "0x40109bf0", + "len": 4, + "stock_sha256": "8879f37f45bb49679a30176c392dc823a5215ba1fdbecc9406684fec9adbc2aa", + "new": "00000000", + "kind": "data", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_amp_dec", + 0 + ] + ] + }, + { + "addr": "0x4010d344", + "len": 4, + "stock_sha256": "e752dd02ad509e30bf4eab19cb5a7ef8760906dfdfa6d410e8a42e3635a2ef39", + "new": "00000000", + "kind": "data", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_flt_dec", + 0 + ] + ] + }, + { + "addr": "0x4010d3b4", + "len": 4, + "stock_sha256": "e752dd02ad509e30bf4eab19cb5a7ef8760906dfdfa6d410e8a42e3635a2ef39", + "new": "00000000", + "kind": "data", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_flt_dec", + 0 + ] + ] + }, + { + "addr": "0x4010d3dc", + "len": 4, + "stock_sha256": "e752dd02ad509e30bf4eab19cb5a7ef8760906dfdfa6d410e8a42e3635a2ef39", + "new": "00000000", + "kind": "data", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_flt_dec", + 0 + ] + ] + } + ], + "collections": {}, + "contribute": [ + { + "to": "ev_tick", + "order": 50, + "data": "00000000", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_tick", + 0 + ] + ], + "claims": [] + }, + { + "to": "core_params", + "order": 50, + "data": "00000000", + "relocs": [ + [ + 0, + "abs32", + "sym:envelopes_params", + 0 + ] + ], + "claims": [] + } + ], + "copied": [], + "resources": { + "regions": [], + "names": [ + "ui:FUNC + turn of AMP HLD", + "ui:value text of AMP ATK/HLD/DEC and FILTER ATK/DEC/REL", + "ui:AMP page envelope picture HLD", + "audio:amp envelope record write", + "audio:filter attack rate table", + "audio:amp hold table", + "audio:amp decay table", + "audio:filter decay and release table" + ], + "core": "0.3" + }, + "requires": [ + "core" + ], + "conflicts": [], + "signature": null +}