diff --git a/.github/workflows/build_simulator.yml b/.github/workflows/build_simulator.yml index e73d769d2..d9e8a52ce 100644 --- a/.github/workflows/build_simulator.yml +++ b/.github/workflows/build_simulator.yml @@ -46,9 +46,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' + token: ${{ secrets.CHECKOUT_TOKEN }} - name: Install mac dependencies if: matrix.os.os == 'macos' diff --git a/.github/workflows/build_test_firmware.yml b/.github/workflows/build_test_firmware.yml index 134a804d3..2b295eb32 100644 --- a/.github/workflows/build_test_firmware.yml +++ b/.github/workflows/build_test_firmware.yml @@ -16,38 +16,41 @@ on: - '!shared/gen_minblep' # Skip generator script (still run CI if generated files change) - '!shared/tableGen' # Skip generator script (still run CI if generated files change) -jobs: +jobs: build: strategy: matrix: gcc: ['12.2.Rel1'] # can add other versions if needed name: "Build firmware" runs-on: ubuntu-latest + container: ghcr.io/lnnrts/metamodule:latest steps: - name: Install Arm GNU Toolchain (arm-none-eabi-gcc) uses: carlosperate/arm-none-eabi-gcc-action@v1 with: release: ${{ matrix.gcc }} + - name: Install cmake + uses: jwlawson/actions-setup-cmake@v1.13 + with: + cmake-version: '3.26.x' + - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' - - - name: Install linux dependencies - run: | - sudo apt install -y ninja-build + token: ${{ secrets.CHECKOUT_TOKEN }} - name: Build and test run: cd firmware && make configure && make all - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: firmware - path: | - firmware/build/mp1corea7/medium/main.elf - firmware/build/mp1corea7/medium/main.uimg + # - name: Upload artifact + # uses: actions/upload-artifact@v3 + # with: + # name: firmware + # path: | + # firmware/build/mp1corea7/medium/main.elf + # firmware/build/mp1corea7/medium/main.uimg - name: Release uses: softprops/action-gh-release@v1 diff --git a/.github/workflows/build_vcv_plugin.yml b/.github/workflows/build_vcv_plugin.yml index 90e3cd640..cf491331c 100644 --- a/.github/workflows/build_vcv_plugin.yml +++ b/.github/workflows/build_vcv_plugin.yml @@ -55,9 +55,10 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive + token: ${{ secrets.CHECKOUT_TOKEN }} - name: Build plugin run: | export PLUGIN_DIR=$GITHUB_WORKSPACE/vcv @@ -79,9 +80,10 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive + token: ${{ secrets.CHECKOUT_TOKEN }} - name: Build plugin run: | export PLUGIN_DIR=$GITHUB_WORKSPACE/vcv @@ -100,9 +102,10 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive + token: ${{ secrets.CHECKOUT_TOKEN }} - name: Get Rack-SDK run: | pushd $HOME @@ -128,9 +131,10 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive + token: ${{ secrets.CHECKOUT_TOKEN }} - name: Get Rack-SDK run: | pushd $HOME diff --git a/.github/workflows/run_vcv_tests.yml b/.github/workflows/run_vcv_tests.yml index 2e1af0856..b2dfd95fc 100644 --- a/.github/workflows/run_vcv_tests.yml +++ b/.github/workflows/run_vcv_tests.yml @@ -35,9 +35,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' + token: ${{ secrets.CHECKOUT_TOKEN }} - name: Install windows dependencies (MSYS2) if: matrix.os.os =='windows' diff --git a/.gitmodules b/.gitmodules index 5282d164f..6fb7a8629 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,3 +34,15 @@ [submodule "firmware/lib/jansson/jansson"] path = firmware/lib/jansson/jansson url = https://github.com/4ms/jansson.git +[submodule "firmware/lib/esp-serial-flasher"] + path = firmware/lib/esp-serial-flasher + url = https://github.com/4ms/esp-serial-flasher.git +[submodule "firmware/lib/lockfree"] + path = firmware/lib/lockfree + url = https://github.com/DNedic/lockfree.git +[submodule "firmware/lib/flatbuffers"] + path = firmware/lib/flatbuffers + url = https://github.com/google/flatbuffers.git +[submodule "firmware/src/wifi/flat"] + path = firmware/src/wifi/flat + url = https://github.com/4ms/metamodule-bridge-protocol.git diff --git a/README.md b/README.md index efda26d62..a40bcbc27 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -## Meta Module +# Meta Module [![Build Simulator](https://github.com/4ms/metamodule/actions/workflows/build_simulator.yml/badge.svg)](https://github.com/4ms/metamodule/actions/workflows/build_simulator.yml) [![Build VCV Rack Plugin](https://github.com/4ms/metamodule/actions/workflows/build_vcv_plugin.yml/badge.svg)](https://github.com/4ms/metamodule/actions/workflows/build_vcv_plugin.yml) [![Run VCV unit tests](https://github.com/4ms/metamodule/actions/workflows/run_vcv_tests.yml/badge.svg)](https://github.com/4ms/metamodule/actions/workflows/run_vcv_tests.yml) [![Build Firmware](https://github.com/4ms/metamodule/actions/workflows/build_test_firmware.yml/badge.svg)](https://github.com/4ms/metamodule/actions/workflows/build_test_firmware.yml) -### Start +## Start First, clone this repo and `cd` into the new directory. @@ -28,21 +28,23 @@ git submodule update --init --recursive Next, setup your development environment by [following the instructions on this page](./docs/Setup.md). -### Next Steps +## Next Steps The Meta Module environment is built using three separate components: The VCV Rack Plugin (which includes the Meta Module patch exporter module), the Firmware for the Meta Module hardware, and the Firmware Simulator that allows you to run the firmware locally to test changes. -To build these components, please follow the separate build guides: +For information about building and using these components, please follow the separate guides: + - [VCV Rack Plugin](./vcv/README.md) -- [Simulator](./simulator/Building.md) +- [Simulator](./simulator/README.md) - [Firmware](./firmware/README.md) -### Usage +## Usage - [Creating Meta Module Patches With VCV](./docs/BasicVCVPatching.md) +- [Updating Firmware](./docs/user-firmware-update.md) -### Contributing +## Contributing If you would like to port your own VCV modules to the Meta Module platform, please see the [Porting Guide](./docs/Porting.md). diff --git a/docs/Firmware-Boot.md b/docs/Firmware-Boot.md new file mode 100644 index 000000000..5b7d817f5 --- /dev/null +++ b/docs/Firmware-Boot.md @@ -0,0 +1,47 @@ +# Firmware Boot Procedure + +The chip's ROM code reads the `BOOT0_2` DIP switch and then: + +- either halts (if it's set to `Debug`) +- or attempts to load a bootloader from the SD Card (if it's set to `SD`) +- or attempts to load a bootloader from the QSPI NOR Flash chip (if it's set to + `Flash`) + +and then starts executing the bootloader. + +The bootloader (which is `mp1-boot`) initializes the DDR RAM and various system +peripherals (security, MMU, clocks). After that it halts if Pin 2 and 4 of the +"Control Expander" (`J102`) are shortened with a jumper. + +Otherwise (no jumper detected), it will check if the rotary encoder is pressed. +If so, it loads an alternative firmware image from whatever media it booted +from (SD or Flash). On the Flash chip, USB-DFU "second-stage bootloader" (SSBL) +is preloaded. The SD card could also have an alternative firmware but this is +not used yet. + +Otherwise (no jumper and rotary not pressed) it loads the firmware image from the +default location on the selected boot media and then starts execution. + +### Secondary A7 core startup + +Both A7 cores startup at the same time, but Core 2 (aka secondary core) will go to +sleep immediately. The first core (primary, aka Core 1) +will proceed with the procedure descrived above. + +The MetaModule firmware awakens the secondary core and tells it to start executing from +the `aux_core_start` assembly code in 'firmware/lib/mdrivlib/target/stm32mp1_ca7/boot/startup_ca7.s` +which does some basic stack setup and then jumps to +the `aux_core_main()` function in `firmware/src/core_a7/aux_core_main.cc`. Here, the +secondary core handles the screen updates and runs the LVGL tasks. It also gets interrupted +by the primary core to help with audio processing. + + +### M4 startup + +The M4 firmware is bundled inside the A7 firmware. After power-on there is no +M4 firmware running (there is no internal flash to store the M4 firmware), so +the M4 core is just sitting idle. The A7 core does some early init and then +loads the M4 firmware and starts that core. + +The M4 firmware is responsible for reading all controls and the gate jacks. It also +handles all USB and SD Card transactions. diff --git a/docs/Porting.md b/docs/Porting.md index afdba992d..26ea17f7f 100644 --- a/docs/Porting.md +++ b/docs/Porting.md @@ -1,9 +1,92 @@ -If you would like to port your VCV-compatible modules to the Meta Module platform, you will need to follow a few steps for exporting artwork and ensuring binary compatibility. +### Instructions for adding a new module (WIP) -### Converting assets +1) First step is add the module code as a git submodule: -(TODO) +```bash +git submodule https://github.com// firmware/vcv_ports/ +``` + +2) Create artwork folders: + +```bash +mkdir -p firmware/src/gui/images//modules/ +mkdir -p firmware/src/gui/images//components/ +``` + +3) Create a modules.cmake file: `firmware/vcv_ports/glue//modules.cmake` +Look at other brands examples and copy what's done there. + +`modules.cmake` must do a few things: + +- Create a list of module names call `Modules`. + These names should be the VCV Rack module slugs. Ideally, these names are + also the names of the SVG faceplate files (without the .svg extension) and + also the names of the C++ source files (without the .cpp or .cc extension), + but if that's not the case you will just need to manually specify the SVGs + and source file names manually. + +- Invoke the module list filtering function on the list, like this: + ```cmake + include(${CMAKE_CURRENT_LIST_DIR}/../filter.cmake) + limit_modules_built("" Modules) + ``` + This allows developers to speed up build times and reduce binary sizes by specifying a white-list of + modules to include in the build. + +- Set a list called `_FACEPLATE_SVGS` ( is all caps), which + contains full paths to the faceplate .svg files. This is used when + (re-)generating all artwork files (convering SVG files to PNG and LVGL + format). + + +4) Create a brand CMakeLists.txt file: `firmware/vcv_ports/glue//CMakeLists.txt` +This file tells CMake how to build your modules. + +- Typically at the top of the CMakeLists file, you will `include()` the + `modules.cmake` file, and use the list of module names to generate the + list of source files needed to compile, but that's not a strict requirement. + +- The CMakeLists.txt must create a CMake target OBJECT library named Library, like this: + ```cmake + add_library( + Library OBJECT + ${BRAND_SOURCE_PATHS} + ${OTHER_SOURCES} + more_source_files.cpp + ) + ``` + You can then use normal CMake commands like `target_include_directories`, + `target_compile_options`, etc to specify how your modules should be built. + + +5) Add the brand name to `firmware/vcv_ports/brands.cmake` + +```cmake +# List of brands +set(brands + ... + +) +``` + + +5) Add the plugin to the VCV whitelist (see `vcv/src/mapping/module_directory.hh`). + +6) Generate faceplate artwork. + You can now run `make faceplate-image` to generate the faceplate artwork. This will + also re-generate all other brands artwork, so it's recommend that you a + limit file (see [Firmware README.md](../firmware/README.md) ) to just + specify your brands' modules, or perhaps even a subset of them just to + start. This works by reading the cmake list you created `BRAND_FACEPLATE_SVGS` + +7) Generate component artwork. + Put all SVG files for components into a folder (from the repo root) `graphics//components/`. + Then run `make comp-images` to generate the component images. This will regenerate all brand's component images. + (TODO: allow limiting this). Please only commit actually changed images. + +8) Connect VCV Widgets to MetaModule Elements and LVGL images. +(TODO). For an example, see [this commit](https://github.com/4ms/metamodule/pull/135/commits/3d5a721e7c9beea818e58401e82ba4faf5d52321) +Also, see issue #47 + -To create the artwork files from the SVGs, you must have Inkscape installed an on your PATH -...To Be Continued... \ No newline at end of file diff --git a/docs/firmware-building.md b/docs/firmware-building.md new file mode 100644 index 000000000..26e82d804 --- /dev/null +++ b/docs/firmware-building.md @@ -0,0 +1,166 @@ +## Building Firmware + +This requires `arm-none-eabi-gcc` version 12.2 or later installed on your PATH. +Please see the [setup guide](../docs/Setup.md) for some important notes about this. + +Make sure you are in the right branch and you already updated the submodules. + +To prepare the build system: (only needs to be run once) + +``` +make configure +``` + +To compile, run: + +``` +make all +``` + +The `make configure` command is a shortcut for running: + +``` +# MacOS, Linux: +cmake --fresh --preset base -GNinja + +# MinGW: +cmake --fresh --preset base -G"Unix Makefiles" +``` + +(The work-around for MinGW is documented with [issue #78](https://github.com/4ms/metamodule/issues/78)) + +The `make all` command is a shortcut for running: + +``` +cmake --build --preset base +``` + +### Limiting the modules built + +You also can limit the modules built to substantially reduce the compilation +and link times, as well as binary size. Create a text file with the modules +you want built, one per line. Each line should contain an +entry in the form `Brand:Module`. For example: + +``` +echo "4ms:EnOsc" >> quickbuild.txt +echo "Befaco:EvenVCO" >> quickbuild.txt +echo "hetrickcv:PhasorGen" >> quickbuild.txt + +make limit quickbuild.txt +``` + +This would tell CMake to re-configure the project and just build those three modules. +You can still open patches containing other modules, but their artwork won't be shown +and you can't play them. + + +### Using an SD Card + +*Optional*: If you plan to flash firmware to an SD Card, then you can specify the +path the SD Card device to save time. If you don't do this, then the system +will prompt you whenever you run one of the SD Card flashing scripts. The +device path should be to the entire SD Card device (not just one partition). +``` +cmake --preset base -DSD_DISK_DEV=/dev/disk4 + +# Alternatively, set an environment variable: +export SD_DISK_DEV=/dev/disk4 +``` + +The firmware is built as `firmware/build/mp1corea7/medium/main.elf` and `main.uimg` +in the same directory. The .elf file is used when debugging, and the .uimg file +is used when copying firmware to NOR Flash or an SD card. + + +### Specifying the toolchain + +*Optional*: if you have multiple versions of the gcc arm toolchain installed and don't want to +change your PATH for this project, you can set the METAMODULE_ARM_NONE_EABI_PATH var like this: + +``` +# Put in your bashrc/zshrc for convenience: +# Note the trailing slash (required) +export METAMODULE_ARM_NONE_EABI_PATH=/path/to/arm-gnu-toolchain-12.x-relX/bin/ +``` + + +### Automatically generated materials + +Several files are automatically generated using python scripts, e.g. faceplate +LVGL code. These generated files are already committed for a few reasons: 1) +the conversion process uses some specific external programs (inkscape CLI, and +a particular version of node); 2) generating all the assets takes a long time; +3) the assets don't change very often (if ever) and are completely orthogonal +to the code. Also conversion from SVG to PNG can generate a file that is +visually the same but has a different binary representation, causing lots of +noise in the git diffs. However if you wish to (re)-generate these files, the +following commands can be run: + +``` +# Generating LVGL image files for components +make comp-images + +# Generating LVGL image files for faceplates +make faceplate-images + +# Updating/creating 4ms VCV artwork SVGs files from *_info.svg files +make vcv-images + +# Updating/creating CoreModule *_info.hh files from *_info.svg +make module-infos + +# All of the above +make regenerate-all +``` + +If you just want to re-generate one image (that is, convert one SVG to a PNG and LVGL format), then you can invoke the python script directly. + +Here are some examples. These commands can be run from anywhere, but for these examples we'll show it from the firmware dir: + +``` +cd firmware/ +``` + + +Make sure the output directories exist first: + +``` +mkdir -p src/gui/images/BRANDNAME/components/ +mkdir -p src/gui/images/BRANDNAME/modules/ +``` + +Convert a component SVG: +(The DPI will be automatically detected and the output will be rescaled properly) + +``` +../shared/svgextract/svgextract.py convertSvgToLvgl \ + path/to/newcomponent.svg \ + src/gui/images/BRANDNAME/components/newcomponent.c +``` + +Convert a faceplate SVG: +This is the same as converting a component SVG, except for Alpha blending is disabled, and the height is fixed at 240px. + +``` +../shared/svgextract/svgextract.py createLvglFaceplate \ + path/to/newfaceplate.svg \ + src/gui/images/BRANDNAME/modules/newfaceplate.c +``` + +Optionally, you can only export one layer from a faceplate SVG file by specifying the layer name as a 3rd argument: + +``` +../shared/svgextract/svgextract.py createLvglFaceplate \ + path/to/newfaceplate.svg \ + src/gui/images/BRANDNAME/modules/newfaceplate.c \ + artworklayer +``` + +The svgextract script can do more, to see its help and view available commands: + +``` +../shared/svgextract/svgextract.py +``` + + diff --git a/docs/firmware-debugging.md b/docs/firmware-debugging.md new file mode 100644 index 000000000..139a1487c --- /dev/null +++ b/docs/firmware-debugging.md @@ -0,0 +1,92 @@ + +## Debugging with gdb and OpenOCD or JLinkGDBServer + + +Connecct an ST-LINK or J-Link programmer to the 10-pin JTAG/SWD header on the PCB. + +If you want to use OpenOCD, run this: + +``` +openocd -f board/stm32mp15x_dk2.cfg +``` + +Alternatively, you can run JLinkGDBServer and then select the STM32MP15x Core A7 as the target. + +``` +TODO: command? +``` + + +In another terminal window, run this command (from the project directory): + +``` +arm-none-eabi-gdb -f build/mp1corea7/medium/main.elf +``` + +From the gdb prompt, type: + +``` +target extended-remote localhost:3333 +load +``` + +The port number (3333) may be different for you, check the output of openocd or JLinkGDBServer +to see what port it's listening on. There may be a different port for each core. + +Remember, any file you load using a debugger will only get loaded into RAM. As +soon as you power down, it will be lost. + +## Debugging with Segger Ozone (OSD32 board only) + + +This requires a J-Link debugger. Within Ozone, create a new project for the +STM32MP15xx Core A7, and load the elf file `build/mp1corea7/medium/main.elf`. + +Make sure the Control Expander "freeze" jumper is installed, as described in +[firmware-loading](firmware-loading.md) + +After re-compiling, power cycle before loading the new .elf file. The Freeze jumper +makes this possible. + +## GPIO pin debugging (pin flipping) + +You can toggle some GPIO pins to indicate states from firmware with minimal impact on firmware timing. +Typically you would read the pins using an oscilloscope or logic probe. + +There are 6 header pins and two SMD pads on the PCB dedicated to this. They can be used like this: + +``` +#include "debug.hh" // Found in firmware/src/medium/ + +Debug::Pin0::high(); +Debug::Pin0::low(); +Debug::Pin1::set(true); //same as ::high() +Debug::Pin1::set(false); //same as ::low() +``` + +The pins and pads are located on the PCB as shown here: +![PCB header locations](./images/pcb-headers.png) + + +## Console output (printf debugging) + +You can view the console output by connecting a USB-UART cable to the TX pin of +the debug header (next to the SWD header). The TX pin is labeled (upper-right +pin). The bottom four pins are all GND. Settings are 115200, 8N1. See image above. + +Use `pr_dbg()`, `pr_warn()`, `pr_err()`, and `pr_trace()` for debug output. These +require the `console/pr_dbg.hh` header. + + +## Using VSCode + +VSCode can be used to debug, using OpenOCD and gdb. + + +Some configuration files (for the mp1 bootloader, so they will need to be adapted to MetaModule): +[here](https://github.com/danngreen/stm32mp1-baremetal/tree/vscode/bootloaders/mp1-boot/.vscode) +and [here](https://github.com/kamejoko80/stm32mp1-baremetal-1/tree/vscode/bootloaders/mp1-boot/.vscode) + +And also [some discussion:](https://github.com/4ms/stm32mp1-baremetal/issues/20) + +TODO: Adapt this to MetaModule. diff --git a/docs/firmware-loading.md b/docs/firmware-loading.md new file mode 100644 index 000000000..ea58549cd --- /dev/null +++ b/docs/firmware-loading.md @@ -0,0 +1,170 @@ +## Loading firmware onto the MetaModule + +You have several choices for how to load the firmware applcation. Each one is covered +in a section below: + +1) Load in RAM over SWD/JTAG + +2) Load into NOR Flash over DFU-USB + +3) Boot from SD Card + + +### Load in RAM over SWD/JTAG + +![PCB header locations](./images/pcb-headers.png) + +This is the preferred method for active firmware development. It requires a +JTAG programmer. + +Attach a JTAG debugger to the 10-pin connector at the top of the module labeled +"SWD". The protocol is actually JTAG, despite the header's name, though SWD may +work since the only difference is the tRST pin instead of NRST. + +If you are already running the application and just need to debug, you can just +attach without loading. + +If you need to load new firmware, then do this: + +1) Install a "Freeze jumper" on `Control Expander` header that bridges the top-left pin +and the pin just to the right of it. Make sure you use the right header, it's +the one above the Wifi header, near the `y` and `z` pots. The jumper should be +horizontal, not vertical, on the top row of pins all the way to the left: + +``` + Control + Expander + [====] o o + o o o o +``` + +See image above for reference. + +2) Power off and back on (full power-cycle is required). + +The console will show: + +``` +Freeze pin detected active, freezing. +Ready to load firmware to DDR RAM via SWD/JTAG. +``` + +Use Jflash, TRACE32, Ozone, openOCD/arm-none-eabi-gdb, etc to load the main.elf file. +If you have a JLink connected, you can program with this; + +``` +make jprog +``` + +This should take 15-30 seconds. + +For other methods, just load the .elf file and then start executing from 0xC0200040. + +Note: If you are familiar with flashing Cortex-M series MCUs, you will notice +some differences. One is that Flash is on an external chip. Another difference is +that the main RAM (DDR RAM) is not available until software initializes it. The +on-board NOR Flash chip has a bootloader installed +([MP1-Boot](https://github.com/4ms/mp1-boot), which is the FSBL). This is +loaded by the BOOTROM on power-up. The MP1-Boot bootloader is responsible for +initializing the DDR RAM peripheral. Obviously, this must be done before +loading the firmware into DDR RAM. So, unlike a Cortex-M chip, you must run a +bootloader before programming the device. However, one of the first things an +application does when it starts running is to enable the MMU and setup various +memory regions, some of which are not writable. Thus, the only time in which +it's possible to load firmware to RAM is after the bootloader has initialized +RAM but before the application has started. To handle this, MP1-Boot has a +"Freeze pin" option. When this pin is detected low (jumper is installed), then +MP1-Boot will halt execution (freeze) after initializing RAM. + +### Load into NOR Flash over DFU-USB + +Loading onto NOR Flash will flash the firmware into the on-board FLASH chip so +you can boot normally without a computer connected. It takes a minute or two, +so this is a good way to flash firmware infrequently, for example, flashing the +latest stable firwmare version. This is not recommended if you're doing active +firmware development since it's slow (use SWD/JTAG in that case). + +Power cycle the module while holding down the rotary encoder button. This +forces an alt firmware to be loaded from NOR Flash (which is a USB-DFU +bootloader). Make sure the jumper mentioned in the SWD/JTAG section is not installed. +If you are using the UART console, then you'll see this in the console: + +``` +USB DFU Loader Starting... +QSPI is initialized. +Connect a USB cable to the computer. +Run `dfu-util --list` in a terminal and you should see this device. +``` + +The button will be flashing green when in USB-DFU bootloader mode. + +Connect a USB cable from a computer to the module. + +You can use a web-based loader [such as this +one](https://devanlai.github.io/webdfu/dfu-util/). Click Connect, and then +select "STM Device in DFU Mode". Then click "Choose File" and select the uimg +file you just built at `build/mp1corea7/medium/main.uimg`. Then click +"Download". There may be an error `DFU GETSTATUS failed: ControlTransferIn +failed: NetworkError: Failed to execute 'controlTransferIn' on 'USBDevice': A +transfer error has occurred.` This is normal, and is not an error. It's safe to +ignore this. + + +Or use the command line (you must have [dfu-util](https://dfu-util.sourceforge.net/) installed): + +``` +make flash-dfu +``` + + +This command loads the main.uimg file to the default address (0x70080000). +It calls `dfu-util -a 0 -s 0x70080000 -D build/mp1corea7/medium/main.uimg` + +This will take between 60 and 120 seconds. +When it's done, unplug the USB cable, power-cycle, and the new code will start up. + + +### Boot from SD Card + +You need a dedicated SD Card, all contents will be erased. A 16GB card is common and works fine, +but smaller or larger should work too. + +You first need to format, partition, and install the bootloader on the card. This only needs +to happen once when you use a new SD Card. + +``` +make format-sd +``` + +This will ask you for the device path (/dev/disk4, for example). Make sure you get it right, because the +script will run `mkfs` or `diskutil eraseDisk`. + +After running this, you will need to eject and re-insert the SD Card because the volumes have changed. + +Then do: + +``` +make flash-bootloader-sd +``` + +This will build the bootloader (mp1-boot) and use `dd` to load it onto the first two partitions of the SD Card. +You will again be asked for the drive name. + +You now have a bootable SD Card. You shouldn't need to repeat the above steps unless you get a new SD Card. + +To flash the application, do this: + +``` +make flash-app-sd +``` + +This will build the application as normal, and then use `dd` to copy it to the fourth partition. + +Eject the card and insert it into the Meta Module. + +To tell the Meta Module to boot using the SD Card, you need to change the BOOT DIP switches. +These are located on the back of the PCB, under the screen near the rotary encoder. +They are labeled "BOOT0_2". There are two switches. Look at the diagram printed on the PCB. +To boot with the SD, both switches should be pushed to the left. +If you want to back to booting from Flash (internal Flash chip), then flip the bottom switch to the right. + diff --git a/docs/images/pcb-headers.jpg b/docs/images/pcb-headers.jpg new file mode 100644 index 000000000..ead735ad4 Binary files /dev/null and b/docs/images/pcb-headers.jpg differ diff --git a/docs/images/pcb-headers.png b/docs/images/pcb-headers.png new file mode 100644 index 000000000..f7848dcf4 Binary files /dev/null and b/docs/images/pcb-headers.png differ diff --git a/simulator/Building.md b/docs/simulator-building.md similarity index 55% rename from simulator/Building.md rename to docs/simulator-building.md index b5bc4b269..c69bf7331 100644 --- a/simulator/Building.md +++ b/docs/simulator-building.md @@ -1,11 +1,7 @@ -For information on how to use the firmware simulator, please see [README.md](./README.md). - ### Building the Simulator The simulator uses SDL2, which must be installed on your host machine. It -simulates graphics and audio output. The window can be re-sized in order to -examine precise pixel alignment. - +simulates graphics and audio output. Install the requirements as described in [Setup](../docs/Setup.md) Make sure you are in the right branch and you already updated the submodules. @@ -37,7 +33,7 @@ build/simulator build/simulator --help ``` -See the simulator [README.md](./README.md) for arguments details. +See the [Simulator Usage guide](simulator-usage.md) for arguments details. When adding/removing assets, sometimes you need to clean the build: @@ -60,3 +56,23 @@ make clean ``` Note that `make run` doesn't allow you to pass arguments. + +### Limiting the modules built + +You also can limit the modules built to substantially reduce the compilation +and link times. Create a text file with the modules +you want built, one per line. Each line should contain an +entry in the form `Brand:Module`. For example: + +``` +echo "4ms:EnOsc" >> quickbuild.txt +echo "Befaco:EvenVCO" >> quickbuild.txt +echo "hetrickcv:PhasorGen" >> quickbuild.txt + +make limit quickbuild.txt +``` + +This would tell CMake to re-configure the project and just build those three modules. +You can still open patches containing other modules, but their artwork won't be shown +and you can't play them. + diff --git a/docs/simulator-usage.md b/docs/simulator-usage.md new file mode 100644 index 000000000..7844484c8 --- /dev/null +++ b/docs/simulator-usage.md @@ -0,0 +1,130 @@ +# MetaModule Simulator Usage + +## Keyboard Control + +The simulator simulates the hardware's rotary encoder, button, knobs, and jacks +(patching/unpatching) using the host computer's keyboard. + +### Navigation + +- `Left Arrow`: Turn encoder counter-clockwise +- `Right Arrow`: Turn encoder clockwise +- `Down Arrow`: Press the encoder +- `Up Arrow`: Press the button (typically goes to the previous page) + +### Knobs + +The knobs on the Metamodule are labeled with letters (A-F, and u-z). These can be turned with the keyboard +by first pressing a letter to select the knob you want to turn, and then pressing ] or [ to increment/decrement the knob; + +- `a`: select knob A +- `b`: select knob B +- `c`: select knob C +- `d`: select knob D +- `e`: select knob E +- `f`: select knob F +- `u`: select knob u +- `v`: select knob v +- `w`: select knob w +- `x`: select knob x +- `y`: select knob y +- `z`: select knob z + +- `[`: turn the selected knob down 5% +- `]`: turn the selected knob up 5% + +TODO: make `{` and `}` inc/dec by small amounts + +The console will report the knob that was turned and its present value. + +### Audio Routing +By default, Audio Out 1 and Audio Out 2 are patched to the soundcard's left and right outputs, respectively. +Pressing a number button will change the routing: + +- `1`: Audio Out 1 -> Left, Audio Out 2 -> Right (default) +- `2`: Audio Out 2 -> Left, Audio Out 3 -> Right +- `3`: Audio Out 3 -> Left, Audio Out 4 -> Right +- `4`: Audio Out 4 -> Left, Audio Out 5 -> Right +- `5`: Audio Out 5 -> Left, Audio Out 6 -> Right +- `6`: Audio Out 6 -> Left, Audio Out 7 -> Right +- `7`: Audio Out 7 -> Left, Audio Out 8 -> Right +- `8`: Audio Out 8 -> Left, Audio Out 1 -> Right + +For now, the only way to test the inputs of a virtual module is to create a patch that runs signals +from a virtual module into the virtual module to be tested. +Future features include: +- Route a .wav file on the host computer to an input jack and play it on startup, and/or when a key is pressed. +- Route an audio interface's input jacks to input jacks. + +### Window + +The window can be re-sized in order to +examine precise pixel alignment. Drag the bottom-right corner of the window. To maintain the aspect ratio, hold shift while dragging. + +You also can specify a zoom level in the command-line arguments, see below. + + +------------------------ + +## Command-line Arguments + +Command-line arguments can be passed to the executable. To see the valid arguments, run: + +``` +build/simulator --help +``` + +Or you can type `-h` instead of `--help` + +Currently these are the options: + +### Audio Output Device + +`-a #`: specify the audio device index to use for output. Run the simulator and look at the console log to see what devices +are available. For example, you might see: + +``` +SDL: 3 audio devices found +0: DELL P2415Q (selected) +1: Studio Display Speakers +2: Mac Studio Speakers +``` + +Device 0 is chosen if the option is not specificed. If you were to do: + +``` +build/simulator -a 1 +``` + +then the Studio Display Speakers would be chosen. + + +### Zoom level + +`-z ###`: specify the initial zoom amount, as a percentage. Default is 100 (1 hardware pixel = 1 simulator pixel). + +A zoom of 300 or more is useful for inspecting pixel alignment. + +You can also change the zoom while the simulator is running by re-sizing the window with the mouse. + +### Patch file directory + +There are two mock "volumes" containing patch files. The `Internal` volume is +the list of factory default patches. This is always loaded. This list is found in +`firmware/src/patch_file/patches_default.hh`. + +Patches are also loaded from a local host computer directory, and appear in the `SD Card` volume. + +`-p path/to/patchfiles/`: The directory to search for patch files (.yml files) + +``` +build/simulator -p ~/MyPatchFiles/ +``` + +If no path is specified, `patches/` will be used (which is a directory in the simulator/ dir). + +If a directory is specified but it contains no valid patch files (or is an +invalid path), the simulator will print an error and ignore it. + + + diff --git a/docs/user-firmware-update.md b/docs/user-firmware-update.md new file mode 100644 index 000000000..1b78ef5e4 --- /dev/null +++ b/docs/user-firmware-update.md @@ -0,0 +1,28 @@ +## MetaModule Users Guide: + +### How to upgrade firmware + +1) Download the latest firmware release from the [MetaModule Github releases](https://github.com/4ms/metamodule/releases). You only need the main.uimg file, the other files are just for doing debugging. + +2) Connect a USB cable from a computer to the module. + +3) Power cycle the module while holding down the rotary encoder. + +4) The button will be flashing green, this tells you that you are in USB-DFU bootloader mode + +5) Open [this web-based DFU loader](https://devanlai.github.io/webdfu/dfu-util/) + +6) Click Connect, and then select "STM Device in DFU Mode". + +7) Then click "Choose File" and select the main.uimg file you just downloaded. + +8) Click "Download". + +9) Wait a couple minutes... it takes a while. There may be this error message: + +`DFU GETSTATUS failed: ControlTransferIn failed: NetworkError: Failed to execute 'controlTransferIn' on 'USBDevice': A transfer error has occurred.` + +This is normal, and is not a problem. It's safe to ignore this. + +10) When the web page says it's done, then unplug the USB cable and power cycle the module + diff --git a/firmware/CMakePresets.json b/firmware/CMakePresets.json index 5c156452a..4318ab9c6 100644 --- a/firmware/CMakePresets.json +++ b/firmware/CMakePresets.json @@ -22,6 +22,24 @@ "value": "cmake/arm-none-eabi-gcc.cmake" } } + }, + { + "name": "full", + "inherits": "base", + "cacheVariables": { + "ENABLE_WIFI_BRIDGE":{ + "type": "BOOL", + "value": "ON" + } + } + }, + { + "name": "pcb-p12", + "description": "PCB p11 with mods (aka p12)", + "inherits": "base", + "cacheVariables": { + "METAMODULE_PCB_VERSION": "12" + } } ], "buildPresets": [ @@ -30,4 +48,4 @@ "configurePreset": "base" } ] -} \ No newline at end of file +} diff --git a/firmware/Dockerfile b/firmware/Dockerfile new file mode 100644 index 000000000..49d23e647 --- /dev/null +++ b/firmware/Dockerfile @@ -0,0 +1,11 @@ +LABEL org.opencontainers.image.source=https://github.com/4ms/metamodule +FROM ubuntu:22.04 + +ARG flatbuffer_version=v23.3.3 + +RUN apt update && apt upgrade -y && apt clean +RUN apt update && apt install git ninja-build build-essential bsdmainutils xxd python3 -y && apt clean +RUN apt update && apt install -y cmake && git clone --depth 1 https://github.com/google/flatbuffers -b $flatbuffer_version && cd flatbuffers && cmake -S . -B build -G Ninja && cmake --build build --target install && cd .. && rm -rf flatbuffers && apt remove cmake -y && apt clean + +RUN flatc --version + diff --git a/firmware/Makefile b/firmware/Makefile index 4a8df9eb9..60c5b11ae 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -1,4 +1,4 @@ -PRESET := base +PRESET ?= base # Detect MinGW and use Make ifdef SYSTEMROOT diff --git a/firmware/README.md b/firmware/README.md index bb30233d8..902053e62 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -1,289 +1,18 @@ -### Building Firmware +## MetaModule Firmware -This requires `arm-none-eabi-gcc` version 12.2 or later installed on your PATH. -Please see the [setup guide](../docs/Setup.md) for some important notes about this. +The MetaModule firmware is built on a host computer using the arm-none-eabi +toolchain, and then loaded onto the MetaModule. -Make sure you are in the right branch and you already updated the submodules. +The following guides are available: -To prepare the build system: (only needs to be run once) +- [Setting up your environment](../docs/Setup.md): required software for your host computer. -``` -make configure -``` +- [Building firmware](../docs/firmware-building.md) -To compile, run: +- [Loading firmware](../docs/firmware-loading.md) -``` -make all -``` +- [User instructions to upgrading firmware](../docs/user-firmware-update.md) -The `make configure` command is a shortcut for running: +- [Debugging](../docs/firmware-debugging.md) -``` -# MacOS, Linux: -cmake --fresh --preset base -GNinja - -# MinGW: -cmake --fresh --preset base -G"Unix Makefiles" -``` - -(The work-around for MinGW is documented with [issue #78](https://github.com/4ms/metamodule/issues/78)) - -The `make all` command is a shortcut for running: - -``` -cmake --build --preset base -``` - -*Optional*: If you plan to flash firmware to an SD Card, then you can specify the -path the SD Card device to save time. If you don't do this, then the system -will prompt you whenever you run one of the SD Card flashing scripts. The -device path should be to the entire SD Card device (not just one partition). -``` -cmake --preset base -DSD_DISK_DEV=/dev/disk4 - -# Alternatively, set an environment variable: -export SD_DISK_DEV=/dev/disk4 -``` - -The firmware is built as `firmware/build/mp1corea7/medium/main.elf` and `main.uimg` -in the same directory. The .elf file is used when debugging, and the .uimg file -is used when copying firmware to NOR Flash or an SD card. - -*Optional*: if you have multiple versions of the gcc arm toolchain installed and don't want to -change your PATH for this project, you can set the METAMODULE_ARM_NONE_EABI_PATH var like this: - -``` -# Put in your bashrc/zshrc for convenience: -# Note the trailing slash (required) -export METAMODULE_ARM_NONE_EABI_PATH=/path/to/arm-gnu-toolchain-12.x-relX/bin/ -``` - -### Console output - -You can view the console output by connecting a USB-UART cable to the TX pin of -the debug header (next to the SWD header). The TX pin is labeled (upper-right -pin). The bottom four pins are all GND. Settings are 115200, 8N1. - - -### Loading firmware onto the device - -You have several choices for how to load the firmware applcation. Each one is covered -in a section below: - -1) Load in RAM over SWD/JTAG - -2) Load into NOR Flash over DFU-USB - -3) Boot from SD Card - - -#### Load in RAM over SWD/JTAG - -This is the preferred method for active firmware development. It requires a -JTAG programmer. - -Attach a JTAG debugger to the 10-pin connector at the top of the module labeled -"SWD". The protocol is actually JTAG, despite the header's name, though SWD may -work since the only difference is the tRST pin instead of NRST. - -If you are already running the application and just need to debug, you can just -attach without loading. - -If you need to load new firmware, then do this: - -1) Install a jumper on `Control Expander` header that bridges the top-left pin -and the pin just to the right of it. Make sure you use the right header, it's -the one above the Wifi header, near the `y` and `z` pots. The jumper should be -horizontal, not vertical, on the top row of pins all the way to the left: - -``` - Control - Expander - [====] o o - o o o o -``` - -2) Power off and back on (full power-cycle is required). - -The console will show: - -``` -Freeze pin detected active, freezing. -Ready to load firmware to DDR RAM via SWD/JTAG. -``` - -Use Jflash, TRACE32, Ozone, openOCD/arm-none-eabi-gdb, etc to load the main.elf file. -If you have a JLink connected, you can program with this; - -``` -make jprog -``` - -This should take 15-30 seconds. - -For other methods, just load the .elf file and then start executing from 0xC0200040. - -Note: If you are familiar with flashing Cortex-M series MCUs, you will notice -some differences. One is that Flash is on an external chip. Another difference is -that the main RAM (DDR RAM) is not available until software initializes it. The -on-board NOR Flash chip has a bootloader installed -([MP1-Boot](https://github.com/4ms/mp1-boot), which is the FSBL). This is -loaded by the BOOTROM on power-up. The MP1-Boot bootloader is responsible for -initializing the DDR RAM peripheral. Obviously, this must be done before -loading the firmware into DDR RAM. So, unlike a Cortex-M chip, you must run a -bootloader before programming the device. However, one of the first things an -application does when it starts running is to enable the MMU and setup various -memory regions, some of which are not writable. Thus, the only time in which -it's possible to load firmware to RAM is after the bootloader has initialized -RAM but before the application has started. To handle this, MP1-Boot has a -"Freeze pin" option. When this pin is detected low (jumper is installed), then -MP1-Boot will halt execution (freeze) after initializing RAM. - -#### Load into NOR Flash over DFU-USB - -Loading onto NOR Flash will flash the firmware into the on-board FLASH chip so -you can boot normally without a computer connected. It takes a minute or two, -so this is a good way to flash firmware infrequently, for example, flashing the -latest stable firwmare version. This is not recommended if you're doing active -firmware development since it's slow (use SWD/JTAG in that case). - -Power cycle the module while holding down the rotary encoder button. This -forces an alt firmware to be loaded from NOR Flash (which is a USB-DFU -bootloader). Make sure the jumper mentioned in the SWD/JTAG section is not installed. -If you are using the UART console, then you'll see this in the console: - -``` -USB DFU Loader Starting... -QSPI is initialized. -Connect a USB cable to the computer. -Run `dfu-util --list` in a terminal and you should see this device. -``` - -The button will be flashing green when in USB-DFU bootloader mode. - -Connect a USB cable from a computer to the module. - -You can use a web-based loader [such as this -one](https://devanlai.github.io/webdfu/dfu-util/). Click Connect, and then -select "STM Device in DFU Mode". Then click "Choose File" and select the uimg -file you just built at `build/mp1corea7/medium/main.uimg`. Then click -"Download". There may be an error `DFU GETSTATUS failed: ControlTransferIn -failed: NetworkError: Failed to execute 'controlTransferIn' on 'USBDevice': A -transfer error has occurred.` This is normal, and is not an error. It's safe to -ignore this. - - -Or use the command line (you must have [dfu-util](https://dfu-util.sourceforge.net/) installed): - -``` -make flash-dfu -``` - - -This command loads the main.uimg file to the default address (0x70080000). -It calls `dfu-util -a 0 -s 0x70080000 -D build/mp1corea7/medium/main.uimg` - -This will take between 60 and 120 seconds. -When it's done, unplug the USB cable, power-cycle, and the new code will start up. - - -#### Boot from SD Card - -You need a dedicated SD Card, all contents will be erased. A 16GB card is common and works fine, -but smaller or larger should work too. - -You first need to format, partition, and install the bootloader on the card. This only needs -to happen once when you use a new SD Card. - -``` -make format-sd -``` - -This will ask you for the device path (/dev/disk4, for example). Make sure you get it right, because the -script will run `mkfs` or `diskutil eraseDisk`. - -After running this, you will need to eject and re-insert the SD Card because the volumes have changed. - -Then do: - -``` -make flash-bootloader-sd -``` - -This will build the bootloader (mp1-boot) and use `dd` to load it onto the first two partitions of the SD Card. -You will again be asked for the drive name. - -You now have a bootable SD Card. You shouldn't need to repeat the above steps unless you get a new SD Card. - -To flash the application, do this: - -``` -make flash-app-sd -``` - -This will build the application as normal, and then use `dd` to copy it to the fourth partition. - -Eject the card and insert it into the Meta Module. - -To tell the Meta Module to boot using the SD Card, you need to change the BOOT DIP switches. -These are located on the back of the PCB, under the screen near the rotary encoder. -They are labeled "BOOT0_2". There are two switches. Look at the diagram printed on the PCB. -To boot with the SD, both switches should be pushed to the left. -If you want to back to booting from Flash (internal Flash chip), then flip the bottom switch to the right. - -### Automatically generated materials - -Several files are automatically generated using python scripts, e.g. faceplate LVGL code. These generated files are already committed for a few reasons: 1) the conversion process uses some specific external programs (inkscape CLI, and a particular version of node); 2) generating all the assets takes a long time; 3) the assets don't change very often (if ever) and are completely orthogonal to the code. Also conversion from SVG to PNG can generate a file that is visually the same but has a different binary representation, causing lots of noise in the git diffs. However if you wish to (re)-generate these files, the following commands can be run: - -``` -# Generating LVGL image files for components -make comp-images - -# Generating LVGL image files for faceplates -make faceplate-images - -# Update image_list.hh -make image-list - -# Updating/creating 4ms VCV artwork SVGs files from *_info.svg files -make vcv-images - -# Updating/creating CoreModule *_info.hh files from *_info.svg -make module-infos - -# All of the above -make regenerate-all -``` - -### Instructions for adding a new module (WIP) - -First step is add the module code as a git submodule: - -``` -git submodule https://github.com// firmware/vcv_ports/ -``` - -Create folder: - -``` -firmware/src/gui/images//modules/ -``` - -TODO which glue files to make/how, currently too complicated: - -* `firmware/vcv_ports/glue//modules.cmake` - list of modules mainly + list of svgs -* `firmware/vcv_ports/glue//CMakeLists.txt` - creates library, include directories, compile arguments - - -Add the following to `firmware\CMakeLists.txt`: - -``` -# List of brands -set(brands - - ... -) -``` - -You will also need to add the plugin to the Hub whitelist (see `vcv/src/mapping/module_directory.hh`). +- [Firmware Boot Process](../docs/Firmware-Boot.md) diff --git a/firmware/lib/esp-serial-flasher b/firmware/lib/esp-serial-flasher new file mode 160000 index 000000000..0a0073ec8 --- /dev/null +++ b/firmware/lib/esp-serial-flasher @@ -0,0 +1 @@ +Subproject commit 0a0073ec8056354cd66ba6ed70abc759e56e3c11 diff --git a/firmware/lib/flatbuffers b/firmware/lib/flatbuffers new file mode 160000 index 000000000..01834de25 --- /dev/null +++ b/firmware/lib/flatbuffers @@ -0,0 +1 @@ +Subproject commit 01834de25e4bf3975a9a00e816292b1ad0fe184b diff --git a/firmware/lib/lockfree b/firmware/lib/lockfree new file mode 160000 index 000000000..74e5c6062 --- /dev/null +++ b/firmware/lib/lockfree @@ -0,0 +1 @@ +Subproject commit 74e5c6062ec2be0f9d287d8806383ff66451cc52 diff --git a/firmware/lib/mdrivlib b/firmware/lib/mdrivlib index 14d5d8b1a..de131f18c 160000 --- a/firmware/lib/mdrivlib +++ b/firmware/lib/mdrivlib @@ -1 +1 @@ -Subproject commit 14d5d8b1ac9d4a36b307dd411ac558d6c1001f0b +Subproject commit de131f18c1d5c88bac40bd7629d25e3e9c7e6fb9 diff --git a/firmware/src/CMakeLists.txt b/firmware/src/CMakeLists.txt index c94a6b0d7..c7d5d46cf 100644 --- a/firmware/src/CMakeLists.txt +++ b/firmware/src/CMakeLists.txt @@ -1,6 +1,8 @@ include(${CMAKE_SOURCE_DIR}/cmake/common.cmake) include(${CMAKE_SOURCE_DIR}/cmake/arch_mp15xa7.cmake) +option(ENABLE_WIFI_BRIDGE OFF "Enable serial bridge to wifi module") + set(FWDIR ${CMAKE_SOURCE_DIR}) set(SHARED ${FWDIR}/../shared) @@ -104,6 +106,36 @@ target_link_libraries(ui PRIVATE lvgl::lvgl mp15xa7_arch) # Fix for SLS generated files (See https://forum.squareline.io/t/unused-variable-target-warning-when-compiling/1610/3 ) target_compile_options(ui PRIVATE -Wno-unused-variable) + +# +# ESP Serial Flasher +# + +if (ENABLE_WIFI_BRIDGE) + set(ESP_SERIAL_FLASHER_PORT "CUSTOM") + add_subdirectory(${FWDIR}/lib/esp-serial-flasher ${CMAKE_CURRENT_BINARY_DIR}/esp-serial-flasher) + target_link_libraries(flasher PRIVATE mp15xa7_arch) +endif() + +# +# Lockfree +# + +if (ENABLE_WIFI_BRIDGE) + add_subdirectory(${FWDIR}/lib/lockfree ${CMAKE_CURRENT_BINARY_DIR}/lockfree) + target_link_libraries(lockfree INTERFACE mp15xa7_arch) +endif() + +# +# Flatbuffers +# (Manually create a target because original cmake includes the flatc compiler) +# + +if (ENABLE_WIFI_BRIDGE) + add_library(flatbuffers INTERFACE) + target_include_directories(flatbuffers INTERFACE ${FWDIR}/lib/flatbuffers/include/) +endif() + # # Main App # @@ -126,6 +158,7 @@ add_executable( ${FWDIR}/src/fs/fatfs/diskio.cc ${FWDIR}/src/fs/fatfs/fattime.cc ${FWDIR}/src/fs/time_convert.cc + # ${SHARED}/CoreModules/hub/hub_medium.cc ${SHARED}/patch_convert/yaml_to_patch.cc @@ -180,6 +213,36 @@ add_executable( ${FACEPLATE_ARTWORK_SOURCES} ) +if (ENABLE_WIFI_BRIDGE) + + target_compile_definitions(main.elf PRIVATE + ENABLE_WIFI_BRIDGE=1 + ) + + target_sources(main.elf PRIVATE + ${FWDIR}/src/wifi/wifi_interface.cc + ${FWDIR}/src/wifi/flasher/flasher.cpp + ${FWDIR}/src/wifi/flasher/flasher.h + ${FWDIR}/src/wifi/flasher/implementation.cpp + ${FWDIR}/src/wifi/flasher/BufferedUSART.h + ${FWDIR}/src/wifi/flasher/BufferedUSART.cpp + ${FWDIR}/src/wifi/comm/BufferedUSART2.h + ${FWDIR}/src/wifi/comm/BufferedUSART2.cpp + ${FWDIR}/src/wifi/comm/framing/Configuration.h + ${FWDIR}/src/wifi/comm/framing/Deframer.cpp + ${FWDIR}/src/wifi/comm/framing/Deframer.h + ${FWDIR}/src/wifi/comm/framing/DynamicDeframer.cpp + ${FWDIR}/src/wifi/comm/framing/DynamicDeframer.h + ${FWDIR}/src/wifi/comm/framing/Framer.cpp + ${FWDIR}/src/wifi/comm/framing/Framer.h + ${FWDIR}/src/wifi/comm/framing/StaticDeframer.h + ) +else() + target_compile_definitions(main.elf PRIVATE + ENABLE_WIFI_BRIDGE=0 + ) +endif() + # Fixup for compiler warning on files that include LVGL 8.3.4 headers: set_source_files_properties( ${FWDIR}/src/core_a7/aux_core_main.cc @@ -212,12 +275,41 @@ target_include_directories( ${FWDIR}/lib/jansson/jansson/src ) +if (ENABLE_WIFI_BRIDGE) + + # Locate flatbuffer compiler + # This falls back to the local flatabuffers repository if not found on PATH + find_program(FLATBUFFERS_FLATC_EXECUTABLE flatc + PATHS ${FWDIR}/lib/flatbuffers/build + REQUIRED) + + message("Found flatbuffers compiler at " ${FLATBUFFERS_FLATC_EXECUTABLE}) + + # Compile flat buffer definitions to cpp headers + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${FWDIR}/lib/flatbuffers/CMake) + include(BuildFlatBuffers) + build_flatbuffers("${CMAKE_CURRENT_LIST_DIR}/wifi/flat/all.fbs" "" flatbuffer_messages_utils "" ${CMAKE_CURRENT_BINARY_DIR}/flat "" "") + + # Create library with generated flatbuffer headers + add_library(flatbuffer_messages INTERFACE) + add_dependencies(flatbuffer_messages flatbuffer_messages_utils) + target_include_directories(flatbuffer_messages INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/flat) +endif() + target_link_libraries(main.elf PRIVATE lvgl::lvgl mp15xa7_arch mdrivlib_interface ui ryml VCV_adaptor) +if (ENABLE_WIFI_BRIDGE) + target_link_libraries(main.elf PRIVATE flasher lockfree flatbuffers flatbuffer_messages) +endif() + foreach(brand ${brands}) target_link_libraries(main.elf PRIVATE ${brand}Library) endforeach() +if (METAMODULE_PCB_VERSION) + target_compile_definitions(main.elf PRIVATE METAMODULE_PCB_VERSION=${METAMODULE_PCB_VERSION}) +endif() + add_dependencies(main.elf m4_firmware) target_link_script( diff --git a/firmware/src/audio/audio.cc b/firmware/src/audio/audio.cc index 79beb3801..0eaa26ddf 100644 --- a/firmware/src/audio/audio.cc +++ b/firmware/src/audio/audio.cc @@ -117,7 +117,7 @@ void AudioStream::start() { AudioConf::SampleT AudioStream::get_audio_output(int output_id) { auto raw_out = player.get_panel_output(output_id) * mute_ctr; - raw_out = -raw_out / OutputHighRangeVolts; + raw_out = -raw_out / OutputMaxVolts; auto scaled_out = AudioOutFrame::scaleOutput(raw_out); return scaled_out; } diff --git a/firmware/src/console/uart_log.hh b/firmware/src/console/uart_log.hh index 0c8aa064c..38b0cc228 100644 --- a/firmware/src/console/uart_log.hh +++ b/firmware/src/console/uart_log.hh @@ -8,7 +8,7 @@ namespace MetaModule { struct UartLog { - static inline mdrivlib::Uart log_uart; + static inline mdrivlib::Uart log_uart; UartLog() { init(); diff --git a/firmware/src/gui/elements/helpers.hh b/firmware/src/gui/elements/helpers.hh index c52c62d8f..adf94f54b 100644 --- a/firmware/src/gui/elements/helpers.hh +++ b/firmware/src/gui/elements/helpers.hh @@ -1,9 +1,7 @@ #pragma once -#include "CoreModules/elements/element_counter.hh" #include "CoreModules/moduleFactory.hh" #include "patch/patch.hh" #include "patch/patch_data.hh" -#include namespace MetaModule { @@ -15,6 +13,7 @@ struct FullElementName { enum class ElementType { Param, Input, Output, Light }; +// return the module name and element name, when all we have is the IDs and type (not the Element) inline FullElementName get_full_element_name(unsigned module_id, unsigned element_idx, ElementType type, PatchData const &patch) { FullElementName fullname{"?", "?"}; @@ -25,17 +24,20 @@ get_full_element_name(unsigned module_id, unsigned element_idx, ElementType type auto &info = ModuleFactory::getModuleInfo(patch.module_slugs[module_id]); if (info.width_hp) { - auto res = std::find_if(info.indices.begin(), info.indices.end(), [=](auto idx) { - return (type == ElementType::Param) ? element_idx == idx.param_idx : - (type == ElementType::Input) ? element_idx == idx.input_idx : - (type == ElementType::Output) ? element_idx == idx.output_idx : - (type == ElementType::Light) ? element_idx == idx.light_idx : - false; - }); - - if (res != info.indices.end()) { - auto el_id = std::distance(info.indices.begin(), res); - fullname.element_name = base_element(info.elements[el_id]).short_name; + // Search in reverse (the matching element is the last one with the matching index) + for (int el_id = info.indices.size() - 1; el_id >= 0; el_id--) { + + auto idx = info.indices[el_id]; + + bool is_found = (type == ElementType::Param) ? element_idx == idx.param_idx : + (type == ElementType::Input) ? element_idx == idx.input_idx : + (type == ElementType::Output) ? element_idx == idx.output_idx : + (type == ElementType::Light) ? element_idx == idx.light_idx : + false; + if (is_found) { + fullname.element_name = base_element(info.elements[el_id]).short_name; + break; + } } } } diff --git a/firmware/src/gui/elements/module_drawer.hh b/firmware/src/gui/elements/module_drawer.hh index f5e80dfcf..fe0722f72 100644 --- a/firmware/src/gui/elements/module_drawer.hh +++ b/firmware/src/gui/elements/module_drawer.hh @@ -86,6 +86,9 @@ struct ModuleDrawer { auto mapped_ring = MapRingDrawer::draw_mapped_ring(el, obj, canvas, mapping_id, height); auto count = ElementCount::count(el); + // TODO: for each member of count that's 0, set the corresponding member of indices to 0xFF (or some flag) + // Just to clarify that some members of indices are not to be used (off by one) + // See tests/element_tests.cc SUBCASE("Some indices are invalid if the type does not match") auto element_ctx = GuiElement{obj, mapped_ring, (uint16_t)module_idx, count, indices, mapping_id}; indices = indices + count; diff --git a/firmware/src/medium/conf/console_uart_conf.hh b/firmware/src/medium/conf/console_uart_conf.hh index f64cde202..289f928ff 100644 --- a/firmware/src/medium/conf/console_uart_conf.hh +++ b/firmware/src/medium/conf/console_uart_conf.hh @@ -2,7 +2,7 @@ #include "drivers/uart_conf.hh" //p11: -constexpr inline UartConf UartConfig{ +constexpr inline UartConf LogUartConfig{ .base_addr = UART7_BASE, .TXPin = {mdrivlib::GPIO::B, mdrivlib::PinNum::_4, mdrivlib::PinAF::AltFunc13}, .RXPin = {mdrivlib::GPIO::B, mdrivlib::PinNum::_3, mdrivlib::PinAF::AltFunc13}, diff --git a/firmware/src/medium/conf/scaling_config.hh b/firmware/src/medium/conf/scaling_config.hh index 054f69d33..7970167b7 100644 --- a/firmware/src/medium/conf/scaling_config.hh +++ b/firmware/src/medium/conf/scaling_config.hh @@ -8,7 +8,8 @@ static inline constexpr int32_t InputHighRangeMillivolts = 10310; static inline constexpr float InputRangeVolts = InputHighRangeVolts - InputLowRangeVolts; static inline constexpr float InputRangeCenterVolts = (InputHighRangeVolts + InputLowRangeVolts) / 2.f; -static inline constexpr float OutputLowRangeVolts = -8.59f; -static inline constexpr float OutputHighRangeVolts = 8.59f; -static inline constexpr float OutputRangeVolts = OutputHighRangeVolts - OutputLowRangeVolts; -static inline constexpr float OutputRangeCenterVolts = (OutputHighRangeVolts + OutputLowRangeVolts) / 2.f; +#if defined(METAMODULE_PCB_VERSION) && (METAMODULE_PCB_VERSION == 12) +static inline constexpr float OutputMaxVolts = 10.4f; +#else +static inline constexpr float OutputMaxVolts = 8.59f; +#endif diff --git a/firmware/src/medium/conf/wifi_uart_conf.hh b/firmware/src/medium/conf/wifi_uart_conf.hh new file mode 100644 index 000000000..54fd66311 --- /dev/null +++ b/firmware/src/medium/conf/wifi_uart_conf.hh @@ -0,0 +1,36 @@ +#pragma once +#include "drivers/uart_conf.hh" +#include "drivers/pin.hh" + + +constexpr inline UartConf WifiBootloaderUartConfig{ + .base_addr = USART6_BASE, + .TXPin = {mdrivlib::GPIO::G, mdrivlib::PinNum::_14, mdrivlib::PinAF::AltFunc7}, + .RXPin = {mdrivlib::GPIO::G, mdrivlib::PinNum::_9, mdrivlib::PinAF::AltFunc7}, + .mode = UartConf::Mode::TXRX, + .baud = 115200, // 230400, 921600 + .wordlen = 8, + .parity = UartConf::Parity::None, + .stopbits = UartConf::StopBits::_1, +}; + +constexpr inline UartConf WifiUartConfig{ + .base_addr = UART5_BASE, + .TXPin = {mdrivlib::GPIO::B, mdrivlib::PinNum::_13, mdrivlib::PinAF::AltFunc14}, + .RXPin = {mdrivlib::GPIO::B, mdrivlib::PinNum::_12, mdrivlib::PinAF::AltFunc14}, + .mode = UartConf::Mode::TXRX, + .baud = 115200, // 230400, 921600 + .wordlen = 8, + .parity = UartConf::Parity::None, + .stopbits = UartConf::StopBits::_1, +}; + +constexpr inline mdrivlib::PinDef WifiBootloaderResetConfig{ + mdrivlib::GPIO::G, + mdrivlib::PinNum::_13, +}; + +constexpr inline mdrivlib::PinDef WifiBootloaderBootSelectConfig{ + mdrivlib::GPIO::G, + mdrivlib::PinNum::_8, +}; \ No newline at end of file diff --git a/firmware/src/wifi/comm/BufferedUSART2.cpp b/firmware/src/wifi/comm/BufferedUSART2.cpp new file mode 100644 index 000000000..259c9ff2c --- /dev/null +++ b/firmware/src/wifi/comm/BufferedUSART2.cpp @@ -0,0 +1,73 @@ +#include "BufferedUSART2.h" + +#include "conf/wifi_uart_conf.hh" +#include "drivers/uart.hh" +#include "drivers/uart_conf.hh" +#include "drivers/interrupt.hh" + +#include +#include +#include + +#include + +static mdrivlib::Uart commMain; +Queue BufferedUSART2::queue; + +#define USART_PERIPH UART5 +#define USART_IRQ UART5_IRQn +#define USART_IRQ_PRIO 3 + +void BufferedUSART2::init() +{ + initPeripheral(); +} + +bool BufferedUSART2::setBaudrate(uint32_t baudRate) +{ + return commMain.set_baudrate(baudRate); +} + +void BufferedUSART2::initPeripheral() +{ + commMain.init(); + + mdrivlib::Interrupt::register_and_start_isr(USART_IRQ, USART_IRQ_PRIO, 0, []() + { + if (LL_USART_IsActiveFlag_RXNE_RXFNE(USART_PERIPH)) + { + do + { + auto val = USART_PERIPH->RDR; + + auto result = queue.Push(val); + if (not result) + { + pr_err("RX Overrun\n"); + } + } + while (LL_USART_IsActiveFlag_RXNE(USART_PERIPH)); + } + else + { + printf_("No flag\n"); + } + }); + + // read RX from hardware to clear RXNE flag + (void)USART_PERIPH->RDR; + + LL_USART_EnableIT_RXNE_RXFNE(USART_PERIPH); +} + +void BufferedUSART2::transmit(uint8_t val) +{ + commMain.transmit(val); +} + +std::optional BufferedUSART2::receive() +{ + return queue.PopOptional(); +} + + diff --git a/firmware/src/wifi/comm/BufferedUSART2.h b/firmware/src/wifi/comm/BufferedUSART2.h new file mode 100644 index 000000000..bbfcf97ae --- /dev/null +++ b/firmware/src/wifi/comm/BufferedUSART2.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +using namespace lockfree::spsc; + + + +class BufferedUSART2 +{ +public: + static void init(); + + static bool setBaudrate(uint32_t); + + static void transmit(uint8_t); + static std::optional receive(); + +private: + static void initPeripheral(); + +private: + static Queue queue; +}; \ No newline at end of file diff --git a/firmware/src/wifi/comm/framing/Configuration.h b/firmware/src/wifi/comm/framing/Configuration.h new file mode 100644 index 000000000..72b499018 --- /dev/null +++ b/firmware/src/wifi/comm/framing/Configuration.h @@ -0,0 +1,18 @@ +/* Author: Lennart@binarylabs.dev */ + +#pragma once + +#include + +namespace Framing +{ + +struct Configuration_t +{ + uint8_t start; + uint8_t end; + uint8_t escape; +}; + + +} diff --git a/firmware/src/wifi/comm/framing/Deframer.cpp b/firmware/src/wifi/comm/framing/Deframer.cpp new file mode 100644 index 000000000..f6a7f15fd --- /dev/null +++ b/firmware/src/wifi/comm/framing/Deframer.cpp @@ -0,0 +1,58 @@ +/* Author: Lennart@binarylabs.dev */ + +#include "Deframer.h" + +namespace Framing +{ + +Deframer::Deframer(const Configuration_t& config, const FUNC func_) : func(func_), Configuration(config) +{ + state = WAIT_HEADER; +} + +void Deframer::reset() +{ + doReset(); + state = WAIT_HEADER; +} + +void Deframer::parse(uint8_t thisByte) +{ + if (state == WAIT_HEADER) + { + if (thisByte == Configuration.start) + { + state = IN_MESSAGE; + } + } + else if (state == IN_MESSAGE) + { + if (thisByte == Configuration.start) + { + doReset(); + state = WAIT_HEADER; + } + else if (thisByte == Configuration.end) + { + doComplete(); + doReset(); + state = WAIT_HEADER; + } + else if (thisByte == Configuration.escape) + { + state = ESCAPE; + } + else + { + doAdd(thisByte); + } + } + else if (state == ESCAPE) + { + doAdd(thisByte); + state = IN_MESSAGE; + } +} + + +} diff --git a/firmware/src/wifi/comm/framing/Deframer.h b/firmware/src/wifi/comm/framing/Deframer.h new file mode 100644 index 000000000..f5eaafd83 --- /dev/null +++ b/firmware/src/wifi/comm/framing/Deframer.h @@ -0,0 +1,39 @@ +/* Author: Lennart@binarylabs.dev */ + +#pragma once + +#include "Configuration.h" + +#include +#include + +namespace Framing +{ + +class Deframer +{ +public: + using FUNC = std::function)>; + + Deframer(const Configuration_t&, const FUNC); + virtual ~Deframer() = default; + + void parse(uint8_t); + void reset(); + +private: + virtual void doReset() = 0; + virtual void doAdd(uint8_t) = 0; + virtual void doComplete() = 0; + +protected: + FUNC func; + +private: + const Configuration_t Configuration; + enum ParsingState_t {WAIT_HEADER, IN_MESSAGE, ESCAPE}; + ParsingState_t state; +}; + +} + diff --git a/firmware/src/wifi/comm/framing/DynamicDeframer.cpp b/firmware/src/wifi/comm/framing/DynamicDeframer.cpp new file mode 100644 index 000000000..24c711f82 --- /dev/null +++ b/firmware/src/wifi/comm/framing/DynamicDeframer.cpp @@ -0,0 +1,25 @@ +/* Author: Lennart@binarylabs.dev */ + +#include "DynamicDeframer.h" + +namespace Framing +{ + + +void DynamicDeframer::doReset() +{ + receiveBuffer.clear(); +} + +void DynamicDeframer::doAdd(uint8_t data) +{ + receiveBuffer.push_back(data); +} + +void DynamicDeframer::doComplete() +{ + func({receiveBuffer.data(), receiveBuffer.size()}); +} + +} + diff --git a/firmware/src/wifi/comm/framing/DynamicDeframer.h b/firmware/src/wifi/comm/framing/DynamicDeframer.h new file mode 100644 index 000000000..bba7c3154 --- /dev/null +++ b/firmware/src/wifi/comm/framing/DynamicDeframer.h @@ -0,0 +1,28 @@ +/* Author: Lennart@binarylabs.dev */ + +#pragma once + +#include "Deframer.h" + +#include + +namespace Framing +{ + +class DynamicDeframer : public Deframer +{ +public: + DynamicDeframer(const Configuration_t& config_, const FUNC func_) : Deframer(config_, func_) {}; + ~DynamicDeframer() = default; + +private: + virtual void doReset() override; + virtual void doAdd(uint8_t) override; + virtual void doComplete() override; + +private: + std::vector receiveBuffer; +}; + +} + diff --git a/firmware/src/wifi/comm/framing/Framer.cpp b/firmware/src/wifi/comm/framing/Framer.cpp new file mode 100644 index 000000000..56e034807 --- /dev/null +++ b/firmware/src/wifi/comm/framing/Framer.cpp @@ -0,0 +1,57 @@ +/* Author: Lennart@binarylabs.dev */ + +#include "Framer.h" + +#include + +namespace Framing +{ + +Framer::Framer(const Configuration_t& config_, const FUNC func_) : Configuration(config_), partialPacket(false), func(func_) +{}; + +void Framer::doStart() +{ + if (!partialPacket) + { + func(Configuration.start); + partialPacket = true; + } + else + { + pr_err("Framer: Cannot start because already inside package"); + } +} +void Framer::doEnd() +{ + if (partialPacket) + { + func(Configuration.end); + partialPacket = false; + } + else + { + pr_err("Framer: Cannot end because not inside package"); + } +} +void Framer::doPayload(uint8_t byte) +{ + if (partialPacket) + { + if (byte == Configuration.start || byte == Configuration.end || byte == Configuration.escape) + { + func(Configuration.escape); + } + + func(byte); + } + else + { + pr_err("Framer: Cannot send payload because not inside package"); + } +} + +} + + + diff --git a/firmware/src/wifi/comm/framing/Framer.h b/firmware/src/wifi/comm/framing/Framer.h new file mode 100644 index 000000000..26fa31a2a --- /dev/null +++ b/firmware/src/wifi/comm/framing/Framer.h @@ -0,0 +1,45 @@ +/* Author: Lennart@binarylabs.dev */ + +#pragma once + +#include "Configuration.h" + +#include + +namespace Framing +{ + +class Framer +{ +public: + using FUNC = std::function; + + Framer(const Configuration_t&, const FUNC); + + template + void send(const CONTAINER& data) + { + doStart(); + + for (auto& byte : data) + { + doPayload(byte); + } + + doEnd(); + } + + void doStart(); + void doEnd(); + void doPayload(uint8_t); + +private: + const Configuration_t Configuration; + bool partialPacket; + FUNC func; + +}; + +} + + diff --git a/firmware/src/wifi/comm/framing/StaticDeframer.h b/firmware/src/wifi/comm/framing/StaticDeframer.h new file mode 100644 index 000000000..14f3f3999 --- /dev/null +++ b/firmware/src/wifi/comm/framing/StaticDeframer.h @@ -0,0 +1,49 @@ +/* Author: Lennart@binarylabs.dev */ + +#pragma once + +#include "Deframer.h" +#include + +#include + +namespace Framing +{ + +template +class StaticDeframer : public Deframer +{ +public: + StaticDeframer(const Configuration_t& config_, const FUNC func_) : Deframer(config_, func_) {}; + ~StaticDeframer() = default; + +private: + virtual void doReset() override + { + fillCount = 0; + } + virtual void doAdd(uint8_t byte) override + { + if (fillCount < SIZE) + { + receiveBuffer[fillCount++] = byte; + } + else + { + pr_err("StaticDeframer: Frame larger than maximum size"); + doReset(); + } + } + virtual void doComplete() override + { + func({receiveBuffer.data(), fillCount}); + } + +private: + std::array receiveBuffer; + std::size_t fillCount = 0; +}; + +} + + diff --git a/firmware/src/wifi/flasher/BufferedUSART.cpp b/firmware/src/wifi/flasher/BufferedUSART.cpp new file mode 100644 index 000000000..b7a0a316c --- /dev/null +++ b/firmware/src/wifi/flasher/BufferedUSART.cpp @@ -0,0 +1,72 @@ +#include "BufferedUSART.h" + +#include "conf/wifi_uart_conf.hh" +#include "drivers/uart.hh" +#include "drivers/uart_conf.hh" +#include "drivers/interrupt.hh" + +#include +#include + +#include + +static mdrivlib::Uart commBoot; +Queue BufferedUSART::queue; + +#define USART_PERIPH USART6 +#define USART_IRQ USART6_IRQn +#define USART_IRQ_PRIO 3 + +void BufferedUSART::init() +{ + initPeripheral(); +} + +bool BufferedUSART::setBaudrate(uint32_t baudRate) +{ + return commBoot.set_baudrate(baudRate); +} + +void BufferedUSART::initPeripheral() +{ + commBoot.init(); + + mdrivlib::Interrupt::register_and_start_isr(USART_IRQ, USART_IRQ_PRIO, 0, []() + { + if (LL_USART_IsActiveFlag_RXNE_RXFNE(USART_PERIPH)) + { + do + { + auto val = USART_PERIPH->RDR; + + auto result = queue.Push(val); + if (not result) + { + pr_err("RX Overrun\n"); + } + } + while (LL_USART_IsActiveFlag_RXNE(USART_PERIPH)); + } + else + { + printf_("No flag\n"); + } + }); + + // read RX from hardware to clear RXNE flag + (void)USART_PERIPH->RDR; + + LL_USART_EnableIT_RXNE_RXFNE(USART_PERIPH); +} + +void BufferedUSART::transmit(uint8_t val) +{ + commBoot.transmit(val); +} + +std::optional BufferedUSART::receive() +{ + return queue.PopOptional(); +} + + diff --git a/firmware/src/wifi/flasher/BufferedUSART.h b/firmware/src/wifi/flasher/BufferedUSART.h new file mode 100644 index 000000000..9fb8c57a8 --- /dev/null +++ b/firmware/src/wifi/flasher/BufferedUSART.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +using namespace lockfree::spsc; + + + +class BufferedUSART +{ +public: + static void init(); + + static bool setBaudrate(uint32_t); + + static void transmit(uint8_t); + static std::optional receive(); + +private: + static void initPeripheral(); + +private: + static Queue queue; +}; \ No newline at end of file diff --git a/firmware/src/wifi/flasher/flasher.cpp b/firmware/src/wifi/flasher/flasher.cpp new file mode 100644 index 000000000..cf29f0d55 --- /dev/null +++ b/firmware/src/wifi/flasher/flasher.cpp @@ -0,0 +1,115 @@ +#include "flasher.h" + +#include "custom_port.h" +#include "esp_loader.h" +#include "esp_loader_io.h" + +#include + +#include + +namespace Flasher +{ + +esp_loader_error_t init(uint32_t baudrate) +{ + loader_port_init(); + + esp_loader_connect_args_t connect_config = ESP_LOADER_CONNECT_DEFAULT(); + + esp_loader_error_t err = esp_loader_connect(&connect_config); + if (err != ESP_LOADER_SUCCESS) + { + pr_err("Flasher: Cannot connect to target. Error: %u\n", err); + } + else + { + pr_dbg("Flasher: Connected to target\n"); + + err = esp_loader_change_transmission_rate(baudrate); + if (err == ESP_LOADER_ERROR_UNSUPPORTED_FUNC) + { + pr_err("Flasher: ESP does not support change transmission rate command.\n"); + } + else if (err != ESP_LOADER_SUCCESS) + { + pr_err("Flasher: Unable to change transmission rate on target.\n"); + } + else + { + err = loader_port_change_transmission_rate(baudrate); + if (err != ESP_LOADER_SUCCESS) + { + pr_err("Flasher: Unable to change transmission rate.\n"); + } + else + { + pr_dbg("Flasher: Transmission rate changed changed\n"); + } + } + } + + return err; +} + +esp_loader_error_t flash(uint32_t address, std::span buffer) +{ + esp_loader_error_t err; + + const std::size_t BatchSize = 1024; + std::array BatchBuffer; + + pr_dbg("Flasher: Erasing flash (this may take a while)...\n"); + err = esp_loader_flash_start(address, buffer.size(), BatchBuffer.size()); + + if (err != ESP_LOADER_SUCCESS) + { + pr_err("Flasher: Erasing flash failed with error %d\n", err); + return err; + } + pr_dbg("Flasher: Start programming\n"); + + const uint8_t* bin_addr = buffer.data(); + const std::size_t binary_size = buffer.size(); + size_t written = 0; + + while (written < binary_size) + { + size_t to_read = std::min(binary_size - written, BatchBuffer.size()); + std::memcpy(BatchBuffer.data(), bin_addr, to_read); + + err = esp_loader_flash_write(BatchBuffer.data(), to_read); + if (err != ESP_LOADER_SUCCESS) + { + pr_err("Packet could not be written! Error %d\n", err); + return err; + } + + bin_addr += to_read; + written += to_read; + + int progress = (int)(((float)written / float(binary_size)) * 100); + pr_dbg("Flasher: Progress: %d %%\n", progress); + }; + + pr_dbg("Flasher: Finished programming\n"); + + #ifdef MD5_ENABLED + err = esp_loader_flash_verify(); + if (err == ESP_LOADER_ERROR_UNSUPPORTED_FUNC) + { + pr_err("Flasher: ESP8266 does not support flash verify command.\n"); + return err; + } + else if (err != ESP_LOADER_SUCCESS) + { + pr_err("Flasher: MD5 does not match. err: %d\n", err); + return err; + } + pr_dbg("Flasher: Flash verified\n"); + #endif + + return ESP_LOADER_SUCCESS; +} + +} \ No newline at end of file diff --git a/firmware/src/wifi/flasher/flasher.h b/firmware/src/wifi/flasher/flasher.h new file mode 100644 index 000000000..8059a2aeb --- /dev/null +++ b/firmware/src/wifi/flasher/flasher.h @@ -0,0 +1,16 @@ +#pragma once + +#include "esp_loader.h" + +#include +#include + + +namespace Flasher +{ + +esp_loader_error_t init(uint32_t baudrate); + +esp_loader_error_t flash(uint32_t, std::span); + +} \ No newline at end of file diff --git a/firmware/src/wifi/flasher/implementation.cpp b/firmware/src/wifi/flasher/implementation.cpp new file mode 100644 index 000000000..78b38238e --- /dev/null +++ b/firmware/src/wifi/flasher/implementation.cpp @@ -0,0 +1,153 @@ +#include "esp_loader.h" +#include "esp_loader_io.h" +#include "custom_port.h" + +#include "conf/wifi_uart_conf.hh" +#include "drivers/pin.hh" + +#include "BufferedUSART.h" + +#include + +#include + +//#define LOG_TRANSACTIONS + +static mdrivlib::Pin resetPin; +static mdrivlib::Pin bootSelectPin; + +extern "C" +{ + +static void serial_debug_print(const uint8_t* data, uint16_t size, bool write) +{ + #ifdef LOG_TRANSACTIONS + if (write) + { + printf_("WRITE "); + } + else + { + printf_("Read "); + } + printf_("Len %u\n", size); + + + for (uint32_t i=0; i +#include + +#include "flasher/flasher.h" +#include "comm/BufferedUSART2.h" +#include "comm/framing/Configuration.h" +#include "comm/framing/StaticDeframer.h" +using namespace Framing; + +#include + + + +#include + +namespace MetaModule +{ + +void WifiInterface::init() +{ + printf_("Initializing Wifi\n"); + + // auto result = Flasher::init(230400); + + BufferedUSART2::init(); + + Configuration_t FrameConfig + { + .start=0x01, + .end=0x02, + .escape=0x03 + }; + + StaticDeframer<100> deframer(FrameConfig, [](auto frame) + { + auto message = GetMessage(frame.data()); + + printf_("Val: %u\n", message->state()); + + }); + + while (true) + { + auto val = BufferedUSART2::receive(); + if (val) + { + deframer.parse(*val); + } + } + +} + +} // namespace MetaModule diff --git a/firmware/src/wifi/wifi_interface.hh b/firmware/src/wifi/wifi_interface.hh new file mode 100644 index 000000000..6f3788515 --- /dev/null +++ b/firmware/src/wifi/wifi_interface.hh @@ -0,0 +1,11 @@ +#pragma once + +namespace MetaModule +{ + +struct WifiInterface { + static void init(); + +private: +}; +} // namespace MetaModule diff --git a/firmware/tests/element_tests.cc b/firmware/tests/element_tests.cc index 2438cb232..ef369e868 100644 --- a/firmware/tests/element_tests.cc +++ b/firmware/tests/element_tests.cc @@ -71,6 +71,12 @@ TEST_CASE("Can get param index") { CHECK(ElementCount::get_indices(Light2).value().light_idx == 4); CHECK(ElementCount::get_indices(SliderLED).value().light_idx == 6); + // This is weird... TODO; change how this works? + // get_indices<>(BaseElement) is not called except in tests, so doesn't matter really + SUBCASE("Some indices are invalid if the type does not match") { + CHECK(ElementCount::get_indices(SliderLED).value().output_idx == 2); + } + CHECK(ElementCount::get_indices(NonExistingKnob) == std::nullopt); // Make sure it can be done at compile-time @@ -91,3 +97,106 @@ TEST_CASE("Can get param index") { static_assert(ElementCount::get_indices(NonExistingKnob) == std::nullopt); } + +namespace MetaModule +{ +struct DEVInfo : ModuleInfoBase { + static constexpr std::string_view slug{"DEV"}; + static constexpr std::string_view description{"Dual EnvVCA"}; + static constexpr uint32_t width_hp = 16; + static constexpr std::string_view svg_filename{"res/modules/DEV_artwork.svg"}; + + using enum Coords; + + static constexpr std::array Elements{{ + Slider25mmVertLED{to_mm<72>(22.415), to_mm<72>(108.25), Center, "Rise A Slider", ""}, + Slider25mmVertLED{to_mm<72>(56.265), to_mm<72>(108.25), Center, "Fall A Slider", ""}, + Slider25mmVertLED{to_mm<72>(174.115), to_mm<72>(108.25), Center, "Rise B Slider", ""}, + Slider25mmVertLED{to_mm<72>(207.965), to_mm<72>(108.25), Center, "Fall B Slider", ""}, + Knob9mm{to_mm<72>(93.13), to_mm<72>(95.86), Center, "Level A", ""}, + Knob9mm{to_mm<72>(137.25), to_mm<72>(95.86), Center, "Level B", ""}, + Knob9mm{to_mm<72>(93.13), to_mm<72>(138.65), Center, "Offset A", ""}, + Knob9mm{to_mm<72>(137.25), to_mm<72>(138.65), Center, "Offset B", ""}, + Knob9mm{to_mm<72>(23.5), to_mm<72>(184.03), Center, "Rise A", ""}, + Knob9mm{to_mm<72>(94.11), to_mm<72>(183.67), Center, "Fall A", ""}, + Knob9mm{to_mm<72>(136.27), to_mm<72>(184.03), Center, "Rise B", ""}, + Knob9mm{to_mm<72>(206.87), to_mm<72>(184.03), Center, "Fall B", ""}, + GateJackInput4ms{to_mm<72>(115.19), to_mm<72>(60.85), Center, "Cycle Gate", ""}, + AnalogJackInput4ms{to_mm<72>(58.56), to_mm<72>(208.68), Center, "Time CV A", ""}, + AnalogJackInput4ms{to_mm<72>(171.86), to_mm<72>(208.68), Center, "Time CV B", ""}, + GateJackInput4ms{to_mm<72>(23.09), to_mm<72>(232.82), Center, "Trig A", ""}, + AnalogJackInput4ms{to_mm<72>(58.52), to_mm<72>(252.75), Center, "Follow A", ""}, + AnalogJackInput4ms{to_mm<72>(171.85), to_mm<72>(252.75), Center, "Follow B", ""}, + GateJackInput4ms{to_mm<72>(207.29), to_mm<72>(232.82), Center, "Trig B", ""}, + AnalogJackInput4ms{to_mm<72>(23.06), to_mm<72>(274.73), Center, "Audio A In", ""}, + AnalogJackInput4ms{to_mm<72>(58.48), to_mm<72>(300.48), Center, "VCA CV A", ""}, + AnalogJackInput4ms{to_mm<72>(171.9), to_mm<72>(300.41), Center, "VCA CV B", ""}, + AnalogJackInput4ms{to_mm<72>(207.29), to_mm<72>(274.76), Center, "Audio B In", ""}, + GateJackOutput4ms{to_mm<72>(95.04), to_mm<72>(233.88), Center, "EOR A", ""}, + GateJackOutput4ms{to_mm<72>(135.28), to_mm<72>(233.99), Center, "EOF B", ""}, + AnalogJackOutput4ms{to_mm<72>(115.22), to_mm<72>(288.78), Center, "OR", ""}, + AnalogJackOutput4ms{to_mm<72>(23.06), to_mm<72>(324.82), Center, "Audio A Out", ""}, + AnalogJackOutput4ms{to_mm<72>(90.49), to_mm<72>(324.82), Center, "Env A Out", ""}, + AnalogJackOutput4ms{to_mm<72>(139.91), to_mm<72>(324.88), Center, "Env B Out", ""}, + AnalogJackOutput4ms{to_mm<72>(207.32), to_mm<72>(324.82), Center, "Audio B Out", ""}, + RedBlueLight{to_mm<72>(45.84), to_mm<72>(176.26), Center, "Rise A Light", ""}, + RedBlueLight{to_mm<72>(70.54), to_mm<72>(176.26), Center, "Fall A Light", ""}, + RedBlueLight{to_mm<72>(159.84), to_mm<72>(176.26), Center, "Rise B Light", ""}, + RedBlueLight{to_mm<72>(184.53), to_mm<72>(176.26), Center, "Fall B Light", ""}, + OrangeLight{to_mm<72>(99.58), to_mm<72>(260.79), Center, "EOR Light", ""}, + OrangeLight{to_mm<72>(130.68), to_mm<72>(261.07), Center, "EOF Light", ""}, + RedBlueLight{to_mm<72>(65.92), to_mm<72>(327.45), Center, "Env A Light", ""}, + RedBlueLight{to_mm<72>(164.39), to_mm<72>(327.52), Center, "Env B Light", ""}, + Toggle3pos{to_mm<72>(17.6), to_mm<72>(41.905), Center, "Rise A Switch", ""}, + Toggle3pos{to_mm<72>(50.49), to_mm<72>(41.905), Center, "Fall A Switch", ""}, + Toggle3pos{to_mm<72>(179.89), to_mm<72>(41.905), Center, "Rise B Switch", ""}, + Toggle3pos{to_mm<72>(212.77), to_mm<72>(41.905), Center, "Fall B Switch", ""}, + LatchingButtonMonoLight{to_mm<72>(82.8), to_mm<72>(41.64), Center, "Cycle A", ""}, + LatchingButtonMonoLight{to_mm<72>(147.61), to_mm<72>(41.68), Center, "Cycle B", ""}, + + OrangeLight{to_mm<72>(130.68), to_mm<72>(261.07), Center, "extra", ""}, + }}; +}; +} // namespace MetaModule + +TEST_CASE("get_indices()") { + auto indices = ElementCount::get_indices(); + + CHECK(indices[11].param_idx == 11); //Fall B + CHECK(indices[12].param_idx == ElementCount::Indices::NoElementMarker); //Cycle Gate, not a param + CHECK(indices[13].param_idx == ElementCount::Indices::NoElementMarker); + + CHECK(indices[38].param_idx == 12); //Rise A Switch = 12 + CHECK(indices[39].param_idx == 13); //Fall A Switch = 13 + + CHECK(indices[42].param_idx == 16); //Cycle A = 16 + CHECK(indices[43].param_idx == 17); //Cycle B = 17 + CHECK(indices[44].param_idx == ElementCount::Indices::NoElementMarker); //extra light, not a param + CHECK(indices[44].light_idx == 20); + + // Check only one element has the matching index + for (auto i = 0; auto ind : indices) { + if (i == 42) + CHECK(ind.param_idx == 16); + else + CHECK_FALSE(ind.param_idx == 16); + + if (i == 11) + CHECK(ind.param_idx == 11); + else + CHECK_FALSE(ind.param_idx == 11); + + if (i == 0) + CHECK(ind.param_idx == 0); + else + CHECK_FALSE(ind.param_idx == 0); + + i++; + } + + constexpr auto CycleA = get(MetaModule::DEVInfo::Elements[42]); + constexpr auto CycleB = get(MetaModule::DEVInfo::Elements[43]); + + static_assert(ElementCount::get_indices(CycleA).value().param_idx == 16); + static_assert(ElementCount::get_indices(CycleB).value().param_idx == 17); +} diff --git a/shared/CoreModules/elements/element_counter.hh b/shared/CoreModules/elements/element_counter.hh index 005236684..c0f029f47 100644 --- a/shared/CoreModules/elements/element_counter.hh +++ b/shared/CoreModules/elements/element_counter.hh @@ -28,6 +28,8 @@ struct Indices { uint8_t input_idx = 0; uint8_t output_idx = 0; + static constexpr uint8_t NoElementMarker = 0xFF; + // Indices + Counts -> Indices constexpr Indices operator+(const Counts rhs) { return {static_cast(param_idx + rhs.num_params), @@ -81,8 +83,14 @@ consteval auto get_indices() { Indices running_total{}; for (unsigned i = 0; auto el : Info::Elements) { - indices[i++] = running_total; Counts el_cnt = count(el); + Indices masked_total = { + .param_idx = el_cnt.num_params > 0 ? running_total.param_idx : Indices::NoElementMarker, + .light_idx = el_cnt.num_lights > 0 ? running_total.light_idx : Indices::NoElementMarker, + .input_idx = el_cnt.num_inputs > 0 ? running_total.input_idx : Indices::NoElementMarker, + .output_idx = el_cnt.num_outputs > 0 ? running_total.output_idx : Indices::NoElementMarker, + }; + indices[i++] = masked_total; running_total = running_total + el_cnt; } diff --git a/simulator/README.md b/simulator/README.md index 018cfea7f..b687be7fc 100644 --- a/simulator/README.md +++ b/simulator/README.md @@ -1,125 +1,12 @@ -## Meta Module Simulator +## MetaModule Simulator -This simulates the screen and the audio outputs. The pixels should be identical to the actual hardware's pixels. +The simulator runs on a host computer and simulates the screen and the audio +outputs. The pixels should be identical to the actual hardware's pixels. The audio should be the same as in hardware (though, your sound card might be AC-coupled, vs. the hardware is DC-coupled). -For information on building the simulator, please see [Building.md](./Building.md). +The following guides are available: -### Keyboard Control +- [Simulator Usage](../docs/simulator-usage.md) +- [Compiling the Simulator](../docs/simulator-building.md) -The simulator simulates the hardware's rotary encoder, button, knobs, and jacks -(patching/unpatching) using the host computer's keyboard. - -#### Navigation - -- `Left Arrow`: Turn encoder counter-clockwise -- `Right Arrow`: Turn encoder clockwise -- `Down Arrow`: Press the encoder -- `Up Arrow`: Press the button (typically goes to the previous page) - -#### Knobs - -The knobs on the Metamodule are labeled with letters (A-F, and u-z). These can be turned with the keyboard -by first pressing a letter to select the knob you want to turn, and then pressing ] or [ to increment/decrement the knob; - -- `a`: select knob A -- `b`: select knob B -- `c`: select knob C -- `d`: select knob D -- `e`: select knob E -- `f`: select knob F -- `u`: select knob u -- `v`: select knob v -- `w`: select knob w -- `x`: select knob x -- `y`: select knob y -- `z`: select knob z - -- `[`: turn the selected knob down 5% -- `]`: turn the selected knob up 5% - -TODO: make `{` and `}` inc/dec by small amounts - -The console will report the knob that was turned and its present value. - -#### Audio Routing -By default, Audio Out 1 and Audio Out 2 are patched to the soundcard's left and right outputs, respectively. -Pressing a number button will change the routing: - -- `1`: Audio Out 1 -> Left, Audio Out 2 -> Right (default) -- `2`: Audio Out 2 -> Left, Audio Out 3 -> Right -- `3`: Audio Out 3 -> Left, Audio Out 4 -> Right -- `4`: Audio Out 4 -> Left, Audio Out 5 -> Right -- `5`: Audio Out 5 -> Left, Audio Out 6 -> Right -- `6`: Audio Out 6 -> Left, Audio Out 7 -> Right -- `7`: Audio Out 7 -> Left, Audio Out 8 -> Right -- `8`: Audio Out 8 -> Left, Audio Out 1 -> Right - -For now, the only way to test the inputs of a virtual module is to create a patch that runs signals -from a virtual module into the virtual module to be tested. -Future features include: -- Route a .wav file on the host computer to an input jack and play it on startup, and/or when a key is pressed. -- Route an audio interface's input jacks to input jacks. - -### Arguments - -Command-line arguments can be passed to the executable. To see the valid arguments, run: - -``` -build/simulator --help -``` - -Or you can type `-h` instead of `--help` - -Currently these are the options: - -#### Audio Output Device - -`-a #`: specify the audio device index to use for output. Run the simulator and look at the console log to see what devices -are available. For example, you might see: - -``` -SDL: 3 audio devices found -0: DELL P2415Q (selected) -1: Studio Display Speakers -2: Mac Studio Speakers -``` - -Device 0 is chosen if the option is not specificed. If you were to do: - -``` -build/simulator -a 1 -``` - -then the Studio Display Speakers would be chosen. - - -#### Zoom level - -`-z ###`: specify the initial zoom amount, as a percentage. Default is 100 (1 hardware pixel = 1 simulator pixel). - -A zoom of 300 or more is useful for inspecting pixel alignment. - -You can also change the zoom while the simulator is running by re-sizing the window with the mouse. - -#### Patch files - -There are two mock "volumes" containing patch files. The `Internal` volume is -the list of factory default patches. This is always loaded. This list is found in -`firmware/src/patch_file/patches_default.hh`. - -Patches are also loaded from a local host computer directory, and appear in the `SD Card` volume. - -`-p path/to/patchfiles/`: The directory to search for patch files (.yml files) - -``` -build/simulator -p ~/MyPatchFiles/ -``` - -If no path is specified, `patches/` will be used (which is a directory in the simulator/ dir). - -If a directory is specified but it contains no valid patch files (or is an -invalid path), the simulator will print an error and ignore it. - - diff --git a/vcv/CHANGELOG.md b/vcv/CHANGELOG.md deleted file mode 100644 index 4d2f2a3d6..000000000 --- a/vcv/CHANGELOG.md +++ /dev/null @@ -1,5 +0,0 @@ -## 4ms VCV Changelog - - -### 2.0.0 -- Initial release \ No newline at end of file diff --git a/vcv/Dockerfile b/vcv/Dockerfile deleted file mode 100644 index cbd057241..000000000 --- a/vcv/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM debian:stretch - -LABEL "com.github.actions.name"="VCVRackPluginBuilder-Windows" -LABEL "com.github.actions.description"="Builds a VCV Rack plugin for Windows" -LABEL "com.github.actions.icon"="headphones" -LABEL "com.github.actions.color"="purple" - -LABEL "repository"="TBD" -LABEL "homepage"="TBD" -LABEL "maintainer"="dewb" - -RUN apt-get update -RUN apt-get install -y build-essential cmake curl gcc g++ git make tar unzip zip libgl1-mesa-dev libglu1-mesa-dev jq g++-mingw-w64-x86-64 - -ADD entrypoint.sh /entrypoint.sh -RUN chmod a+x /entrypoint.sh - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/vcv/README.md b/vcv/README.md index 909c9c97f..a92baf551 100644 --- a/vcv/README.md +++ b/vcv/README.md @@ -4,7 +4,7 @@ Make sure to install all prerequisites as described on the [Setup guide](../docs ### Building VCV Rack Plugin -You must have the Rack-SDK on your computer already. Version 2.2.3 is known to +You must have the Rack-SDK on your computer already. Version 2.4.1 is known to work. Set the environment variable `RACK_DIR` equal to the path to the location of Rack-SDK. For instance, add this to your .bashrc or .zshrc: diff --git a/vcv/docker_build.sh b/vcv/docker_build.sh deleted file mode 100644 index 6295c4001..000000000 --- a/vcv/docker_build.sh +++ /dev/null @@ -1 +0,0 @@ -docker build -t test . diff --git a/vcv/docker_run.sh b/vcv/docker_run.sh deleted file mode 100644 index b7ed624a6..000000000 --- a/vcv/docker_run.sh +++ /dev/null @@ -1,2 +0,0 @@ -docker run -v /Users/dann/4ms/stm32/meta-module:/plugin test - diff --git a/vcv/entrypoint.sh b/vcv/entrypoint.sh deleted file mode 100755 index 257837e62..000000000 --- a/vcv/entrypoint.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh - -set -eu - -export RACK_DIR=$(PWD)/Rack-SDK -#export RACK_USER_DIR=./ -export RACK_SDK_VERSION=1.1.6 - -curl -L https://vcvrack.com/downloads/Rack-SDK-${RACK_SDK_VERSION}.zip -o rack-sdk.zip -unzip -o rack-sdk.zip -rm rack-sdk.zip - -#export CC=x86_64-w64-mingw32-gcc-posix -#export CXX=x86_64-w64-mingw32-g++-posix -export CC=x86_64-w64-mingw32-gcc -export CXX=x86_64-w64-mingw32-g++ -export STRIP=x86_64-w64-mingw32-strip - -cd /plugin -cd vcv -make clean -make dist - diff --git a/vcv/plugin-minimal.json b/vcv/plugin-minimal.json deleted file mode 100644 index 1cedc5e15..000000000 --- a/vcv/plugin-minimal.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "slug": "4msCompany", - "name": "4msCompany", - "version": "2.0.0", - "license": "proprietary", - "brand": "4msCompany", - "author": "", - "authorEmail": "", - "authorUrl": "", - "pluginUrl": "", - "manualUrl": "", - "sourceUrl": "", - "donateUrl": "", - "changelogUrl": "", - "modules": [ - { - "slug": "HubMedium", - "name": "MetaModule", - "description": "MetaModule", - "tags": [] - }, - { - "slug": "ENVVCA", - "name": "ENVVCA", - "description": "ENVVCA", - "tags": [] - }, - { - "slug": "Freeverb", - "name": "Freeverb", - "description": "Freeverb", - "tags": [] - }, - { - "slug": "SMR", - "name": "SMR", - "description": "Spectral Multiband Resonator", - "tags": [] - } - ] -} diff --git a/vcv/plugins-unused.json b/vcv/plugins-unused.json deleted file mode 100644 index ed59c87d8..000000000 --- a/vcv/plugins-unused.json +++ /dev/null @@ -1,373 +0,0 @@ - { - "slug": "VCA", - "name": "VCA", - "description": "VCA", - "tags": [] - }, - { - "slug": "Send", - "name": "Send", - "description": "Send", - "tags": [] - }, - - { - "slug": "Logic", - "name": "Logic", - "description": "Logic", - "tags": [] - }, - { - "slug": "LFO", - "name": "LFO", - "description": "LFO", - "tags": [] - }, - { - "slug": "FadeDelay", - "name": "FadeDelay", - "description": "FadeDelay", - "tags": [] - }, - - { - "slug": "Att", - "name": "Att", - "description": "Att", - "tags": [] - }, - - - - - { - "slug": "dual_opener", - "name": "Dual Opener", - "description": "", - "tags": [] - }, - { - "slug": "dspTemplate", - "name": "dspTemplate", - "description": "", - "tags": [] - }, - { - "slug": "adjTest", - "name": "adjTest", - "description": "", - "tags": [] - }, - - { - "slug": "PANEL_8", - "name": "MetaModuleMini", - "description": "MetaModule 16HP 8 knobs, stereo Audio In, 4-channel audio out, 4 CV inputs, 2 gate in, clock in/out, two RGB buttons", - "tags": [] - }, - { - "slug": "PANEL_MED", - "name": "PanelMedium", - "description": "Panel Medium 26HP", - "tags": [] - }, -{ - "slug": "INFOSC64", - "name": "Infosc-64bit-phase", - "description": "infinite-oscillator-64bit-Phase", - "tags": [] -}, -{ - "slug": "INFOSC01", - "name": "Infosc01", - "description": "infinite-oscillator-01", - "tags": [] -}, -{ - "slug": "DJEMBE", - "name": "Djembe", - "description": "Djembe", - "tags": [] -}, -{ - "slug": "BIPOLARSOURCE", - "name": "Bipolarsource", - "description": "Bipolar Voltage Source", - "tags": [] -}, -{ - "slug": "MULTILFO", - "name": "Multilfo", - "description": "Multi Output LFO", - "tags": [] -}, -{ - "slug": "ENVELOPEFOLLOWER", - "name": "Envelopefollower", - "description": "Envelope Follower", - "tags": [] -}, -{ - "slug": "VOLTAGESOURCE", - "name": "Voltagesource", - "description": "Voltage Source", - "tags": [] -}, -{ - "slug": "NOISE", - "name": "Noise", - "description": "Noise", - "tags": [] -}, -{ - "slug": "SAMPLEPLAYER", - "name": "Sampleplayer", - "description": "Sample Player", - "tags": [] -}, -{ - "slug": "SLEWLIMITER", - "name": "Slewlimiter", - "description": "Slew Limiter", - "tags": [] -}, -{ - "slug": "BANDPASSFILTER", - "name": "Bandpassfilter", - "description": "Bandpass Filter", - "tags": [] -}, -{ - "slug": "HIGHPASSFILTER", - "name": "Highpassfilter", - "description": "Highpass Filter", - "tags": [] -}, -{ - "slug": "REVERB", - "name": "Reverb", - "description": "Reverb", - "tags": [] -}, -{ - "slug": "KARPLUS", - "name": "Karplus", - "description": "Karplus", - "tags": [] -}, -{ - "slug": "GATESEQ16", - "name": "Gateseq16", - "description": "16 Step Gate Sequencer", - "tags": [] -}, -{ - "slug": "GATESEQ8", - "name": "Gateseq8", - "description": "8 Step Gate Sequencer", - "tags": [] -}, -{ - "slug": "OCTAVE", - "name": "Octave", - "description": "Octave", - "tags": [] -}, -{ - "slug": "MINMAX", - "name": "Minmax", - "description": "Minimum/Maximum", - "tags": [] -}, -{ - "slug": "DRUM", - "name": "Drum", - "description": "Drum", - "tags": [] -}, -{ - "slug": "COMPLEXENVELOPE", - "name": "Complexenvelope", - "description": "Complex Envelope", - "tags": [] -}, -{ - "slug": "FMOSC", - "name": "Fmosc", - "description": "FM Oscillator", - "tags": [] -}, -{ - "slug": "STEREOMIXER", - "name": "Stereomixer", - "description": "Stereo Mixer", - "tags": [] -}, -{ - "slug": "PANNER", - "name": "Panner", - "description": "Panner", - "tags": [] -}, -{ - "slug": "GATECONVERTER", - "name": "Gateconverter", - "description": "Gate Converter", - "tags": [] -}, -{ - "slug": "DETUNE", - "name": "Detune", - "description": "Detune", - "tags": [] -}, -{ - "slug": "PITCHSHIFT", - "name": "Pitchshift", - "description": "Pitch Shifter", - "tags": [] -}, -{ - "slug": "SWITCH4TO1", - "name": "Switch4to1", - "description": "4 to 1 Switch", - "tags": [] -}, -{ - "slug": "SWITCH1TO4", - "name": "Switch1to4", - "description": "1 to 4 Switch", - "tags": [] -}, - { - "slug": "EIGHTSTEPPROB", - "name": "Eightstepprob", - "description": "8 Step Probability Sequencer", - "tags": [] - }, - { - "slug": "QUANTIZER", - "name": "Quantizer", - "description": "Quantizer", - "tags": [] - }, - { - "slug": "FOURSTEP", - "name": "Fourstep", - "description": "4 Step Sequencer", - "tags": [] - }, - { - "slug": "EIGHTSTEP", - "name": "Eightstep", - "description": "8 Step Sequencer", - "tags": [] - }, - { - "slug": "BITCRUSH", - "name": "Bitcrush", - "description": "Bit Crusher", - "tags": [] - }, - { - "slug": "PHASER", - "name": "Phaser", - "description": "Phaser", - "tags": [] - }, - { - "slug": "FREQSHIFT", - "name": "Freqshift", - "description": "Frequency Shifter", - "tags": [] - }, - { - "slug": "LOWPASSFILTER", - "name": "Lowpassfilter", - "description": "Low Pass Filter", - "tags": [] - }, - { - "slug": "LOWPASSGATE", - "name": "Lowpassgate", - "description": "Low Pass Gate", - "tags": [] - }, - { - "slug": "CLKMULTIPLIER", - "name": "Clkmultiplier", - "description": "clock multiplier", - "tags": [] - }, - { - "slug": "CLKDIVIDER", - "name": "Clkdivider", - "description": "clock divider", - "tags": [] - }, - { - "slug": "comparator", - "name": "Comparator", - "description": "", - "tags": [] - }, - { - "slug": "send", - "name": "Send", - "description": "", - "tags": [] - }, - { - "slug": "lfo", - "name": "lfo", - "description": "", - "tags": [] - }, - { - "slug": "ad_envelope", - "name": "AD envelope", - "description": "", - "tags": [] - }, - { - "slug": "crossfade", - "name": "crossfade", - "description": "", - "tags": [] - }, - { - "slug": "mixer4", - "name": "mixer4", - "description": "", - "tags": [] - }, - { - "slug": "logic", - "name": "logic", - "description": "", - "tags": [] - }, - { - "slug": "samplehold", - "name": "samplehold", - "description": "", - "tags": [] - }, - { - "slug": "attenuvert", - "name": "attenuvert", - "description": "", - "tags": [] - }, - { - "slug": "fadedelay", - "name": "fadedelay", - "description": "", - "tags": [] - }, - { - "slug": "vca", - "name": "vca", - "description": "", - "tags": [] - } diff --git a/vcv/res/12hptemplate.svg b/vcv/res/12hptemplate.svg deleted file mode 100644 index 371cbc8af..000000000 --- a/vcv/res/12hptemplate.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/vcv/res/16hptemplate.svg b/vcv/res/16hptemplate.svg deleted file mode 100644 index 8f7c5b715..000000000 --- a/vcv/res/16hptemplate.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/vcv/res/4hptemplate.svg b/vcv/res/4hptemplate.svg deleted file mode 100644 index d93520f0a..000000000 --- a/vcv/res/4hptemplate.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/vcv/res/8hptemplate.svg b/vcv/res/8hptemplate.svg deleted file mode 100644 index 38b74b8be..000000000 --- a/vcv/res/8hptemplate.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/vcv/res/meta-module-medium-p8.svg b/vcv/res/meta-module-medium-p8.svg deleted file mode 100644 index 330a3adfe..000000000 --- a/vcv/res/meta-module-medium-p8.svg +++ /dev/null @@ -1,381 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vcv/res/meta-module-medium.svg b/vcv/res/meta-module-medium.svg deleted file mode 100644 index d4a475ba0..000000000 --- a/vcv/res/meta-module-medium.svg +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vcv/res/meta-module-mini.svg b/vcv/res/meta-module-mini.svg deleted file mode 100644 index 2e8d5107f..000000000 --- a/vcv/res/meta-module-mini.svg +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -