diff --git a/.gitignore b/.gitignore index 521fbc2..a1d2079 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,13 @@ help/*.h help/*.json templates/*.cpp templates/*.h -templates/*.json \ No newline at end of file +templates/*.json +.DS_Store +*.DS_Store +**/.DS_Store + +# Cosmolab hardware v1 (prototypes only, not for distribution) +source/cosmolab_hw_v1.json +source/COSMOLAB_BOARDS_README.md +COSMOLAB_README.md +COSMOLAB_BUTTON_POLARITY_FIX.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..448d776 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,68 @@ +# Changelog - Cosmolab Oopsy Support + +All notable changes to Cosmolab board support in oopsy. + +## [1.0.0] - 2025-12-08 + +### Added +- **Full Cosmolab hardware support** + - 32 LED-backlit buttons via CD4021 shift registers + - 32 button LEDs via PCA9685 I2C drivers + - 8 potentiometers via CD4051 multiplexer (already supported in oopsy) + - 16 knob LEDs (2 per knob: illumination + position indicator) via PCA9685 + - 4 CV inputs, 2 CV outputs (Eurorack compatible) + - 1 Trigger input, 1 Trigger output (Eurorack compatible) + - MIDI I/O, OLED display (64x32) + +- **Updated to libDaisy v8.0.0 (from v5.x)** + - Improved MIDI handling with DMA listen mode + - Better hardware compatibility + - Bug fixes and performance improvements + +- **Fixed PCA9685 LED driver support** + - Added `hardware.LoopProcess()` call in main loop (line 958 of genlib_daisy.h) + - Enables proper I2C LED driver updates + - **Benefits all boards using PCA9685 or other I2C peripherals** + +- **Added CD4021 shift register support** + - New `CD4021` parent component for shift register chains + - New `CD4021Switch` for normal polarity (pull-down resistors) + - New `CD4021SwitchInverted` for inverted polarity (pull-up resistors) + - Includes debouncing and edge detection + - **Supports both common hardware configurations** + +### Changed +- Updated `genlib_daisy.h` to support boards with I2C peripherals +- Updated MIDI UART handling for libDaisy v8.0.0 compatibility + +### Technical Notes + +**LoopProcess() Fix:** +The addition of `hardware.LoopProcess()` was essential for boards with peripherals requiring periodic servicing. This is not oopsy-specific—it's a general requirement for boards using: +- I2C LED drivers (PCA9685, etc.) +- I2C sensors or displays +- Any hardware requiring non-audio-rate updates + +**Component Polarity Support:** +Following libDaisy's `Switch::POLARITY_NORMAL/INVERTED` pattern, we added both normal and inverted variants for CD4021 shift registers to support different pull resistor configurations without forcing hardware redesigns. + +--- + +## Testing + +Tested on actual Cosmolab hardware: +- ✅ All 32 buttons +- ✅ All 48 LEDs +- ✅ All 8 potentiometers +- ✅ CV I/O +- ✅ MIDI I/O +- ✅ Display +- ✅ Audio I/O + +--- + +## See Also + +- `COSMOLAB_README.md` - Detailed technical documentation +- `source/cosmolab.json` - Board definition +- `source/COSMOLAB_BOARDS_README.md` - Board selection guide diff --git a/COSMOLAB_README.md b/COSMOLAB_README.md new file mode 100644 index 0000000..375a9d0 --- /dev/null +++ b/COSMOLAB_README.md @@ -0,0 +1,220 @@ +# Cosmolab Support for Oopsy + +This document describes the work done to bring Cosmolab board support to oopsy, including the fixes and enhancements needed to make all hardware components work correctly. + +## Overview + +Cosmolab is a Eurorack/desktop music production board based on the Daisy Seed platform. It features: +- 32 LED-backlit buttons (via PCA9685 I2C LED drivers) +- 8 potentiometers with dual LED feedback (via CD4051 multiplexer) + - Each knob has 2 LEDs: one underneath for illumination, one above at 12 o'clock position +- 4 CV inputs, 2 CV outputs (Eurorack compatible) +- 1 Trigger input, 1 Trigger output (Eurorack compatible) +- OLED display (64x32) +- MIDI I/O +- Audio I/O (2 channels) + +## Version History + +### v1.0.0 (2025-12-08) +- ✅ Full Cosmolab hardware support +- ✅ Updated to libDaisy v8.0.0 (from v5.x) +- ✅ Fixed PCA9685 LED driver support +- ✅ Added CD4021SwitchInverted component for hardware flexibility + +--- + +## Technical Implementation + +### 1. LibDaisy v8.0.0 Update + +**Challenge:** Oopsy was using an older version of libDaisy (v5.x), which had API differences and missing features needed for Cosmolab. + +**Solution:** Updated oopsy to use libDaisy v8.0.0, including: +- Updated MIDI UART handling (now uses DMA listen mode) +- Updated hardware initialization patterns +- Compatibility with newer Daisy Seed bootrom + +**Files Modified:** +- `source/genlib_daisy.h` - Updated MIDI callbacks and initialization +- Submodule `source/libdaisy` - Updated to v8.0.0 + +--- + +### 2. PCA9685 LED Driver Support + +**Problem:** The PCA9685 I2C LED drivers (which control all 48 LEDs on Cosmolab) were not being updated in the main loop, causing LEDs to remain off or not respond to software commands. + +**Root Cause:** Oopsy's main loop was missing a call to `hardware.LoopProcess()`, which is required for hardware components that need periodic servicing (like I2C-based LED drivers). + +**Solution:** Added `hardware.LoopProcess()` call in the main UI update loop. + +**Technical Details:** +```cpp +// In genlib_daisy.h, line 958: +// Call hardware loop processing (LED drivers, etc.) +hardware.LoopProcess(); +``` + +This call triggers: +- PCA9685 buffer swap and I2C transmission +- Any other hardware-specific loop processing needed by the board class + +**Why This Fix Helps Others:** +- **Any board with PCA9685 LED drivers** will need this fix +- **Any board with I2C peripherals** that need periodic updates +- The fix is board-agnostic and doesn't break existing boards + +**Files Modified:** +- `source/genlib_daisy.h` (line 958) + +--- + +### 3. CD4021 Shift Register Button Support + +**Problem:** CD4021 shift registers (used to read 32 buttons) needed proper debouncing and edge detection. + +**Solution:** Added comprehensive CD4021 support with two polarity options: +- `CD4021Switch` - For hardware with pull-down resistors (standard) +- `CD4021SwitchInverted` - For hardware with pull-up resistors + +**Why Two Components?** +Different hardware designs use different pull resistor configurations: +- Pull-down: Button press = HIGH, released = LOW (less common) +- Pull-up: Button press = LOW, released = HIGH (more common, less wiring) + +This mirrors libDaisy's own `Switch::POLARITY_NORMAL` and `Switch::POLARITY_INVERTED` pattern. + +**Technical Details:** + +Normal polarity (`CD4021Switch`): +```cpp +state == 0xFF → button released (buffer full of 1s) +state != 0xFF → button pressed +``` + +Inverted polarity (`CD4021SwitchInverted`): +```cpp +state == 0xFF → button pressed (hardware inverted, reads as 1s) +state != 0xFF → button released +``` + +**Why This Fix Helps Others:** +- CD4021 is a standard shift register for button matrices +- Both pull-up and pull-down configurations are valid design choices +- Having both options avoids forcing specific hardware decisions +- Useful for any project reading 8+ buttons via shift registers + +**Files Modified:** +- `source/component_defs.json` - Added CD4021, CD4021Switch, and CD4021SwitchInverted + +--- + +### 4. Board Definition Files + +Created comprehensive board definition for Cosmolab hardware: + +**`source/cosmolab.json`** - Main board definition with: +- PCA9685 LED driver configuration (3 drivers, 48 LEDs total) +- CD4021 shift register configuration (4 chips, 32 buttons total) +- CD4051 multiplexer configuration (8 potentiometers) +- CV inputs/outputs, switches, MIDI, display + +**Component Hierarchy:** +``` +cosmolab.json +├── Parents (shared peripherals): +│ ├── i2c (for LED drivers and display) +│ ├── led_driver (PCA9685 x3) +│ ├── pad_shift (CD4021 x4) +│ └── pot_mux (CD4051) +│ +└── Components: + ├── 32 buttons (pada1-padd8) via pad_shift + ├── 32 LEDs for buttons (led_key_a*-d*) via led_driver + ├── 8 knobs (knob1-8) via pot_mux + ├── 16 LEDs for knobs (2 per knob: led_knob_*, led_knob_under_*) via led_driver + ├── 4 CV inputs + ├── 2 CV outputs + ├── 1 Trigger input (Eurorack) + ├── 1 Trigger output (Eurorack) + ├── 2 switches + └── OLED display (64x32) +``` + +--- + +## Key Learnings & Best Practices + +### 1. Hardware Loop Processing +**Always call `hardware.LoopProcess()` in the main loop** if your board has: +- I2C peripherals (LED drivers, displays, sensors) +- SPI peripherals that need periodic updates +- Any component requiring non-audio-rate servicing + +### 2. Component Design Patterns +When adding new component types to oopsy: +- Follow lib Daisy's naming and patterns (e.g., POLARITY_NORMAL/INVERTED) +- Provide both hardware configuration options when reasonable +- Document hardware requirements clearly +- Test with actual hardware, not just simulation + +### 3. Multiplexer Best Practices +- CD4051 (analog mux): Great for potentiometers, saves ADC inputs +- CD4021 (shift register): Perfect for button matrices, saves GPIO +- Document select pin mapping clearly +- Consider debouncing for shift register inputs + +### 4. Pull Resistor Considerations +- Pull-up: More common, less wiring, better noise immunity +- Pull-down: Less common, explicit about "pressed" state +- Support both in software for maximum hardware flexibility + +--- + +## Files Modified Summary + +| File | Purpose | Lines Changed | +|------|---------|---------------| +| `source/genlib_daisy.h` | Added LoopProcess() call | ~1 | +| `source/component_defs.json` | Added CD4021, CD4051, variants | ~100 | +| `source/cosmolab.json` | Board definition | New file | +| `source/libdaisy/` (submodule) | Updated to v8.0.0 | Submodule | + +--- + +## Testing + +All features tested on actual Cosmolab hardware: +- ✅ All 32 buttons working with correct LED feedback +- ✅ All 8 potentiometers reading correctly +- ✅ CV inputs/outputs functioning +- ✅ OLED display working +- ✅ MIDI I/O operational +- ✅ Audio I/O clean and stable + +--- + +## Future Enhancements + +- [ ] Consider contributing fixes upstream to official oopsy repo +- [ ] Add polarity as a parameter instead of separate components +- [ ] Document more complex board definition patterns +- [ ] Add examples using Cosmolab-specific features + +--- + +## Credits + +- **Faselunare** - Cosmolab hardware design +- **Electro-Smith** - Daisy platform and libDaisy +- **oopsy** - Gen~ to Daisy bridge + +## References + +- [Oopsy GitHub](https://github.com/electro-smith/oopsy) +- [libDaisy Documentation](https://electro-smith.github.io/libDaisy/) +- [Daisy Wiki](https://github.com/electro-smith/DaisyWiki/wiki) +- [PCA9685 Datasheet](https://www.nxp.com/docs/en/data-sheet/PCA9685.pdf) +- [CD4051 Datasheet](https://www.ti.com/product/CD4051B) +- [CD4021 Datasheet](https://www.ti.com/product/CD4021B) diff --git a/README.md b/README.md index 12997bc..fdbd4c1 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,102 @@ For details of the licensing terms of code exported from gen~ see https://suppor - Cycling '74: https://cycling74.com - MW: https://www.modwiggler.com/forum/viewtopic.php?f=16&t=242322 +## Changelog + +### libdaisy v8.0.0 Update (December 2025) + +This update brings Oopsy compatibility with libdaisy v8.0.0 and adds support for the Cosmolab board by Faselunare. The update was performed by Francesco Mulassano of Faselunare. + +**⚠️ Important:** After updating to this version, you must run `./install.sh` to rebuild libdaisy with the new APIs. This is required for the changes to take effect. + +#### libdaisy v8.0.0 Compatibility Updates + +**Breaking Changes Addressed:** + +1. **GPIO/Pin Migration** + - **Why:** libdaisy v8.0.0 completed the GPIO/Pin migration. The `dsy_gpio` and `dsy_gpio_pin` structs are deprecated and no longer accepted by libdaisy objects. + - **Changes:** + - Updated `component_defs.json` to use `GPIO` class with `Config` struct instead of `dsy_gpio` + - Changed `GateOut` initialization from `dsy_gpio_init(&gateout)` to `GPIO::Init(Config)` + - Updated `GateIn` to use `Pin` directly instead of `dsy_gpio_pin*` pointer + - Changed `Switch::PULL_UP` to `GPIO::Pull::PULLUP` + - Updated Pin syntax from `{DSY_GPIOB, 7}` to `Pin(PORTB, 7)` + +2. **UartHandler API Migration** + - **Why:** The old UART methods (`StartRx`, `Readable`, `PopRx`) were deprecated in libdaisy v5.1.0 and removed in v8.0.0. The new API uses DMA listen mode for better performance and reliability. + - **Changes:** + - Replaced `uart.StartRx()` with `uart.DmaListenStart()` using circular buffer callback + - Replaced `uart.Readable()` and `uart.PopRx()` with DMA buffer reading + - Added `MidiRxCallback` function to handle incoming MIDI data via DMA interrupts + - Implemented circular buffer system for MIDI reception + +3. **Hardware Structure Updates** + - **Why:** The internal structure changed from `hardware.seed` to `hardware.som` to support different System-on-Module types (seed, patch_sm, petal_125b_sm). + - **Changes:** + - Updated `genlib_daisy.h` to use `som` pointer instead of `sub_board` + - Changed `hardware.seed.AudioSampleRate()` calls to `som->()` calls + - Added conditional compilation for different SOM types + - Updated `oopsy.js` to generate code using `som->AudioSampleRate()` instead of `hardware.seed.AudioSampleRate()` + +4. **System API Updates** + - **Why:** `System::ResetToBootloader()` now requires an explicit `BootloaderMode` parameter. + - **Changes:** + - Updated calls to include `BootloaderMode::STM` parameter + +5. **Component Definitions** + - **Why:** Missing component definition files prevented support for newer components like `CD4021Switch`. + - **Changes:** + - Added `component_defs.json`, `component_defs_patchsm.json`, `component_defs_petalsm.json` + - Added `json2daisy.js` and `daisy_glue.js` utility files + - Updated `oopsy.js` to use `json2daisy.generate_header()` for component generation + - Fixed Makefile generation to include `hardware.includes` and `APP_TYPE` support + +6. **Makefile Improvements** + - **Why:** The Makefile was incorrectly including petal_sm source files for all boards. + - **Changes:** + - Made petal_sm source inclusion conditional based on `hardware.som` type + - Only includes `daisy_petal_125b_sm.cpp` when `som == 'petal_125b_sm'` + +#### Cosmolab Board Support + +Added full support for the Cosmolab board by Faselunare: +- Board definition in `source/cosmolab.json` +- Max/MSP template `templates/oopsy_cosmolab.maxpat` +- Object mapping in `init/oopsy-objectmappings.txt` +- Integration in `patchers/oopsy.maxpat` menu + +The Cosmolab board features: +- 32 pads (4x8 matrix) using CD4021 shift registers +- 8 multiplexed knobs using CD4051 +- 48 RGB LEDs via PCA9685 drivers +- OLED display support +- MIDI input/output support +- 4 CV inputs, 2 CV outputs +- Gate input and output + +#### Migration Notes + +If you're upgrading from an earlier version of Oopsy: + +1. **Run `./install.sh`** - This is critical! It will rebuild libdaisy with the new APIs. +2. **Update your custom board definitions** - If you have custom JSON board definitions, you may need to update them to use the new GPIO/Pin syntax. +3. **Check your gen~ code** - If you're using any deprecated APIs directly in your gen~ code, update them according to the libdaisy v8.0.0 migration guide. + +#### Technical Details + +All changes follow the official libdaisy v8.0.0 migration guide: +- [libdaisy v8.0.0 Release Notes](https://github.com/electro-smith/libDaisy/releases/tag/v8.0.0) +- GPIO/Pin migration examples from changelog +- UartHandler DMA API documentation + +**Total commits:** 14 commits implementing the migration + +#### Disclaimer + +This software is provided **AS IS** without warranty of any kind, express or implied. The update was performed by Francesco Mulassano of Faselunare for the purpose of adding Cosmolab board support and updating to libdaisy v8.0.0. Use at your own risk. + ----- Oopsy was authored by [Graham](https://github.com/grrrwaaa) [Wakefield](http://alicelab.world) in 2020-2021. + +**libdaisy v8.0.0 Update:** December 2024 by Francesco Mulassano (Faselunare) diff --git a/examples/knobled.maxpat b/examples/knobled.maxpat new file mode 100644 index 0000000..5e34c41 --- /dev/null +++ b/examples/knobled.maxpat @@ -0,0 +1,59 @@ +{ + "patcher": { + "fileversion": 1, + "appversion": { + "major": 9, + "minor": 1, + "revision": 1, + "architecture": "x64", + "modernui": 1 + }, + "classnamespace": "box", + "rect": [ 481.0, 261.0, 640.0, 480.0 ], + "boxes": [ + { + "box": { + "id": "obj-4", + "maxclass": "newobj", + "numinlets": 1, + "numoutlets": 82, + "outlettype": [ "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal" ], + "patching_rect": [ 47.0, 302.0, 869.5, 22.0 ], + "saved_object_attributes": { + "exportfolder": "Macintosh HD:/Users/francesco/Dev/2025/Git_Faselunare/Faselunare/FLCosmolab/oopsy/examples/", + "exportname": "_2fUsers_2ffrancesco_2fDev_2f2025_2fGit_Faselunare_2fFaselunare_2fFLCosmolab_2fexamples_2fgen_2fcosmo_test_2egendsp" + }, + "text": "gen~ @gen /Users/francesco/Dev/2025/Git_Faselunare/Faselunare/FLCosmolab/examples/gen/cosmo_test.gendsp", + "varname": "_2fUsers_2ffrancesco_2fDev_2f2025_2fGit_Faselunare_2fFaselunare_2fFLCosmolab_2fexamples_2fgen_2fcosmo_test_2egendsp" + } + }, + { + "box": { + "bgmode": 0, + "border": 0, + "clickthrough": 0, + "enablehscroll": 0, + "enablevscroll": 0, + "id": "obj-9", + "lockeddragscroll": 0, + "lockedsize": 0, + "maxclass": "bpatcher", + "name": "oopsy.maxpat", + "numinlets": 1, + "numoutlets": 0, + "offset": [ 0.0, 0.0 ], + "patching_rect": [ 108.5, 111.0, 128.0, 128.0 ], + "viewvisibility": 1 + } + } + ], + "lines": [], + "parameters": { + "obj-9::obj-32": [ "live.text[2]", "FILTER", 0 ], + "obj-9::obj-33": [ "live.text[1]", "FILTER", 0 ], + "obj-9::obj-34": [ "live.text[3]", "FILTER", 0 ], + "inherited_shortname": 1 + }, + "autosave": 0 + } +} \ No newline at end of file diff --git a/init/oopsy-objectmappings.txt b/init/oopsy-objectmappings.txt index be811ad..1f3fcc4 100644 --- a/init/oopsy-objectmappings.txt +++ b/init/oopsy-objectmappings.txt @@ -12,3 +12,5 @@ max definesubstitution oopsy.bluemchen bpatcher @name oopsy.maxpat @args bluemch max objectfile oopsy.bluemchen oopsy.bluemchen; max definesubstitution oopsy.nehcmeulb bpatcher @name oopsy.maxpat @args nehcmeulb; max objectfile oopsy.nehcmeulb oopsy.nehcmeulb; +max definesubstitution oopsy.cosmolab bpatcher @name oopsy.maxpat @args cosmolab @patching_rect 256 256 171 171; +max objectfile oopsy.cosmolab oopsy.cosmolab; diff --git a/patchers/oopsy.maxpat b/patchers/oopsy.maxpat index aed8cf3..d5e82ea 100644 --- a/patchers/oopsy.maxpat +++ b/patchers/oopsy.maxpat @@ -353,7 +353,7 @@ "box" : { "fontsize" : 9.0, "id" : "obj-76", - "items" : [ "patch", ",", "patch_sm", ",", "field", ",", "petal", ",", "pod", ",", "versio", ",", "bluemchen", ",", "nehcmeulb" ], + "items" : [ "patch", ",", "patch_sm", ",", "field", ",", "petal", ",", "pod", ",", "versio", ",", "bluemchen", ",", "nehcmeulb", ",", "cosmolab" ], "maxclass" : "umenu", "numinlets" : 1, "numoutlets" : 3, diff --git a/source/COSMOLAB_BOARDS_README.md b/source/COSMOLAB_BOARDS_README.md new file mode 100644 index 0000000..2ba963e --- /dev/null +++ b/source/COSMOLAB_BOARDS_README.md @@ -0,0 +1,96 @@ +# Cosmolab Board Definitions + +This directory contains two board definition files for Cosmolab: + +## 📋 Files + +### `cosmolab.json` ✅ **Use this for:** +- ✅ **Plugdata/Pure Data simulation** +- ✅ **Hardware v2+** (with corrected button polarity) +- ✅ **Development and testing** + +**Button behavior:** Normal polarity (button press = LOW, released = HIGH) + +--- + +### `cosmolab_hw_v1.json` ⚠️ **Use this ONLY for:** +- ⚠️ **Current hardware v1** (with inverted button polarity issue) + +**Button behavior:** Inverted polarity to compensate for hardware pull-up resistors + +--- + +## 🚀 How to Use + +### For Plugdata/Simulation (ALWAYS use normal) +Your Max patches will automatically work with `cosmolab` board when simulating in Plugdata. + +### For Hardware v1 (Current) +When compiling firmware for the actual hardware v1: + +```bash +# In your Max patch, or via command line: +oopsy your_patch.maxpat --board cosmolab_hw_v1 + +# Then build and flash: +cd build +make clean && make +make program-dfu +``` + +### For Hardware v2+ (Future) +When you get hardware v2 with corrected polarity: + +```bash +# Simply use the standard board: +oopsy your_patch.maxpat --board cosmolab + +cd build +make clean && make +make program-dfu +``` + +--- + +## 🔧 Technical Details + +### The Hardware Issue +Hardware v1 has pull-up resistors on the CD4021 shift register button inputs: +- Button **not pressed** → pin reads HIGH (1) +- Button **pressed** → pin reads LOW (0) + +This is inverted from the expected behavior. + +### The Software Fix +- `cosmolab.json`: Uses `CD4021Switch` component (normal polarity) +- `cosmolab_hw_v1.json`: Uses `CD4021SwitchInverted` component (compensates for inverted polarity) + +### Why Two Files? +Having separate files allows: +1. **Plugdata compatibility** - simulation works correctly with normal polarity +2. **Hardware v1 support** - actual hardware works with inverted polarity +3. **Future-proof** - clean migration path to v2+ hardware +4. **No patch modifications** - your Max patches don't need any changes! + +--- + +## 📚 Related Documentation +- `COSMOLAB_BUTTON_POLARITY_FIX.md` - Technical explanation (English) +- `COSMOLAB_FIX_PULSANTI_IT.md` - Quick guide (Italian) +- `CHANGELOG_CD4021SwitchInverted.md` - Change log + +--- + +## ❓ FAQ + +**Q: Which file should I use for testing in Plugdata?** +A: Always use `cosmolab` (not `cosmolab_hw_v1`) + +**Q: My buttons don't work on hardware!** +A: Make sure you're compiling with `--board cosmolab_hw_v1` for hardware v1 + +**Q: Will I need to change my Max patches?** +A: No! The board definition handles everything. Your patches work with both. + +**Q: What happens when hardware v2 arrives?** +A: Just switch to compiling with `--board cosmolab` instead of `--board cosmolab_hw_v1` diff --git a/source/component_defs.json b/source/component_defs.json new file mode 100644 index 0000000..8743ba9 --- /dev/null +++ b/source/component_defs.json @@ -0,0 +1,1490 @@ +{ + "Switch": { + "map_init": "{name}.Init(som.GetPin({pin}), som.AudioCallbackRate(), {type}, {polarity}, {pull});", + "typename": "daisy::Switch", + "direction": "in", + "pin": "a", + "type": "daisy::Switch::TYPE_MOMENTARY", + "polarity": "daisy::Switch::POLARITY_INVERTED", + "pull": "daisy::GPIO::Pull::PULLUP", + "process": "{name}.Debounce();", + "updaterate": "{name}.SetUpdateRate(som.AudioCallbackRate());", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.{name}.Pressed()?1.f:0.f)", + "range": [ + 0, + 1 + ], + "bool": false + }, + { + "name": "{name}_press", + "get": "({class_name}.{name}.Pressed()?1.f:0.f)", + "range": [ + 0, + 1 + ], + "bool": false + }, + { + "name": "{name}_seconds", + "get": "({class_name}.{name}.TimeHeldMs()*0.001f)", + "bool": false + } + ] + }, + "Switch3": { + "map_init": "{name}.Init(som.GetPin({pin_a}), som.GetPin({pin_b}));", + "typename": "daisy::Switch3", + "direction": "in", + "pin": "a,b", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.{name}.Read()*0.5f+0.5f)", + "range": [ + 0, + 2 + ], + "bool": false + } + ] + }, + "Encoder": { + "map_init": "{name}.Init(som.GetPin({pin_a}), som.GetPin({pin_b}), som.GetPin({pin_click}), som.AudioCallbackRate());", + "typename": "daisy::Encoder", + "direction": "in", + "pin": "a,b,click", + "process": "{name}.Debounce();", + "updaterate": "{name}.SetUpdateRate(som.AudioCallbackRate());", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.{name}.Increment())", + "range": [ + -1, + 1 + ], + "bool": false + }, + { + "name": "{name}_press", + "get": "({class_name}.{name}.Pressed()?1.f:0.f)", + "range": [ + 0, + 1 + ], + "bool": false + }, + { + "name": "{name}_rise", + "get": "({class_name}.{name}.RisingEdge()?1.f:0.f)", + "range": [ + 0, + 1 + ], + "bool": true + }, + { + "name": "{name}_fall", + "get": "({class_name}.{name}.FallingEdge()?1.f:0.f)", + "range": [ + 0, + 1 + ], + "bool": true + }, + { + "name": "{name}_seconds", + "get": "({class_name}.{name}.TimeHeldMs()*0.001f)", + "bool": false + } + ] + }, + "GateIn": { + "map_init": "{name}.Init(som.GetPin({pin}), {invert});", + "typename": "daisy::GateIn", + "direction": "in", + "pin": "a", + "invert": "true", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.{name}.State()?1.f:0.f)", + "range": [ + 0, + 1 + ], + "bool": false + }, + { + "name": "{name}_trig", + "get": "({class_name}.{name}.Trig()?1.f:0.f)", + "range": [ + 0, + 1 + ], + "bool": true + } + ] + }, + "AnalogControl": { + "init_single": "cfg[{i}].InitSingle(som.GetPin({pin}));", + "map_init": "{name}.Init(som.adc.GetPtr({i}), som.AudioCallbackRate(), {flip}, {invert});", + "typename": "daisy::AnalogControl", + "direction": "in", + "pin": "a", + "flip": "false", + "invert": "false", + "slew": "1.0/som.AudioCallbackRate()", + "process": "{name}.Process();", + "updaterate": "{name}.SetSampleRate(som.AudioCallbackRate());", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.{name}.Value())", + "range": [ + 0, + 1 + ], + "bool": false + } + ] + }, + "Led": { + "map_init": "{name}.Init(som.GetPin({pin}), {invert});\n {name}.Set(0.0f);", + "typename": "daisy::Led", + "direction": "out", + "pin": "a", + "invert": "true", + "postprocess": "{name}.Update();", + "mapping": [ + { + "name": "{name}", + "set": "{class_name}.{name}.Set({value});" + } + ] + }, + "RgbLed": { + "map_init": "{name}.Init(som.GetPin({pin_r}), som.GetPin({pin_g}), som.GetPin({pin_b}), {invert});\n {name}.Set(0.0f, 0.0f, 0.0f);", + "typename": "daisy::RgbLed", + "direction": "out", + "pin": "r,g,b", + "invert": "true", + "postprocess": "{name}.Update();", + "mapping": [ + { + "name": "{name}_red", + "set": "{class_name}.{name}.SetRed({value});" + }, + { + "name": "{name}_green", + "set": "{class_name}.{name}.SetGreen({value});" + }, + { + "name": "{name}_blue", + "set": "{class_name}.{name}.SetBlue({value});" + }, + { + "name": "{name}", + "set": "{class_name}.{name}.Set(clamp(-{value}, 0.f, 1.f), 0.f, clamp({value}, 0.f, 1.f));" + }, + { + "name": "{name}_white", + "set": "{class_name}.{name}.Set({value},{value},{value});" + } + ] + }, + "GateOut": { + "map_init": "daisy::GPIO::Config {name}_cfg;\n {name}_cfg.pin = som.GetPin({pin});\n {name}_cfg.mode = daisy::GPIO::Mode::OUTPUT;\n {name}_cfg.pull = daisy::GPIO::Pull::NOPULL;\n {name}.Init({name}_cfg);", + "typename": "daisy::GPIO", + "direction": "out", + "pin": "a", + "mapping": [ + { + "name": "{name}", + "set": "{class_name}.{name}.Write({value} > 0.f ? true : false);" + } + ] + }, + "CVOuts": { + "map_init": "{name}.bitdepth = {bitdepth};\n {name}.buff_state = {buff_state};\n {name}.mode = {mode};\n {name}.chn = {channel};\n som.dac.Init({name});\n som.dac.WriteValue({channel}, 0);", + "typename": "daisy::DacHandle::Config", + "direction": "out", + "pin": "", + "bitdepth": "daisy::DacHandle::BitDepth::BITS_12", + "buff_state": "daisy::DacHandle::BufferState::ENABLED", + "mode": "daisy::DacHandle::Mode::POLLING", + "channel": "daisy::DacHandle::Channel::BOTH", + "mapping": [ + { + "name": "{name}1", + "set": "{class_name}.som.dac.WriteValue(daisy::DacHandle::Channel::ONE, {value} * 4095);", + "where": "main" + }, + { + "name": "{name}2", + "set": "{class_name}.som.dac.WriteValue(daisy::DacHandle::Channel::TWO, {value} * 4095);", + "where": "main" + } + ] + }, + "i2c": { + "map_init": "{name}.Init({{{peripheral}, {{som.GetPin({pin_scl}), som.GetPin({pin_sda})}}, {speed}, {mode}}});", + "typename": "daisy::I2CHandle", + "pin": "scl,sda", + "peripheral": "daisy::I2CHandle::Config::Peripheral::I2C_1", + "speed": "daisy::I2CHandle::Config::Speed::I2C_1MHZ", + "mode": "daisy::I2CHandle::Config::Mode::I2C_MASTER", + "mapping": [] + }, + "PCA9685": { + "map_init": "{name}.Init({parent}, {address}, {name}_dma_buffer_a, {name}_dma_buffer_b);", + "typename": "daisy::LedDriverPca9685<{driver_count}, true>", + "non_class_decl": "daisy::LedDriverPca9685<{driver_count}, true>::DmaBuffer DMA_BUFFER_MEM_SECTION {name}_dma_buffer_a, {name}_dma_buffer_b;", + "driver_count": 1, + "address": "{0x00}", + "parent": "", + "pin": "", + "loopprocess": "{name}.SwapBuffersAndTransmit();", + "mapping": [] + }, + "PCA9685Led": { + "map_init": "", + "pin": "", + "typename": "", + "parent": "", + "direction": "out", + "index": 0, + "mapping": [ + { + "name": "{name}", + "set": "{class_name}.{parent}.SetLed({index}, {value});" + } + ] + }, + "PCA9685RgbLed": { + "map_init": "", + "typename": "", + "direction": "out", + "parent": "", + "pin": "", + "index": { + "red": 0, + "green": 1, + "blue": 2 + }, + "mapping": [ + { + "name": "{name}_red", + "set": "{class_name}.{parent}.SetLed({index_red}, {value});" + }, + { + "name": "{name}_green", + "set": "{class_name}.{parent}.SetLed({index_green}, {value});" + }, + { + "name": "{name}_blue", + "set": "{class_name}.{parent}.SetLed({index_blue}, {value});" + }, + { + "name": "{name}", + "set": "{class_name}.{parent}.SetLed({index_red}, {value});\n {class_name}.{parent}.SetLed({index_green}, {value});\n {class_name}.{parent}.SetLed({index_blue}, {value});" + }, + { + "name": "{name}_white", + "set": "{class_name}.{parent}.SetLed({index_red}, {value});\n {class_name}.{parent}.SetLed({index_green}, {value});\n {class_name}.{parent}.SetLed({index_blue}, {value});" + } + ] + }, + "CD4021": { + "map_init": "{name}.Init({{ som.GetPin({pin_clk}), som.GetPin({pin_cs}), {{ som.GetPin({pin_data}) }} }});", + "typename": "daisy::ShiftRegister4021<{driver_count}>", + "non_class_decl": "uint8_t {name}_debounced[8*{driver_count}];", + "driver_count": 1, + "pin": "clk,cs,data", + "postprocess": "{name}.Update();", + "mapping": [] + }, + "CD4021Switch": { + "map_init": "", + "typename": "", + "direction": "in", + "parent": "", + "index": 0, + "postprocess": "{parent}_debounced[{index}] = {parent}.State({index}) | ({parent}_debounced[{index}] << 1);", + "mapping": [ + { + "name": "{name}", + "get": "(json2daisy::{parent}_debounced[{index}] != 0xFF)", + "bool": false + }, + { + "name": "{name}_rise", + "get": "(json2daisy::{parent}_debounced[{index}] == 0xFE)", + "bool": true + }, + { + "name": "{name}_fall", + "get": "(json2daisy::{parent}_debounced[{index}] == 0x7F)", + "bool": true + } + ] + }, + "CD4021SwitchInverted": { + "map_init": "", + "typename": "", + "direction": "in", + "parent": "", + "index": 0, + "postprocess": "{parent}_debounced[{index}] = {parent}.State({index}) | ({parent}_debounced[{index}] << 1);", + "mapping": [ + { + "name": "{name}", + "get": "(json2daisy::{parent}_debounced[{index}] == 0xFF)", + "bool": false + }, + { + "name": "{name}_rise", + "get": "(json2daisy::{parent}_debounced[{index}] == 0x7F)", + "bool": true + }, + { + "name": "{name}_fall", + "get": "(json2daisy::{parent}_debounced[{index}] == 0xFE)", + "bool": true + } + ] + }, + "CD4051": { + "init_single": "size_t {name}_index = {i};\n cfg[{name}_index].InitMux(som.GetPin({pin_adc}), {mux_count}, som.GetPin({pin_sel0}), som.GetPin({pin_sel1}), som.GetPin({pin_sel2}));", + "typename": "", + "mux_count": 1, + "pin": "adc,sel0,sel1,sel2", + "mapping": [] + }, + "CD4051AnalogControl": { + "map_init": "{name}.Init(som.adc.GetMuxPtr({parent}_index, {index}), som.AudioCallbackRate(), {flip}, {invert});", + "parent": "", + "index": 0, + "typename": "daisy::AnalogControl", + "direction": "in", + "flip": "false", + "invert": "false", + "slew": "1.0/som.AudioCallbackRate()", + "process": "{name}.Process();", + "updaterate": "{name}.SetSampleRate(som.AudioCallbackRate());", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.{name}.Value())", + "range": [ + 0, + 1 + ], + "bool": false + } + ] + }, + "AnalogControlBipolar": { + "init_single": "cfg[{i}].InitSingle(som.GetPin({pin}));", + "map_init": "{name}.InitBipolarCv(som.adc.GetPtr({i}), som.AudioCallbackRate());", + "typename": "daisy::AnalogControl", + "direction": "in", + "pin": "a", + "slew": "1.0/som.AudioCallbackRate()", + "process": "{name}.Process();", + "updaterate": "{name}.SetSampleRate(som.AudioCallbackRate());", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.{name}.Value())", + "range": [ + -1, + 1 + ], + "bool": false + } + ] + }, + "MotorShield": { + "map_init": "daisy::Pca9685::Config {name}_pca_config;\n {name}_pca_config.address = {address};\n {name}_pca_config.periph = {periph};\n {name}_pca_config.speed = {speed};\n {name}_pca_config.scl = som.GetPin({pin_scl});\n {name}_pca_config.sda = som.GetPin({pin_sda});\n daisy::Adafruit_MotorShield::Config {name}_config;\n {name}_config.pca9685_config = {name}_pca_config;\n {name}_config.frequency = {frequency};\n {name}.Init({name}_config);", + "typename": "daisy::Adafruit_MotorShield", + "direction": "out", + "pin": "scl,sda", + "address": "daisy::Pca9685::PCA9685_I2C_BASE_ADDRESS", + "periph": "daisy::I2CHandle::Config::Peripheral::I2C_1", + "speed": "daisy::I2CHandle::Config::Speed::I2C_400KHZ", + "frequency": "1600", + "mapping": [] + }, + "StepperMotor": { + "map_init": "{name} = {parent}.GetStepper({steps}, {index});\n {name}->SetSpeed({speed});", + "typename": "daisy::Adafruit_MotorShield::Adafruit_StepperMotor*", + "direction": "out", + "parent": "", + "steps": 48, + "index": 1, + "speed": 60, + "loopprocess": "{name}->Process();", + "mapping": [ + { + "name": "{name}", + "set": "if ({value} > 0)\n {class_name}.{name}->StepNonblocking({value}, daisy::Adafruit_MotorShield::FORWARD);\n else if ({value} < 0)\n {class_name}.{name}->StepNonblocking(-{value}, daisy::Adafruit_MotorShield::BACKWARD);" + }, + { + "name": "{name}_release", + "set": "if ({value})\n {class_name}.{name}->Release();" + } + ] + }, + "DcMotor": { + "map_init": "{name} = {parent}.GetMotor({index});\n {name}->SetSpeedFine({speed} * 4095);", + "typename": "daisy::Adafruit_MotorShield::Adafruit_DCMotor*", + "direction": "out", + "parent": "", + "index": 1, + "speed": 0.25, + "mapping": [ + { + "name": "{name}", + "set": "if ({value} > 0)\n {class_name}.{name}->Run(daisy::Adafruit_MotorShield::FORWARD);\n else if ({value} < 0)\n {class_name}.{name}->Run(daisy::Adafruit_MotorShield::BACKWARD);\n else\n {class_name}.{name}->FullOff();", + "permit_scale": false + } + ] + }, + "Bme280": { + "map_init": "daisy::Bme280I2CTransport::Config {name}_config;\n {name}_config.periph = {periph};\n {name}_config.speed = {speed};\n {name}_config.scl = som.GetPin({pin_scl});\n {name}_config.sda = som.GetPin({pin_sda});\n {name}_config.address = {address};\n daisy::Bme280I2C::Config {name}_config_main;\n {name}_config_main.transport_config = {name}_config;\n {name}.Init({name}_config_main);", + "typename": "daisy::Bme280I2C", + "direction": "in", + "pin": "scl,sda", + "address": "daisy::Pca9685::PCA9685_I2C_BASE_ADDRESS", + "periph": "daisy::I2CHandle::Config::Peripheral::I2C_1", + "speed": "daisy::I2CHandle::Config::Speed::I2C_400KHZ", + "pressure": 1013, + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{name}.ReadTemperature()", + "where": "main", + "permit_scale": false + }, + { + "name": "{name}_temp", + "get": "{class_name}.{name}.ReadTemperature()", + "where": "main", + "permit_scale": false + }, + { + "name": "{name}_hum", + "get": "{class_name}.{name}.ReadHumidity()", + "where": "main", + "permit_scale": false + }, + { + "name": "{name}_press", + "get": "{class_name}.{name}.ReadPressure()", + "where": "main", + "permit_scale": false + }, + { + "name": "{name}_alt", + "get": "{class_name}.{name}.ReadAltitude({pressure})", + "where": "main", + "permit_scale": false + } + ] + }, + "HallSensor": { + "map_init": "daisy::HallSensor::Config {name}_config;\n {name}_config.periph = {periph};\n {name}_config.pin = som.GetPin({pin});\n {name}.Init({name}_config);", + "typename": "daisy::HallSensor", + "direction": "in", + "pin": 14, + "periph": "daisy::HallSensor::Config::Peripheral::TIM_4", + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{name}.GetState()" + }, + { + "name": "{name}_count", + "get": "{class_name}.{name}.GetCount()", + "permit_scale": false + } + ] + }, + "Tlv493d": { + "map_init": "daisy::Tlv493dI2CTransport::Config {name}_config;\n {name}_config.periph = {periph};\n {name}_config.speed = {speed};\n {name}_config.scl = som.GetPin({pin_scl});\n {name}_config.sda = som.GetPin({pin_sda});\n daisy::Tlv493dI2C::Config {name}_main_conf;\n {name}_main_conf.transport_config = {name}_config;\n {name}_main_conf.address = {address};\n {name}.Init({name}_main_conf);", + "typename": "daisy::Tlv493dI2C", + "direction": "in", + "pin": "scl,sda", + "address": "TLV493D_ADDRESS1", + "periph": "daisy::I2CHandle::Config::Peripheral::I2C_1", + "speed": "daisy::I2CHandle::Config::Speed::I2C_400KHZ", + "loopprocess": "{name}.UpdateData();", + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{name}.GetAmount()", + "permit_scale": false + }, + { + "name": "{name}_x", + "get": "{class_name}.{name}.GetX()", + "permit_scale": false + }, + { + "name": "{name}_y", + "get": "{class_name}.{name}.GetY()", + "permit_scale": false + }, + { + "name": "{name}_z", + "get": "{class_name}.{name}.GetZ()", + "permit_scale": false + }, + { + "name": "{name}_amount", + "get": "{class_name}.{name}.GetAmount()", + "permit_scale": false + }, + { + "name": "{name}_azimuth", + "get": "{class_name}.{name}.GetAzimuth()", + "permit_scale": false + }, + { + "name": "{name}_polar", + "get": "{class_name}.{name}.GetPolar()", + "permit_scale": false + } + ] + }, + "Mpr121": { + "map_init": "daisy::Mpr121I2CTransport::Config {name}_config;\n {name}_config.periph = {periph};\n {name}_config.speed = {speed};\n {name}_config.scl = som.GetPin({pin_scl});\n {name}_config.sda = som.GetPin({pin_sda});\n {name}_config.dev_addr = {address};\n daisy::Mpr121I2C::Config {name}_main_conf;\n {name}_main_conf.transport_config = {name}_config;\n {name}_main_conf.touch_threshold = {touch_threshold};\n {name}_main_conf.release_threshold = {release_threshold};\n {name}.Init({name}_main_conf);", + "typename": "daisy::Mpr121I2C", + "direction": "in", + "pin": "scl,sda", + "address": "MPR121_I2CADDR_DEFAULT", + "periph": "daisy::I2CHandle::Config::Peripheral::I2C_1", + "speed": "daisy::I2CHandle::Config::Speed::I2C_400KHZ", + "touch_threshold": "MPR121_TOUCH_THRESHOLD_DEFAULT", + "release_threshold": "MPR121_RELEASE_THRESHOLD_DEFAULT", + "mapping": [ + { + "name": "{name}", + "get": "(({class_name}.{name}.Touched() & 0x001) ? 1.0f : 0.f)", + "where": "main" + }, + { + "name": "{name}_ch0", + "get": "(({class_name}.{name}.Touched() & 0x001) ? 1.0f : 0.f)", + "where": "main" + }, + { + "name": "{name}_ch1", + "get": "(({class_name}.{name}.Touched() & 0x002) ? 1.0f : 0.f)", + "where": "main" + }, + { + "name": "{name}_ch2", + "get": "(({class_name}.{name}.Touched() & 0x004) ? 1.0f : 0.f)", + "where": "main" + }, + { + "name": "{name}_ch3", + "get": "(({class_name}.{name}.Touched() & 0x008) ? 1.0f : 0.f)", + "where": "main" + }, + { + "name": "{name}_ch4", + "get": "(({class_name}.{name}.Touched() & 0x010) ? 1.0f : 0.f)", + "where": "main" + }, + { + "name": "{name}_ch5", + "get": "(({class_name}.{name}.Touched() & 0x020) ? 1.0f : 0.f)", + "where": "main" + }, + { + "name": "{name}_ch6", + "get": "(({class_name}.{name}.Touched() & 0x040) ? 1.0f : 0.f)", + "where": "main" + }, + { + "name": "{name}_ch7", + "get": "(({class_name}.{name}.Touched() & 0x080) ? 1.0f : 0.f)", + "where": "main" + }, + { + "name": "{name}_ch8", + "get": "(({class_name}.{name}.Touched() & 0x100) ? 1.0f : 0.f)", + "where": "main" + }, + { + "name": "{name}_ch9", + "get": "(({class_name}.{name}.Touched() & 0x200) ? 1.0f : 0.f)", + "where": "main" + }, + { + "name": "{name}_ch10", + "get": "(({class_name}.{name}.Touched() & 0x400) ? 1.0f : 0.f)", + "where": "main" + }, + { + "name": "{name}_ch11", + "get": "(({class_name}.{name}.Touched() & 0x800) ? 1.0f : 0.f)", + "where": "main" + }, + { + "name": "{name}_ch0_raw", + "get": "{class_name}.{name}.FilteredData(0)", + "where": "main", + "range": [ + 0, + 1023 + ] + }, + { + "name": "{name}_ch1_raw", + "get": "{class_name}.{name}.FilteredData(1)", + "where": "main", + "range": [ + 0, + 1023 + ] + }, + { + "name": "{name}_ch2_raw", + "get": "{class_name}.{name}.FilteredData(2)", + "where": "main", + "range": [ + 0, + 1023 + ] + }, + { + "name": "{name}_ch3_raw", + "get": "{class_name}.{name}.FilteredData(3)", + "where": "main", + "range": [ + 0, + 1023 + ] + }, + { + "name": "{name}_ch4_raw", + "get": "{class_name}.{name}.FilteredData(4)", + "where": "main", + "range": [ + 0, + 1023 + ] + }, + { + "name": "{name}_ch5_raw", + "get": "{class_name}.{name}.FilteredData(5)", + "where": "main", + "range": [ + 0, + 1023 + ] + }, + { + "name": "{name}_ch6_raw", + "get": "{class_name}.{name}.FilteredData(6)", + "where": "main", + "range": [ + 0, + 1023 + ] + }, + { + "name": "{name}_ch7_raw", + "get": "{class_name}.{name}.FilteredData(7)", + "where": "main", + "range": [ + 0, + 1023 + ] + }, + { + "name": "{name}_ch8_raw", + "get": "{class_name}.{name}.FilteredData(8)", + "where": "main", + "range": [ + 0, + 1023 + ] + }, + { + "name": "{name}_ch9_raw", + "get": "{class_name}.{name}.FilteredData(9)", + "where": "main", + "range": [ + 0, + 1023 + ] + }, + { + "name": "{name}_ch10_raw", + "get": "{class_name}.{name}.FilteredData(10)", + "where": "main", + "range": [ + 0, + 1023 + ] + }, + { + "name": "{name}_ch11_raw", + "get": "{class_name}.{name}.FilteredData(11)", + "where": "main", + "range": [ + 0, + 1023 + ] + } + ] + }, + "Apds9960": { + "map_init": "daisy::Apds9960I2CTransport::Config {name}_config;\n {name}_config.periph = {periph};\n {name}_config.speed = {speed};\n {name}_config.scl = som.GetPin({pin_scl});\n {name}_config.sda = som.GetPin({pin_sda});\n daisy::Apds9960I2C::Config {name}_main_conf;\n {name}_main_conf.transport_config = {name}_config;\n {name}.Init({name}_main_conf);", + "typename": "daisy::Apds9960I2C", + "direction": "in", + "pin": "scl,sda", + "periph": "daisy::I2CHandle::Config::Peripheral::I2C_1", + "speed": "daisy::I2CHandle::Config::Speed::I2C_400KHZ", + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{name}.ReadGesture()", + "where": "main", + "permit_scale": false + }, + { + "name": "{name}_gest", + "get": "{class_name}.{name}.ReadGesture()", + "where": "main", + "permit_scale": false + }, + { + "name": "{name}_prox", + "get": "{class_name}.{name}.ReadProximity()", + "where": "main", + "permit_scale": false + }, + { + "name": "{name}_red", + "get": "{class_name}.{name}.GetColorDataRed()", + "where": "main", + "permit_scale": false + }, + { + "name": "{name}_green", + "get": "{class_name}.{name}.GetColorDataGreen()", + "where": "main", + "permit_scale": false + }, + { + "name": "{name}_blue", + "get": "{class_name}.{name}.GetColorDataBlue()", + "where": "main", + "permit_scale": false + }, + { + "name": "{name}_clear", + "get": "{class_name}.{name}.GetColorDataClear()", + "where": "main", + "permit_scale": false + } + ] + }, + "Bmp390": { + "map_init": "daisy::Bmp390I2CTransport::Config {name}_config;\n {name}_config.periph = {periph};\n {name}_config.speed = {speed};\n {name}_config.scl = som.GetPin({pin_scl});\n {name}_config.sda = som.GetPin({pin_sda});\n daisy::Bmp390I2C::Config {name}_config_main;\n {name}_config_main.transport_config = {name}_config;\n {name}.Init({name}_config_main);", + "typename": "daisy::Bmp390I2C", + "direction": "in", + "pin": "scl,sda", + "periph": "daisy::I2CHandle::Config::Peripheral::I2C_1", + "speed": "daisy::I2CHandle::Config::Speed::I2C_400KHZ", + "pressure": 1013, + "loopprocess": "{name}.Process();", + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{name}.ReadTemperature()", + "permit_scale": false + }, + { + "name": "{name}_temp", + "get": "{class_name}.{name}.ReadTemperature()", + "permit_scale": false + }, + { + "name": "{name}_press", + "get": "{class_name}.{name}.ReadPressure()", + "permit_scale": false + }, + { + "name": "{name}_alt", + "get": "{class_name}.{name}.ReadAltitude({pressure})", + "permit_scale": false + } + ] + }, + "Vl53l1x": { + "map_init": "daisy::Vl53l1xI2CTransport::Config {name}_config;\n {name}_config.periph = {periph};\n {name}_config.speed = {speed};\n {name}_config.scl = som.GetPin({pin_scl});\n {name}_config.sda = som.GetPin({pin_sda});\n {name}_config.dev_addr = {address};\n daisy::Vl53l1xI2C::Config {name}_main_conf;\n {name}_main_conf.transport_config = {name}_config;\n {name}_main_conf.xShut = som.GetPin({pin_xshut});\n {name}.Init({name}_main_conf);\n {name}.StartRanging();\n {name}.StartTemperatureUpdate();", + "typename": "daisy::Vl53l1xI2C", + "direction": "in", + "pin": "scl,sda,xshut", + "address": 82, + "periph": "daisy::I2CHandle::Config::Peripheral::I2C_1", + "speed": "daisy::I2CHandle::Config::Speed::I2C_400KHZ", + "loopprocess": "{name}.Process();", + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{name}.GetDistance()", + "permit_scale": false + }, + { + "name": "{name}_dist", + "get": "{class_name}.{name}.GetDistance()", + "permit_scale": false + }, + { + "name": "{name}_sig_per_spad", + "get": "{class_name}.{name}.GetSignalPerSpad()", + "permit_scale": false + }, + { + "name": "{name}_amb_per_spad", + "get": "{class_name}.{name}.GetAmbientPerSpad()", + "permit_scale": false + }, + { + "name": "{name}_sig_rate", + "get": "{class_name}.{name}.GetSignalRate()", + "permit_scale": false + }, + { + "name": "{name}_amb_rate", + "get": "{class_name}.{name}.GetAmbientRate()", + "permit_scale": false + }, + { + "name": "{name}_spad_nb", + "get": "{class_name}.{name}.GetSpadNb()", + "permit_scale": false + } + ] + }, + "Vl53l0x": { + "map_init": "daisy::Adafruit_VL53L0X::Config {name}_config;\n {name}_config.periph = {periph};\n {name}_config.speed = {speed};\n {name}_config.scl = som.GetPin({pin_scl});\n {name}_config.sda = som.GetPin({pin_sda});\n {name}_config.dev_addr = {address};\n {name}_config.vl_config = {vl_config};\n {name}.Init({name}_config);", + "typename": "daisy::Adafruit_VL53L0X", + "direction": "in", + "pin": "scl,sda", + "address": "VL53L0X_I2C_ADDR", + "periph": "daisy::I2CHandle::Config::Peripheral::I2C_1", + "speed": "daisy::I2CHandle::Config::Speed::I2C_400KHZ", + "vl_config": "daisy::Adafruit_VL53L0X::VL53L0X_Sense_config_t::VL53L0X_SENSE_DEFAULT", + "loopprocess": "{name}.Process();", + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{name}.GetRangeMilliMeter()", + "permit_scale": false + } + ] + }, + "NeoTrellis": { + "map_init": "daisy::NeoTrellisI2CTransport::Config {name}_config;\n {name}_config.periph = {periph};\n {name}_config.speed = {speed};\n {name}_config.scl = som.GetPin({pin_scl});\n {name}_config.sda = som.GetPin({pin_sda});\n {name}_config.address = {address};\n daisy::NeoTrellisI2C::Config {name}_main_conf;\n {name}_main_conf.transport_config = {name}_config;\n {name}.Init({name}_main_conf);", + "typename": "daisy::NeoTrellisI2C", + "direction": "in", + "pin": "scl,sda", + "address": "NEO_TRELLIS_ADDR", + "periph": "daisy::I2CHandle::Config::Peripheral::I2C_1", + "speed": "daisy::I2CHandle::Config::Speed::I2C_400KHZ", + "loopprocess": "{name}.Process();", + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{name}.GetRising(0)", + "bool": true + }, + { + "name": "{name}_0", + "get": "{class_name}.{name}.GetRising(0)", + "bool": true + }, + { + "name": "{name}_1", + "get": "{class_name}.{name}.GetRising(1)", + "bool": true + }, + { + "name": "{name}_2", + "get": "{class_name}.{name}.GetRising(2)", + "bool": true + }, + { + "name": "{name}_3", + "get": "{class_name}.{name}.GetRising(3)", + "bool": true + }, + { + "name": "{name}_4", + "get": "{class_name}.{name}.GetRising(4)", + "bool": true + }, + { + "name": "{name}_5", + "get": "{class_name}.{name}.GetRising(5)", + "bool": true + }, + { + "name": "{name}_6", + "get": "{class_name}.{name}.GetRising(6)", + "bool": true + }, + { + "name": "{name}_7", + "get": "{class_name}.{name}.GetRising(7)", + "bool": true + }, + { + "name": "{name}_8", + "get": "{class_name}.{name}.GetRising(8)", + "bool": true + }, + { + "name": "{name}_9", + "get": "{class_name}.{name}.GetRising(9)", + "bool": true + }, + { + "name": "{name}_10", + "get": "{class_name}.{name}.GetRising(10)", + "bool": true + }, + { + "name": "{name}_11", + "get": "{class_name}.{name}.GetRising(11)", + "bool": true + }, + { + "name": "{name}_12", + "get": "{class_name}.{name}.GetRising(12)", + "bool": true + }, + { + "name": "{name}_13", + "get": "{class_name}.{name}.GetRising(13)", + "bool": true + }, + { + "name": "{name}_14", + "get": "{class_name}.{name}.GetRising(14)", + "bool": true + }, + { + "name": "{name}_15", + "get": "{class_name}.{name}.GetRising(15)", + "bool": true + }, + { + "name": "{name}_0_falling", + "get": "{class_name}.{name}.GetFalling(0)", + "bool": true + }, + { + "name": "{name}_1_falling", + "get": "{class_name}.{name}.GetFalling(1)", + "bool": true + }, + { + "name": "{name}_2_falling", + "get": "{class_name}.{name}.GetFalling(2)", + "bool": true + }, + { + "name": "{name}_3_falling", + "get": "{class_name}.{name}.GetFalling(3)", + "bool": true + }, + { + "name": "{name}_4_falling", + "get": "{class_name}.{name}.GetFalling(4)", + "bool": true + }, + { + "name": "{name}_5_falling", + "get": "{class_name}.{name}.GetFalling(5)", + "bool": true + }, + { + "name": "{name}_6_falling", + "get": "{class_name}.{name}.GetFalling(6)", + "bool": true + }, + { + "name": "{name}_7_falling", + "get": "{class_name}.{name}.GetFalling(7)", + "bool": true + }, + { + "name": "{name}_8_falling", + "get": "{class_name}.{name}.GetFalling(8)", + "bool": true + }, + { + "name": "{name}_9_falling", + "get": "{class_name}.{name}.GetFalling(9)", + "bool": true + }, + { + "name": "{name}_10_falling", + "get": "{class_name}.{name}.GetFalling(10)", + "bool": true + }, + { + "name": "{name}_11_falling", + "get": "{class_name}.{name}.GetFalling(11)", + "bool": true + }, + { + "name": "{name}_12_falling", + "get": "{class_name}.{name}.GetFalling(12)", + "bool": true + }, + { + "name": "{name}_13_falling", + "get": "{class_name}.{name}.GetFalling(13)", + "bool": true + }, + { + "name": "{name}_14_falling", + "get": "{class_name}.{name}.GetFalling(14)", + "bool": true + }, + { + "name": "{name}_15_falling", + "get": "{class_name}.{name}.GetFalling(15)", + "bool": true + }, + { + "name": "{name}_0_state", + "get": "({class_name}.{name}.GetState(0) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_1_state", + "get": "({class_name}.{name}.GetState(1) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_2_state", + "get": "({class_name}.{name}.GetState(2) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_3_state", + "get": "({class_name}.{name}.GetState(3) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_4_state", + "get": "({class_name}.{name}.GetState(4) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_5_state", + "get": "({class_name}.{name}.GetState(5) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_6_state", + "get": "({class_name}.{name}.GetState(6) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_7_state", + "get": "({class_name}.{name}.GetState(7) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_8_state", + "get": "({class_name}.{name}.GetState(8) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_9_state", + "get": "({class_name}.{name}.GetState(9) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_10_state", + "get": "({class_name}.{name}.GetState(10) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_11_state", + "get": "({class_name}.{name}.GetState(11) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_12_state", + "get": "({class_name}.{name}.GetState(12) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_13_state", + "get": "({class_name}.{name}.GetState(13) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_14_state", + "get": "({class_name}.{name}.GetState(14) ? 1.0f : 0.0f)" + }, + { + "name": "{name}_15_state", + "get": "({class_name}.{name}.GetState(15) ? 1.0f : 0.0f)" + } + ] + }, + "NeoTrellisLeds": { + "map_init": "", + "parent": "", + "direction": "out", + "loopprocess": "{parent}.Show();", + "mapping": [ + { + "name": "{name}", + "set": "{class_name}.{parent}.SetPixelColor(0, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_0", + "set": "{class_name}.{parent}.SetPixelColor(0, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_1", + "set": "{class_name}.{parent}.SetPixelColor(1, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_2", + "set": "{class_name}.{parent}.SetPixelColor(2, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_3", + "set": "{class_name}.{parent}.SetPixelColor(3, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_4", + "set": "{class_name}.{parent}.SetPixelColor(4, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_5", + "set": "{class_name}.{parent}.SetPixelColor(5, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_6", + "set": "{class_name}.{parent}.SetPixelColor(6, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_7", + "set": "{class_name}.{parent}.SetPixelColor(7, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_8", + "set": "{class_name}.{parent}.SetPixelColor(8, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_9", + "set": "{class_name}.{parent}.SetPixelColor(9, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_10", + "set": "{class_name}.{parent}.SetPixelColor(10, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_11", + "set": "{class_name}.{parent}.SetPixelColor(11, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_12", + "set": "{class_name}.{parent}.SetPixelColor(12, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_13", + "set": "{class_name}.{parent}.SetPixelColor(13, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_14", + "set": "{class_name}.{parent}.SetPixelColor(14, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + }, + { + "name": "{name}_15", + "set": "{class_name}.{parent}.SetPixelColor(15, {class_name}.{parent}.pixels.Color({value}, {value}, {value}));", + "where": "main" + } + ] + }, + "Bno055": { + "map_init": "daisy::Bno055I2CTransport::Config {name}_config;\n {name}_config.periph = {periph};\n {name}_config.speed = {speed};\n {name}_config.scl = som.GetPin({pin_scl});\n {name}_config.sda = som.GetPin({pin_sda});\n {name}_config.address = {address};\n daisy::Bno055I2C::Config {name}_main_conf;\n {name}_main_conf.transport_config = {name}_config;\n {name}.Init({name}_main_conf);", + "typename": "daisy::Bno055I2C", + "direction": "in", + "pin": "scl,sda", + "address": "BNO055_ADDRESS_A", + "periph": "daisy::I2CHandle::Config::Peripheral::I2C_1", + "speed": "daisy::I2CHandle::Config::Speed::I2C_400KHZ", + "loopprocess": "{name}.Process();", + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{name}.GetVectorAccelerometer().x", + "permit_scale": false + }, + { + "name": "{name}_accel_x", + "get": "{class_name}.{name}.GetVectorAccelerometer().x", + "permit_scale": false + }, + { + "name": "{name}_accel_y", + "get": "{class_name}.{name}.GetVectorAccelerometer().y", + "permit_scale": false + }, + { + "name": "{name}_accel_z", + "get": "{class_name}.{name}.GetVectorAccelerometer().z", + "permit_scale": false + }, + { + "name": "{name}_magnet_x", + "get": "{class_name}.{name}.GetVectorMagnetometer().x", + "permit_scale": false + }, + { + "name": "{name}_magnet_y", + "get": "{class_name}.{name}.GetVectorMagnetometer().y", + "permit_scale": false + }, + { + "name": "{name}_magnet_z", + "get": "{class_name}.{name}.GetVectorMagnetometer().z", + "permit_scale": false + }, + { + "name": "{name}_gyro_x", + "get": "{class_name}.{name}.GetVectorGyroscope().x", + "permit_scale": false + }, + { + "name": "{name}_gyro_y", + "get": "{class_name}.{name}.GetVectorGyroscope().y", + "permit_scale": false + }, + { + "name": "{name}_gyro_z", + "get": "{class_name}.{name}.GetVectorGyroscope().z", + "permit_scale": false + }, + { + "name": "{name}_euler_x", + "get": "{class_name}.{name}.GetVectorEuler().x", + "permit_scale": false + }, + { + "name": "{name}_euler_y", + "get": "{class_name}.{name}.GetVectorEuler().y", + "permit_scale": false + }, + { + "name": "{name}_euler_z", + "get": "{class_name}.{name}.GetVectorEuler().z", + "permit_scale": false + }, + { + "name": "{name}_linear_accel_x", + "get": "{class_name}.{name}.GetVectorLinearAccel().x", + "permit_scale": false + }, + { + "name": "{name}_linear_accel_y", + "get": "{class_name}.{name}.GetVectorLinearAccel().y", + "permit_scale": false + }, + { + "name": "{name}_linear_accel_z", + "get": "{class_name}.{name}.GetVectorLinearAccel().z", + "permit_scale": false + }, + { + "name": "{name}_grav_x", + "get": "{class_name}.{name}.GetVectorGravity().x", + "permit_scale": false + }, + { + "name": "{name}_grav_y", + "get": "{class_name}.{name}.GetVectorGravity().y", + "permit_scale": false + }, + { + "name": "{name}_grav_z", + "get": "{class_name}.{name}.GetVectorGravity().z", + "permit_scale": false + }, + { + "name": "{name}_quat_x", + "get": "{class_name}.{name}.GetQuat().x", + "permit_scale": false + }, + { + "name": "{name}_quat_y", + "get": "{class_name}.{name}.GetQuat().y", + "permit_scale": false + }, + { + "name": "{name}_quat_z", + "get": "{class_name}.{name}.GetQuat().z", + "permit_scale": false + }, + { + "name": "{name}_quat_w", + "get": "{class_name}.{name}.GetQuat().w", + "permit_scale": false + } + ] + }, + "Icm20948": { + "map_init": "daisy::Icm20948I2CTransport::Config {name}_config;\n {name}_config.periph = {periph};\n {name}_config.speed = {speed};\n {name}_config.scl = som.GetPin({pin_scl});\n {name}_config.sda = som.GetPin({pin_sda});\n {name}_config.address = {address};\n daisy::Icm20948I2C::Config {name}_main_conf;\n {name}_main_conf.transport_config = {name}_config;\n {name}.Init({name}_main_conf);", + "typename": "daisy::Icm20948I2C", + "direction": "in", + "pin": "scl,sda", + "address": "ICM20948_I2CADDR_DEFAULT", + "periph": "daisy::I2CHandle::Config::Peripheral::I2C_1", + "speed": "daisy::I2CHandle::Config::Speed::I2C_400KHZ", + "loopprocess": "{name}.Process();", + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{name}.GetAccelVect().x", + "permit_scale": false + }, + { + "name": "{name}_accel_x", + "get": "{class_name}.{name}.GetAccelVect().x", + "permit_scale": false + }, + { + "name": "{name}_accel_y", + "get": "{class_name}.{name}.GetAccelVect().y", + "permit_scale": false + }, + { + "name": "{name}_accel_z", + "get": "{class_name}.{name}.GetAccelVect().z", + "permit_scale": false + }, + { + "name": "{name}_magnet_x", + "get": "{class_name}.{name}.GetMagVect().x", + "permit_scale": false + }, + { + "name": "{name}_magnet_y", + "get": "{class_name}.{name}.GetMagVect().y", + "permit_scale": false + }, + { + "name": "{name}_magnet_z", + "get": "{class_name}.{name}.GetMagVect().z", + "permit_scale": false + }, + { + "name": "{name}_gyro_x", + "get": "{class_name}.{name}.GetGyroVect().x", + "permit_scale": false + }, + { + "name": "{name}_gyro_y", + "get": "{class_name}.{name}.GetGyroVect().y", + "permit_scale": false + }, + { + "name": "{name}_gyro_z", + "get": "{class_name}.{name}.GetGyroVect().z", + "permit_scale": false + } + ] + }, + "Dps310": { + "map_init": "daisy::Dps310I2CTransport::Config {name}_config;\n {name}_config.address = {address};\n {name}_config.periph = {periph};\n {name}_config.speed = {speed};\n {name}_config.scl = som.GetPin({pin_scl});\n {name}_config.sda = som.GetPin({pin_sda});\n daisy::Dps310I2C::Config {name}_config_main;\n {name}_config_main.transport_config = {name}_config;\n {name}.Init({name}_config_main);", + "typename": "daisy::Dps310I2C", + "direction": "in", + "pin": "scl,sda", + "periph": "daisy::I2CHandle::Config::Peripheral::I2C_1", + "speed": "daisy::I2CHandle::Config::Speed::I2C_400KHZ", + "address": "DPS310_I2CADDR_DEFAULT", + "pressure": 1013, + "loopprocess": "{name}.Process();", + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{name}.GetTemperature()", + "permit_scale": false + }, + { + "name": "{name}_temp", + "get": "{class_name}.{name}.GetTemperature()", + "permit_scale": false + }, + { + "name": "{name}_press", + "get": "{class_name}.{name}.GetPressure()", + "permit_scale": false + }, + { + "name": "{name}_alt", + "get": "{class_name}.{name}.GetAltitude({pressure})", + "permit_scale": false + } + ] + }, + "CodeClass": { + "map_init": "{name}.Init(&som);", + "typename": "", + "process": "", + "loopprocess": "", + "header_path": "", + "mapping": [] + }, + "CodeInput": { + "direction": "out", + "typename": "", + "parent": "", + "setter": "", + "mapping": [ + { + "name": "{name}", + "set": "{class_name}.{parent}.{setter}({value});", + "permit_scale": false + } + ] + }, + "CodeOutput": { + "direction": "in", + "typename": "", + "parent": "", + "getter": "", + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{parent}.{getter}()", + "permit_scale": false + } + ] + } +} \ No newline at end of file diff --git a/source/component_defs_patchsm.json b/source/component_defs_patchsm.json new file mode 100644 index 0000000..7c12383 --- /dev/null +++ b/source/component_defs_patchsm.json @@ -0,0 +1,262 @@ +{ + "Switch": { + "map_init": "{name}.Init(daisy::patch_sm::DaisyPatchSM::{pin}, som.AudioCallbackRate(), {type}, {polarity}, {pull});", + "typename": "daisy::Switch", + "direction": "in", + "pin": "a", + "type": "daisy::Switch::TYPE_MOMENTARY", + "polarity": "daisy::Switch::POLARITY_INVERTED", + "pull": "daisy::Switch::PULL_UP", + "process": "{name}.Debounce();", + "updaterate": "{name}.SetUpdateRate(som.AudioCallbackRate());", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.{name}.Pressed()?1.f:0.f)", + "range": [0, 1], + "bool": false + }, + { + "name": "{name}_press", + "get": "({class_name}.{name}.Pressed()?1.f:0.f)", + "range": [0, 1], + "bool": false + }, + { + "name": "{name}_seconds", + "get": "({class_name}.{name}.TimeHeldMs()*0.001f)", + "bool": false + } + ] + }, + "Switch3": { + "map_init": "{name}.Init(daisy::patch_sm::DaisyPatchSM::{pin_a}, daisy::patch_sm::DaisyPatchSM::{pin_b}));", + "typename": "daisy::Switch3", + "direction": "in", + "pin": "a,b", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.{name}.Read()*0.5f+0.5f)", + "range": [0, 2], + "bool": false + } + ] + }, + "Encoder": { + "map_init": "{name}.Init(daisy::patch_sm::DaisyPatchSM::{pin_a}, daisy::patch_sm::DaisyPatchSM::{pin_b}, daisy::patch_sm::DaisyPatchSM::{pin_click}, som.AudioCallbackRate());", + "typename": "daisy::Encoder", + "direction": "in", + "pin": "a,b,click", + "process": "{name}.Debounce();", + "updaterate": "{name}.SetUpdateRate(som.AudioCallbackRate());", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.{name}.Increment())", + "range": [-1, 1], + "bool": false + }, + { + "name": "{name}_press", + "get": "({class_name}.{name}.Pressed()?1.f:0.f)", + "range": [0, 1], + "bool": false + }, + { + "name": "{name}_rise", + "get": "({class_name}.{name}.RisingEdge()?1.f:0.f)", + "range": [0, 1], + "bool": true + }, + { + "name": "{name}_fall", + "get": "({class_name}.{name}.FallingEdge()?1.f:0.f)", + "range": [0, 1], + "bool": true + }, + { + "name": "{name}_seconds", + "get": "({class_name}.{name}.TimeHeldMs()*0.001f)", + "range": null, + "bool": false + } + ] + }, + "GateIn": { + "map_init": "{name}.Init(&daisy::patch_sm::DaisyPatchSM::{pin});", + "typename": "daisy::GateIn", + "direction": "in", + "pin": "a", + "default_prefix": "som.", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.{default_prefix}{name}.State()?1.f:0.f)", + "range": [0, 1], + "bool": false + }, + { + "name": "{name}_trig", + "get": "({class_name}.{default_prefix}{name}.Trig()?1.f:0.f)", + "range": [0, 1], + "bool": true + } + ] + }, + "AnalogControl": { + "init_single": "cfg[{i}].InitSingle(daisy::patch_sm::DaisyPatchSM::{pin});", + "map_init": "{name}.Init(som.adc.GetPtr({i}), som.AudioCallbackRate(), {flip}, {invert});", + "typename": "daisy::AnalogControl", + "direction": "in", + "pin": "a", + "flip": "false", + "invert": "false", + "slew": "1.0/som.AudioCallbackRate()", + "process": "{name}.Process();", + "updaterate": "{name}.SetSampleRate(som.AudioCallbackRate());", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.som.GetAdcValue((int)daisy::patch_sm::{name_upper}))", + "range": [0, 1], + "bool": false + } + ] + }, + "AnalogControlBipolar": { + "init_single": "cfg[{i}].InitSingle(daisy::patch_sm::DaisyPatchSM::{pin});", + "map_init": "{name}.Init(som.adc.GetPtr({i}), som.AudioCallbackRate(), {flip}, {invert});", + "typename": "daisy::AnalogControl", + "direction": "in", + "pin": "a", + "flip": "false", + "invert": "false", + "slew": "1.0/som.AudioCallbackRate()", + "process": "{name}.Process();", + "updaterate": "{name}.SetSampleRate(som.AudioCallbackRate());", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.som.GetAdcValue((int)daisy::patch_sm::{name_upper}))", + "range": [-1, 1], + "bool": false + } + ] + }, + "Led": { + "map_init": "{name}.Init(daisy::patch_sm::DaisyPatchSM::{pin}, {invert});\n\t\t{name}.Set(0.0f);", + "typename": "daisy::Led", + "direction": "out", + "pin": "a", + "invert": "true", + "postprocess": "{name}.Update();", + "mapping": [ + { + "name": "{name}", + "set": "{class_name}.{name}.Set({value});" + } + ] + }, + "RgbLed": { + "map_init": "{name}.Init(daisy::patch_sm::DaisyPatchSM::{pin_r}, daisy::patch_sm::DaisyPatchSM::{pin_g}, daisy::patch_sm::DaisyPatchSM::{pin_b}, {invert});\n\t\t{name}.Set(0.0f, 0.0f, 0.0f);", + "typename": "daisy::RgbLed", + "direction": "out", + "pin": "r,g,b", + "invert": "true", + "postprocess": "{name}.Update();", + "mapping": [ + { + "name": "{name}_red", + "set": "{class_name}.{name}.SetRed({value});" + }, + { + "name": "{name}_green", + "set": "{class_name}.{name}.SetGreen({value});" + }, + { + "name": "{name}_blue", + "set": "{class_name}.{name}.SetBlue({value});" + }, + { + "name": "{name}", + "set": "{class_name}.{name}.Set(clamp(-{value}, 0.f, 1.f), 0.f, clamp({value}, 0.f, 1.f));" + }, + { + "name": "{name}_white", + "set": "{class_name}.{name}.Set({value},{value},{value});" + } + ] + }, + "GateOut": { + "map_init": "{name}.pin = daisy::patch_sm::DaisyPatchSM::{pin};\n\t\t{name}.mode = {mode};\n\t\t{name}.pull = {pull};\n\t\tdsy_gpio_init(&{name});", + "typename": "dsy_gpio", + "direction": "out", + "pin": "a", + "default_prefix": "som.", + "mode": "DSY_GPIO_MODE_OUTPUT_PP", + "pull": "DSY_GPIO_NOPULL", + "mapping": [ + { + "name": "{name}", + "set": "dsy_gpio_write(&{class_name}.{default_prefix}{name}, {value});" + } + ] + }, + "CVOuts": { + "map_init": "{name}.bitdepth = {bitdepth};\n\t\t{name}.buff_state = {buff_state};\n\t\t{name}.mode = {mode};\n\t\t{name}.chn = {channel};\n\t\tsom.dac.Init({name});\n\t\tsom.dac.WriteValue({channel}, 0);", + "typename": "daisy::DacHandle::Config", + "direction": "out", + "pin": "", + "bitdepth": "daisy::DacHandle::BitDepth::BITS_12", + "buff_state": "daisy::DacHandle::BufferState::ENABLED", + "mode": "daisy::DacHandle::Mode::POLLING", + "channel": "daisy::DacHandle::Channel::BOTH", + "mapping": [ + { + "name": "{name}1", + "set": "{class_name}.som.WriteCvOut(daisy::patch_sm::CV_OUT_1, {value} * 5.f);", + "where": "main" + }, + { + "name": "{name}2", + "set": "{class_name}.som.WriteCvOut(daisy::patch_sm::CV_OUT_2, {value} * 5.f);", + "where": "main" + } + ] + }, + "CodeClass": { + "map_init": "{name}.Init(&som);", + "typename": "", + "process": "", + "loopprocess": "", + "header_path": "", + "mapping": [] + }, + "CodeInput": { + "direction": "out", + "typename": "", + "parent": "", + "setter": "", + "mapping": [ + { + "name": "{name}", + "set": "{class_name}.{parent}.{setter}({value});", + "permit_scale": false + } + ] + }, + "CodeOutput": { + "direction": "in", + "typename": "", + "parent": "", + "getter": "", + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{parent}.{getter}()", + "permit_scale": false + } + ] + } +} diff --git a/source/component_defs_petalsm.json b/source/component_defs_petalsm.json new file mode 100644 index 0000000..a40141a --- /dev/null +++ b/source/component_defs_petalsm.json @@ -0,0 +1,154 @@ +{ + "Switch": { + "map_init": "", + "typename": "", + "direction": "in", + "index": 1, + "updaterate": "", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.som.footswitch{index}.Pressed()?1.f:0.f)", + "range": [0, 1], + "bool": false + }, + { + "name": "{name}_press", + "get": "({class_name}.som.footswitch{index}.Pressed()?1.f:0.f)", + "range": [0, 1], + "bool": false + }, + { + "name": "{name}_seconds", + "get": "({class_name}.som.footswitch{index}.TimeHeldMs()*0.001f)", + "bool": false + } + ] + }, + "Switch3": { + "map_init": "", + "typename": "", + "direction": "in", + "index": 0, + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.som.toggle[{index}].Read()*0.5f+0.5f)", + "range": [0, 2], + "bool": false + } + ] + }, + "AnalogControl": { + "init_single": "", + "map_init": "", + "typename": "", + "direction": "in", + "index": 0, + "updaterate": "", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.som.knob[{index}].Value())", + "range": [0, 1], + "bool": false + } + ] + }, + "Expression": { + "direction": "in", + "mapping": [ + { + "name": "{name}", + "get": "({class_name}.som.GetExpressionValue())" + } + ] + }, + "Led": { + "map_init": "", + "typename": "", + "direction": "out", + "index": 2, + "postprocess": "", + "mapping": [ + { + "name": "{name}", + "set": "{class_name}.som.led[{index}].Set({value});" + } + ] + }, + "RgbLed": { + "map_init": "", + "typename": "", + "direction": "out", + "index": "r,g,b", + "mapping": [ + { + "name": "{name}_red", + "set": "{class_name}.som.led[{index_r}].Set({value});" + }, + { + "name": "{name}_green", + "set": "{class_name}.som.led[{index_g}].Set({value});" + }, + { + "name": "{name}_blue", + "set": "{class_name}.som.led[{index_b}].Set({value});" + }, + { + "name": "{name}", + "set": "{class_name}.som.led[{index_r}].Set(clamp(-{value}, 0.f, 1.f)); \n{class_name}.som.led[{index_g}].Set(0); \n{class_name}.som.led[{index_b}].Set(clamp({value}, 0.f, 1.f));" + }, + { + "name": "{name}_white", + "set": "{class_name}.som.led[{index_r}].Set({value}); \n{class_name}.som.led[{index_g}].Set({value}); \n{class_name}.som.led[{index_b}].Set({value});" + } + ] + }, + "Relay": { + "map_init": "", + "typename": "", + "direction": "out", + "mapping": [ + { + "name": "{name}", + "set": "{class_name}.som.SetBypassState({value});" + } + ] + }, + "CodeClass": { + "map_init": "{name}.Init(&som);", + "typename": "", + "process": "", + "loopprocess": "", + "header_path": "", + "mapping": [] + }, + "CodeInput": { + "direction": "out", + "typename": "", + "parent": "", + "setter": "", + "mapping": [ + { + "name": "{name}", + "set": "{class_name}.{parent}.{setter}({value});", + "permit_scale": false + } + ] + }, + "CodeOutput": { + "direction": "in", + "typename": "", + "parent": "", + "getter": "", + "mapping": [ + { + "name": "{name}", + "get": "{class_name}.{parent}.{getter}()", + "permit_scale": false + } + ] + } +} + diff --git a/source/cosmolab.json b/source/cosmolab.json new file mode 100644 index 0000000..6b9d9b0 --- /dev/null +++ b/source/cosmolab.json @@ -0,0 +1,585 @@ +{ + "vendor": "faselunare", + "name": "cosmolab", + "version": "1.0.0", + "som": "seed", + "defines": { + "OOPSY_TARGET_HAS_OLED": 1, + "OOPSY_TARGET_HAS_MIDI_INPUT": 1, + "OOPSY_TARGET_HAS_MIDI_OUTPUT": 1 + }, + "display": { + "driver": "daisy::SSD130xI2c64x32Driver", + "dim": [ + 64, + 32 + ] + }, + "parents": { + "i2c": { + "component": "i2c", + "pin": { + "scl": 11, + "sda": 12 + } + }, + "led_driver": { + "component": "PCA9685", + "address": "{0x00, 0x02, 0xCC}", + "parent": "i2c", + "driver_count": 3 + }, + "pad_shift": { + "component": "CD4021", + "driver_count": 4, + "pin": { + "clk": 28, + "cs": 27, + "data": 26 + } + }, + "pot_mux": { + "component": "CD4051", + "mux_count": 8, + "pin": { + "adc": 16, + "sel0": 21, + "sel1": 20, + "sel2": 19 + } + } + }, + "audio": { + "channels": 2 + }, + "components": { + "sw1": { + "component": "Switch", + "pin": 30 + }, + "sw2": { + "component": "Switch", + "pin": 29 + }, + "cv1": { + "component": "AnalogControl", + "pin": 17 + }, + "cv2": { + "component": "AnalogControl", + "pin": 18 + }, + "cv3": { + "component": "AnalogControl", + "pin": 25 + }, + "cv4": { + "component": "AnalogControl", + "pin": 24 + }, + "knob1": { + "component": "CD4051AnalogControl", + "index": 0, + "parent": "pot_mux" + }, + "knob2": { + "component": "CD4051AnalogControl", + "index": 1, + "parent": "pot_mux" + }, + "knob3": { + "component": "CD4051AnalogControl", + "index": 2, + "parent": "pot_mux" + }, + "knob4": { + "component": "CD4051AnalogControl", + "index": 3, + "parent": "pot_mux" + }, + "knob5": { + "component": "CD4051AnalogControl", + "index": 4, + "parent": "pot_mux" + }, + "knob6": { + "component": "CD4051AnalogControl", + "index": 5, + "parent": "pot_mux" + }, + "knob7": { + "component": "CD4051AnalogControl", + "index": 6, + "parent": "pot_mux" + }, + "knob8": { + "component": "CD4051AnalogControl", + "index": 7, + "parent": "pot_mux" + }, + "cvout": { + "component": "CVOuts" + }, + "gatein": { + "component": "GateIn", + "pin": 0 + }, + "gateout": { + "component": "GateOut", + "pin": 15 + }, + "pada1": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 0, + "note": "Linear keyboard 1" + }, + "pada2": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 1, + "note": "Linear keyboard 2" + }, + "pada3": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 2, + "note": "Linear keyboard 3" + }, + "pada4": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 3, + "note": "Linear keyboard 4" + }, + "pada5": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 4, + "note": "Linear keyboard 5" + }, + "pada6": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 5, + "note": "Linear keyboard 6" + }, + "pada7": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 6, + "note": "Linear keyboard 7" + }, + "pada8": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 7, + "note": "Linear keyboard 8" + }, + "padb1": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 8, + "note": "Linear keyboard 9" + }, + "padb2": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 9, + "note": "Linear keyboard 10" + }, + "padb3": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 10, + "note": "Linear keyboard 11" + }, + "padb4": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 11, + "note": "Linear keyboard 12" + }, + "padb5": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 12, + "note": "Linear keyboard 13" + }, + "padb6": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 13, + "note": "Linear keyboard 14" + }, + "padb7": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 14, + "note": "Linear keyboard 15" + }, + "padb8": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 15, + "note": "Linear keyboard 16" + }, + "padc1": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 16 + }, + "padc2": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 17 + }, + "padc3": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 18 + }, + "padc4": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 19 + }, + "padc5": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 20 + }, + "padc6": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 21 + }, + "padc7": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 22 + }, + "padc8": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 23 + }, + "padd1": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 24 + }, + "padd2": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 25 + }, + "padd3": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 26 + }, + "padd4": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 27 + }, + "padd5": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 28 + }, + "padd6": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 29 + }, + "padd7": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 30 + }, + "padd8": { + "component": "CD4021Switch", + "parent": "pad_shift", + "index": 31 + }, + "led_key_a1": { + "component": "PCA9685Led", + "index": 0, + "parent": "led_driver" + }, + "led_key_a2": { + "component": "PCA9685Led", + "index": 1, + "parent": "led_driver" + }, + "led_key_a3": { + "component": "PCA9685Led", + "index": 2, + "parent": "led_driver" + }, + "led_key_a4": { + "component": "PCA9685Led", + "index": 3, + "parent": "led_driver" + }, + "led_key_a5": { + "component": "PCA9685Led", + "index": 4, + "parent": "led_driver" + }, + "led_key_a6": { + "component": "PCA9685Led", + "index": 5, + "parent": "led_driver" + }, + "led_key_a7": { + "component": "PCA9685Led", + "index": 6, + "parent": "led_driver" + }, + "led_key_a8": { + "component": "PCA9685Led", + "index": 7, + "parent": "led_driver" + }, + "led_key_b1": { + "component": "PCA9685Led", + "index": 8, + "parent": "led_driver" + }, + "led_key_b2": { + "component": "PCA9685Led", + "index": 9, + "parent": "led_driver" + }, + "led_key_b3": { + "component": "PCA9685Led", + "index": 10, + "parent": "led_driver" + }, + "led_key_b4": { + "component": "PCA9685Led", + "index": 11, + "parent": "led_driver" + }, + "led_key_b5": { + "component": "PCA9685Led", + "index": 12, + "parent": "led_driver" + }, + "led_key_b6": { + "component": "PCA9685Led", + "index": 13, + "parent": "led_driver" + }, + "led_key_b7": { + "component": "PCA9685Led", + "index": 14, + "parent": "led_driver" + }, + "led_key_b8": { + "component": "PCA9685Led", + "index": 15, + "parent": "led_driver" + }, + "led_knob_1": { + "component": "PCA9685Led", + "index": 16, + "parent": "led_driver" + }, + "led_knob_2": { + "component": "PCA9685Led", + "index": 17, + "parent": "led_driver" + }, + "led_knob_3": { + "component": "PCA9685Led", + "index": 18, + "parent": "led_driver" + }, + "led_knob_4": { + "component": "PCA9685Led", + "index": 19, + "parent": "led_driver" + }, + "led_knob_5": { + "component": "PCA9685Led", + "index": 20, + "parent": "led_driver" + }, + "led_knob_6": { + "component": "PCA9685Led", + "index": 21, + "parent": "led_driver" + }, + "led_knob_7": { + "component": "PCA9685Led", + "index": 22, + "parent": "led_driver" + }, + "led_knob_8": { + "component": "PCA9685Led", + "index": 23, + "parent": "led_driver" + }, + "led_knob_under_1": { + "component": "PCA9685Led", + "index": 24, + "parent": "led_driver" + }, + "led_knob_under_2": { + "component": "PCA9685Led", + "index": 25, + "parent": "led_driver" + }, + "led_knob_under_3": { + "component": "PCA9685Led", + "index": 26, + "parent": "led_driver" + }, + "led_knob_under_4": { + "component": "PCA9685Led", + "index": 27, + "parent": "led_driver" + }, + "led_knob_under_5": { + "component": "PCA9685Led", + "index": 28, + "parent": "led_driver" + }, + "led_knob_under_6": { + "component": "PCA9685Led", + "index": 29, + "parent": "led_driver" + }, + "led_knob_under_7": { + "component": "PCA9685Led", + "index": 30, + "parent": "led_driver" + }, + "led_knob_under_8": { + "component": "PCA9685Led", + "index": 31, + "parent": "led_driver" + }, + "led_key_c1": { + "component": "PCA9685Led", + "index": 32, + "parent": "led_driver" + }, + "led_key_c2": { + "component": "PCA9685Led", + "index": 33, + "parent": "led_driver" + }, + "led_key_c3": { + "component": "PCA9685Led", + "index": 34, + "parent": "led_driver" + }, + "led_key_c4": { + "component": "PCA9685Led", + "index": 35, + "parent": "led_driver" + }, + "led_key_c5": { + "component": "PCA9685Led", + "index": 36, + "parent": "led_driver" + }, + "led_key_c6": { + "component": "PCA9685Led", + "index": 37, + "parent": "led_driver" + }, + "led_key_c7": { + "component": "PCA9685Led", + "index": 38, + "parent": "led_driver" + }, + "led_key_c8": { + "component": "PCA9685Led", + "index": 39, + "parent": "led_driver" + }, + "led_key_d1": { + "component": "PCA9685Led", + "index": 40, + "parent": "led_driver" + }, + "led_key_d2": { + "component": "PCA9685Led", + "index": 41, + "parent": "led_driver" + }, + "led_key_d3": { + "component": "PCA9685Led", + "index": 42, + "parent": "led_driver" + }, + "led_key_d4": { + "component": "PCA9685Led", + "index": 43, + "parent": "led_driver" + }, + "led_key_d5": { + "component": "PCA9685Led", + "index": 44, + "parent": "led_driver" + }, + "led_key_d6": { + "component": "PCA9685Led", + "index": 45, + "parent": "led_driver" + }, + "led_key_d7": { + "component": "PCA9685Led", + "index": 46, + "parent": "led_driver" + }, + "led_key_d8": { + "component": "PCA9685Led", + "index": 47, + "parent": "led_driver" + } + }, + "aliases": { + "ctrl1": "knob1", + "ctrl2": "knob2", + "ctrl3": "knob3", + "ctrl4": "knob4", + "ctrl5": "knob5", + "ctrl6": "knob6", + "ctrl7": "knob7", + "ctrl8": "knob8", + "knob": "knob1", + "ctrl": "knob1", + "cv": "cv1", + "gate": "gatein", + "switch": "sw1", + "switch1": "sw1", + "switch2": "sw2", + "padsw1": "sw1", + "padsw2": "sw2", + "led": "led_key_a1", + "leds": "led_key_a1", + "knob1l": "led_knob_under_1", + "knob2l": "led_knob_under_2", + "knob3l": "led_knob_under_3", + "knob4l": "led_knob_under_4", + "knob5l": "led_knob_under_5", + "knob6l": "led_knob_under_6", + "knob7l": "led_knob_under_7", + "knob8l": "led_knob_under_8", + "knob1i": "led_knob_1", + "knob2i": "led_knob_2", + "knob3i": "led_knob_3", + "knob4i": "led_knob_4", + "knob5i": "led_knob_5", + "knob6i": "led_knob_6", + "knob7i": "led_knob_7", + "knob8i": "led_knob_8" + } +} \ No newline at end of file diff --git a/source/daisy_glue.js b/source/daisy_glue.js new file mode 100644 index 0000000..f751471 --- /dev/null +++ b/source/daisy_glue.js @@ -0,0 +1,255 @@ +const assert = require("assert"); + +function stringFormatMap(template, formatMap) +{ + if (typeof template === 'undefined') + return ''; + const format_match = /{\s*([^{}\s]*)\s*}/g; + const open_curly = /{{/g; + const close_curly = /}}/g; + let pass1 = template.replace(open_curly, () => { + return '{' + }); + let pass2 = pass1.replace(close_curly, () => { + return '}' + }); + let pass3 = pass2.replace(format_match, (substring, value, index) => { + return value in formatMap ? formatMap[value] : ''; + }); + return pass3; +} + +// .filter for objects that returns object +Object.filter = (obj, predicate) => + Object.keys(obj).filter(key => predicate(obj[key])) + .map(key => obj[key]); + +function filter_match(sequence, key, match, key_exclude = null, match_exclude = null) +{ + if (key_exclude !== null && match_exclude !== null) + { + return Object.filter(sequence, item => key in item && item[key] == match && ((item[key_exclude] || null) != match_exclude)); + } + else + return Object.filter(sequence, item => key in item && item[key] == match); +} + +function verify_param_exists(name, original_name, components, input=true) +{ + for (let comp of components) + { + if (comp.component == 'CVOuts') + { + if (name == comp.name) + { + assert(!input, `Parameter ${original_name} cannot be used as an ${input ? 'input' : 'output'}`); + return; + } + } + else + { + let variants = comp.mapping.map(item => stringFormatMap(item.name, comp)); + if (variants.includes(name)) + { + assert((input && comp.direction == 'input') || (!input && comp.direction == 'output'), + `Parameter ${original_name} cannot be used as an ${input ? 'input' : 'output'}`); + return; + } + } + } + assert(false, `Unkown parameter ${original_name}`); +} + +function verify_param_direction(name, components) +{ + for (let comp of components) + { + if (comp.component == 'CVOuts') + { + if (name == comp.name) + return true; + } + else + { + let variants = comp.mapping.map(item => stringFormatMap(item.name, comp)); + if (variants.includes(name)) + return true; + } + } + return false; +} + +function get_root_component(variant, original_name, components) +{ + for (let comp of components) + { + if (comp.component == 'CVOuts') + { + if (variant == comp.name) + return variant; + } + else + { + let variants = comp.mapping.map(item => stringFormatMap(item.name, comp)); + if (variants.includes(variant)) + return comp.name; + } + } + assert(false, `Unkown parameter ${original_name}`); +} + +function get_component_mapping(component_variant, original_name, component, components) +{ + for (let variant of component.mapping) + { + if (component.component == 'CVOuts') + { + let stripped = stringFormatMap(variant.name, {name: ''}); + if (component.name.includes(stripped)) + return variant; + } + else if (stringFormatMap(variant.name, component) == component_variant) + return variant; + } + assert(false, `Unkown parameter ${original_name}`); +} + + +function verify_param_used(component, params_in, params_out, params_in_original_name, params_out_original_name, components) +{ + // Exclude parents, since they don't have 1-1 i/o mapping + if (component.is_parent || false) + return true; + + let combined_params; + Object.assign(combined_params, params_in, params_out); + let combined_names; + Object.assign(combined_names, params_in_original_name, params_out_original_name); + for (let param in combined_params) + { + let root = get_root_component(param, combined_names[param], components); + if (root == component.name) + return true; + } + return false; +} + +function de_alias(name, aliases, components) +{ + let low = name.toLowerCase(); + // simple case + if (aliases.includes(low)) + return aliases[low]; + // aliased variant + let potential_aliases = Object.filter(aliases, item => low.includes(item)); + for (let alias of potential_aliases) + { + target_component = filter_match(components, 'name', aliases[alias])[0] || undefined; + if (typeof target_component === 'undefined') + continue; + if (target_component.component != 'CVOuts') + { + for (let mapping of target_component.mapping) + { + if (stringFormatMap(mapping.name, {name: alias}) == low) + return stringFormatMap(mapping.name, {name: aliases[alias]}); + } + } + } + // otherwise, it's a direct parameter or unkown one + return low; +} + +// Parses the `parameters` passed from oopsy and generates getters and setters +// according to the info in `components`. The `aliases` help disambiguate parameters +// and the `object_name` sets the identifier for the generated Daisy hardware class. +exports.parse_parameters = function parse_parameters(parameters, components, aliases, object_name) +{ + // Verify that the params are valid and remove unused components + let replacements = {}; + + let params_in = {}; + let params_in_original_names = {}; + for (property in parameters.in) + { + let de_aliased = de_alias(property, aliases, components); + params_in[de_aliased] = parameters.in[property]; + params_in_original_names[de_aliased] = property; + } + + let params_out = {}; + let params_out_original_names = {}; + for (property in parameters.out) + { + let de_aliased = de_alias(property, aliases, components); + params_out[de_aliased] = parameters.out[property]; + params_out_original_names[de_aliased] = property; + } + + for (property in params_in) + verify_param_exists(property, params_in_original_names[property], components, input=true); + for (property in params_out) + verify_param_exists(property, params_out_original_names[property], components, input=false); + + for (let i = components.length - 1; i > -1; i--) + { + let used = verify_param_used(components[i], params_in, params_out, + params_in_original_names, params_out_original_names, components); + if (!used) + components.splice(i, 1); + } + + let out_idx = 0; + replacements.parameters = []; + replacements.output_parameters = []; + replacements.callback_write_out = []; + replacements.loop_write_out = ''; + replacements.callback_write_in = []; + + for (let param_name in params_in) + { + root = get_root_component(param_name, params_in_original_names[param_name], components); + let component = filter_match(components, 'name', root)[0]; + let param_struct = { + name: root, + type: component.component.toUpperCase() + }; + replacements.parameters.push(param_struct); + let mapping = get_component_mapping(param_name, params_in_original_names[param_name], component, components); + + let component_info; + Object.assign(component_info, component); + component_info.name = root; + component_info.class_name = object_name; + component_info.name_upper = root.toUpperCase(); + component_info.value = `output_data[${out_idx}]`; + component_info.default_prefix = (component.is_default || false) ? component.default_prefix || '' : ''; + let process = stringFormatMap(mapping.get, component_info); + replacements.callback_write_in.push({process: process, bool: mapping.bool}); + } + + for (let param_name in params_out) + { + root = get_root_component(param_name, params_in_original_names[param_name], components); + let component = filter_match(components, 'name', root)[0]; + let param_struct = { + name: root, + index: out_idx + }; + replacements.output_parameters.push(param_struct); + let mapping = get_component_mapping(param_name, params_out_original_names[param_name], component, components); + let write_location = (mapping.where || 'callback') == 'callback' ? 'callback_write_out' : 'loop_write_out'; + let component_info; + Object.assign(component_info, component); + component_info.name = root; + component_info.class_name = object_name; + component_info.value = `output_data[${out_idx}]`; + component_info.default_prefix = (component.is_default || false) ? component.default_prefix || '' : ''; + let write = stringFormatMap(mapping.set, component_info); + replacements[write_location] += `\n ${write}`; + } + + replacements.output_comps = replacements.output_parameters.length; + + return replacements; +} \ No newline at end of file diff --git a/source/genlib_daisy.h b/source/genlib_daisy.h index 527c7f7..8aa41df 100644 --- a/source/genlib_daisy.h +++ b/source/genlib_daisy.h @@ -2,25 +2,37 @@ #define GENLIB_DAISY_H /* -Oopsy was authored in 2020-2021 by Graham Wakefield. Copyright 2021 Electrosmith, Corp. and Graham Wakefield. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Oopsy was authored in 2020-2021 by Graham Wakefield. Copyright 2021 +Electrosmith, Corp. and Graham Wakefield. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "daisy.h" +#include "daisy_seed.h" #include "genlib.h" -#include "genlib_ops.h" #include "genlib_exportfunctions.h" -#include "daisy_seed.h" +#include "genlib_ops.h" -#include -#include #include // memset +#include #include // vprintf +#include // #if defined(OOPSY_TARGET_SEED) // typedef struct { @@ -34,9 +46,18 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI // #endif #ifdef OOPSY_USE_USB_SERIAL_INPUT -static char sumbuff[1024]; -static uint32_t rx_size = 0; -static bool update = false; +static char sumbuff[1024]; +static uint32_t rx_size = 0; +static bool update = false; +#endif + +// A temproary measure to preserve Field compatibility +#ifdef OOPSY_TARGET_FIELD +#include "daisy_field.h" +#endif + +#ifdef OOPSY_TARGET_PETAL +#include "petal_led_hardcode.h" #endif ////////////////////////// DAISY EXPORT INTERFACING ////////////////////////// @@ -46,7 +67,7 @@ static bool update = false; #define OOPSY_SUPER_LONG_PRESS_MS (20000) #define OOPSY_DISPLAY_PERIOD_MS 10 #define OOPSY_SCOPE_MAX_ZOOM (8) -static const uint32_t OOPSY_SRAM_SIZE = 512 * 1024; +static const uint32_t OOPSY_SRAM_SIZE = 512 * 1024; static const uint32_t OOPSY_SDRAM_SIZE = 64 * 1024 * 1024; // Added dedicated global SDFile to replace old global from libDaisy @@ -54,1072 +75,1248 @@ FIL SDFile; namespace oopsy { - uint32_t sram_used = 0, sram_usable = 0; - uint32_t sdram_used = 0, sdram_usable = 0; - char * sram_pool = nullptr; - char DSY_SDRAM_BSS sdram_pool[OOPSY_SDRAM_SIZE]; - - void init() { - if (!sram_pool) sram_pool = (char *)malloc(OOPSY_SRAM_SIZE); - sram_usable = OOPSY_SRAM_SIZE; - sram_used = 0; - sdram_usable = OOPSY_SDRAM_SIZE; - sdram_used = 0; - } - - void * allocate(uint32_t size) { - if (size < sram_usable) { - void * p = sram_pool + sram_used; - sram_used += size; - sram_usable -= size; - return p; - } else if (size < sdram_usable) { - void * p = sdram_pool + sdram_used; - sdram_used += size; - sdram_usable -= size; - return p; - } - return nullptr; - } - - void memset(void *p, int c, long size) { - char *p2 = (char *)p; - int i; - for (i = 0; i < size; i++, p2++) *p2 = char(c); - } - - // void genlib_memcpy(void *dst, const void *src, long size) { - // char *s2 = (char *)src; - // char *d2 = (char *)dst; - // int i; - // for (i = 0; i < size; i++, s2++, d2++) - // *d2 = *s2; - // } - - // void test() { - // // memory test: - // size_t allocated = 0; - // size_t sz = 256; - // int i; - // while (sz < 515) { - // sz++; - // void * m = malloc(sz * 1024); - // if (!m) break; - // free(m); - // log("%d: malloced %dk", i, sz); - // i++; - // } - // log("all OK"); - // } - - struct Timer { - int32_t period = OOPSY_DISPLAY_PERIOD_MS, - t = OOPSY_DISPLAY_PERIOD_MS; - - bool ready(int32_t dt) { - t += dt; - if (t > period) { - t = 0; - return true; - } - return false; - } - }; - - struct AppDef { - const char * name; - void (*load)(); - }; - typedef enum { - #ifdef OOPSY_TARGET_HAS_OLED - MODE_SCOPE, - #ifdef OOPSY_HAS_PARAM_VIEW - MODE_PARAMS, - #endif - MODE_CONSOLE, - #endif - #ifdef OOPSY_MULTI_APP - MODE_MENU, - #endif - MODE_COUNT - } Mode; - - - - struct GenDaisy { - - Daisy hardware; - #ifdef OOPSY_TARGET_PATCH_SM - Daisy *sub_board = &hardware; - #else - daisy::DaisySeed *sub_board = &hardware.seed; - #endif - AppDef * appdefs = nullptr; - - int mode, screensave=0; - int app_count = 1, app_selected = 0, app_selecting = 0, app_load_scheduled = 0; - int /*menu_button_held = 0, */menu_button_released = 0, menu_button_held_ms = 0, menu_button_incr = 0; - int is_mode_selecting = 0; - int param_count = 0; - #ifdef OOPSY_HAS_PARAM_VIEW - int param_selected = 0, param_is_tweaking = 0, param_scroll = 0; - #endif - - uint32_t t = 0, dt = 10, blockcount = 0; - Timer uitimer; - - // percent (0-100) of available processing time used - float audioCpuUsage = 0; - - void (*mainloopCallback)(uint32_t t, uint32_t dt); - void (*displayCallback)(uint32_t t, uint32_t dt); - #ifdef OOPSY_HAS_PARAM_VIEW - void (*paramCallback)(int idx, char * label, int len, bool tweak); - #endif - void * app = nullptr; - void * gen = nullptr; - bool nullAudioCallbackRunning = false; - - #ifdef OOPSY_TARGET_HAS_OLED - - enum { - SCOPESTYLE_OVERLAY = 0, - SCOPESTYLE_TOPBOTTOM, - SCOPESTYLE_LEFTRIGHT, - SCOPESTYLE_LISSAJOUS, - SCOPESTYLE_COUNT - } ScopeStyles; - - enum { - SCOPEOPTION_STYLE = 0, - SCOPEOPTION_SOURCE, - SCOPEOPTION_ZOOM, - SCOPEOPTION_COUNT - } ScopeOptions; - - FontDef& font = Font_6x8; - uint_fast8_t scope_zoom = 7; - uint_fast8_t scope_step = 0; - uint_fast8_t scope_option = 0, scope_style = SCOPESTYLE_TOPBOTTOM, scope_source = OOPSY_IO_COUNT/2; - uint16_t console_cols, console_rows, console_line; - char * console_stats; - char * console_memory; - char ** console_lines; - float scope_data[OOPSY_OLED_DISPLAY_WIDTH*2][2]; // 128 pixels - char scope_label[11]; - #endif // OOPSY_TARGET_HAS_OLED - - #ifdef OOPSY_TARGET_USES_MIDI_UART - - struct MidiNote { - uint8_t chan, pitch, vel, press; - - void init() { - chan = 0; - pitch = 36; - vel = press = 0; - } - - // call at block rate - // (so long as at least velocity out is defined) - void update(GenDaisy& daisy, uint8_t v, uint8_t p=36, uint8_t c=0) { - // a change of pitch or chan must stop an ongoing note - if (vel && (p != pitch || c != chan)) { - // send note off to stop old note - vel = 0; - daisy.midi_message3(144 + chan, pitch, vel); - } - pitch = p; - chan = c; - // a change of velocity between zero and nonzero should trigger a note on/off - if ((!v) != (!vel)) { - daisy.midi_message3(144 + chan, pitch, v); - } - vel = v; - } - - // call in the throttled section (if a pressure output was defined) - void update_pressure(GenDaisy& daisy, uint8_t pressure) { - if (vel && pressure != press) { - // send pressure - daisy.midi_message3(160 + chan, pitch, pressure); - } - press = pressure; - } - }; - - struct { - uint8_t status=0; - uint8_t lastbyte=0; - uint8_t byte[2]; - } midi; - - daisy::UartHandler uart; - uint32_t midi_out_writeidx = 0; - uint32_t midi_out_readidx = 0; - - uint8_t midi_in_written = 0;//, midi_out_written = 0; - uint8_t midi_in_active = 0, midi_out_active = 0; - uint8_t midi_out_data[OOPSY_MIDI_BUFFER_SIZE]; - float midi_in_data[OOPSY_BLOCK_SIZE]; - int midi_data_idx = 0; - int midi_parse_state = 0; - #endif //OOPSY_TARGET_USES_MIDI_UART - - #ifdef OOPSY_TARGET_USES_SDMMC - struct WavFormatChunk { - uint32_t size; // 16 - uint16_t format; // 1=PCM - uint16_t chans; // e.g. 2 - uint32_t samplerate; // e.g. 44100 - uint32_t bytespersecond; // bytes per second, = sr * bitspersample * chans/8 - uint16_t bytesperframe; // bytes per frame, = bitspersample * chans/8 - uint16_t bitspersample; // e.g. 16 for 16-bit - }; - - // at minimum this should fit one frame of 4 bytes-per-sample x numchans - #define OOPSY_WAV_WORKSPACE_BYTES (256) - - daisy::SdmmcHandler handler; - daisy::FatFSInterface fsi; - - uint8_t workspace[OOPSY_WAV_WORKSPACE_BYTES]; - - void sdcard_init() { - daisy::SdmmcHandler::Config sdconfig; - sdconfig.Defaults(); // 4-bit, 50MHz - // sdconfig.clock_powersave = false; - // sdconfig.speed = daisy::SdmmcHandler::Speed::FAST; - sdconfig.width = daisy::SdmmcHandler::BusWidth::BITS_1; - handler.Init(sdconfig); - fsi.Init(daisy::FatFSInterface::Config::MEDIA_SD); - f_mount(&fsi.GetSDFileSystem(), fsi.GetSDPath(), 1); - } - - // TODO: resizing without wasting memory - int sdcard_load_wav(const char * filename, Data& gendata) { - float * buffer = gendata.mData; - size_t buffer_frames = gendata.dim; - size_t buffer_channels = gendata.channels; - size_t bytesread = 0; - WavFormatChunk format; - uint32_t header[3]; - uint32_t marker, frames, chunksize, frames_per_read, frames_to_read, frames_read, total_frames_to_read; - uint32_t buffer_index = 0; - size_t bytespersample; - if(f_open(&SDFile, filename, (FA_OPEN_EXISTING | FA_READ)) != FR_OK) { - log("no %s", filename); - return -1; - } - if (f_eof(&SDFile) - || f_read(&SDFile, (void *)&header, sizeof(header), &bytesread) != FR_OK - || header[0] != daisy::kWavFileChunkId - || header[2] != daisy::kWavFileWaveId) goto badwav; - // find the format chunk: - do { - if (f_eof(&SDFile) || f_read(&SDFile, (void *)&marker, sizeof(marker), &bytesread) != FR_OK) break; - } while (marker != daisy::kWavFileSubChunk1Id); - if (f_eof(&SDFile) - || f_read(&SDFile, (void *)&format, sizeof(format), &bytesread) != FR_OK - || format.chans == 0 - || format.samplerate == 0 - || format.bitspersample == 0) goto badwav; - // find the data chunk: - do { - if (f_eof(&SDFile) || f_read(&SDFile, (void *)&marker, sizeof(marker), &bytesread) != FR_OK) break; - } while (marker != daisy::kWavFileSubChunk2Id); - bytespersample = format.bytesperframe / format.chans; - if (f_eof(&SDFile) - || f_read(&SDFile, (void *)&chunksize, sizeof(chunksize), &bytesread) != FR_OK - || format.format != 1 - || bytespersample < 2 - || bytespersample > 4) goto badwav; // only 16/24/32-bit PCM, sorry - // make sure we read in (multiples of) whole frames - frames = chunksize / format.bytesperframe; - frames_per_read = OOPSY_WAV_WORKSPACE_BYTES / format.bytesperframe; - frames_to_read = frames_per_read; - total_frames_to_read = buffer_frames; - // log("b=%u c=%u t=%u", buffer_frames, buffer_channels, total_frames_to_read); - // log("f=%u c=%u p=%u", frames, format.chans, frames_per_read); - // log("bp=%u c=%u p=%u", frames_to_read * format.bytesperframe); - do { - if (frames_to_read > total_frames_to_read) frames_to_read = total_frames_to_read; - f_read(&SDFile, workspace, frames_to_read * format.bytesperframe, &bytesread); - frames_read = bytesread / format.bytesperframe; - //log("_r=%u t=%u", frames_read, frames_to_read); - switch (bytespersample) { - case 2: { // 16 bit - for (size_t f=0; f> 8; - buffer[(buffer_index+f)*buffer_channels + c] = (float)(((double)b) * 0.00000011920928955078125); - } - } - } break; - case 4: { // 32 bit - for (size_t f=0; f 0 && total_frames_to_read > 0); - f_close(&SDFile); - log("read %s", filename); - return buffer_index; - badwav: - f_close(&SDFile); - log("bad %s", filename); - return -1; - } - #endif - - template - void reset(A& newapp) { - // first, remove callbacks: - mainloopCallback = nullMainloopCallback; - displayCallback = nullMainloopCallback; - nullAudioCallbackRunning = false; - sub_board->ChangeAudioCallback(nullAudioCallback); - while (!nullAudioCallbackRunning) daisy::System::Delay(1); - // reset memory - oopsy::init(); - // install new app: - app = &newapp; - newapp.init(*this); - // install new callbacks: - mainloopCallback = newapp.staticMainloopCallback; - displayCallback = newapp.staticDisplayCallback; - #if defined(OOPSY_TARGET_HAS_OLED) && defined(OOPSY_HAS_PARAM_VIEW) - paramCallback = newapp.staticParamCallback; - #endif - - sub_board->ChangeAudioCallback(newapp.staticAudioCallback); - log("gen~ %s", appdefs[app_selected].name); - log("SR %dkHz / %dHz", (int)(sub_board->AudioSampleRate()/1000), (int)sub_board->AudioCallbackRate()); - { - log("%d%s/%dKB+%d%s/%dMB", - oopsy::sram_used > 1024 ? oopsy::sram_used/1024 : oopsy::sram_used, - (oopsy::sram_used > 1024 || oopsy::sram_used == 0) ? "" : "B", - OOPSY_SRAM_SIZE/1024, - oopsy::sdram_used > 1048576 ? oopsy::sdram_used/1048576 : oopsy::sdram_used/1024, - (oopsy::sdram_used > 1048576 || oopsy::sdram_used == 0) ? "" : "KB", - OOPSY_SDRAM_SIZE/1048576); - // console_display(); - // hardware.display.Update(); - } - - // reset some state: - menu_button_incr = 0; - #if defined(OOPSY_TARGET_SEED) - hardware.menu_rotate = 0; - #endif - #ifdef OOPSY_TARGET_USES_MIDI_UART - midi_out_writeidx = 0; - midi_out_readidx = 0; - midi_data_idx = 0; - midi_in_written = 0;//, midi_out_written = 0; - midi_in_active = 0, midi_out_active = 0; - // reset: - midi_message1(255); - midi_message3(176, 123, 0); - #endif - blockcount = 0; - } - - #ifdef OOPSY_USE_USB_SERIAL_INPUT - static void UsbCallback(uint8_t* buf, uint32_t* len) { - memcpy(sumbuff, buf, *len); - rx_size = *len; - update = true; - } - #endif - - int run(AppDef * appdefs, int count) { - this->appdefs = appdefs; - app_count = count; - mode = 0; - - #ifdef OOPSY_USE_USB_SERIAL_INPUT - sub_board->usb.Init(daisy::UsbHandle::FS_INTERNAL); - daisy::System::Delay(500); - sub_board->usb.SetReceiveCallback(UsbCallback, daisy::UsbHandle::FS_INTERNAL); - #endif - - #ifdef OOPSY_USE_LOGGING - daisy::Logger::StartLog(false); - - //usbhandle SetReceiveCallback(ReceiveCallback cb, UsbPeriph dev); - - // TODO REMOVE THIS HACK WHEN STARTING SERIAL OVER USB DOESN'T FREAK OUT WITH AUDIO CALLBACK - daisy::System::Delay(275); - #endif - - #ifdef OOPSY_TARGET_HAS_OLED - console_cols = OOPSY_OLED_DISPLAY_WIDTH / font.FontWidth + 1; // +1 to accommodate null terminators. - console_rows = OOPSY_OLED_DISPLAY_HEIGHT / font.FontHeight; - console_memory = (char *)calloc(console_cols, console_rows); - console_stats = (char *)calloc(console_cols, 1); - for (int i=0; iadc.Start(); - sub_board->StartAudio(nullAudioCallback); - mainloopCallback = nullMainloopCallback; - displayCallback = nullMainloopCallback; - - #ifdef OOPSY_TARGET_USES_SDMMC - sdcard_init(); - #endif - - #ifdef OOPSY_TARGET_USES_MIDI_UART - midi_out_writeidx = 0; - midi_out_readidx = 0; - midi_data_idx = 0; - midi_in_written = 0;//, midi_out_written = 0; - midi_in_active = 0, midi_out_active = 0; - daisy::UartHandler::Config config; - config.baudrate = 31250; - config.periph = daisy::UartHandler::Config::Peripheral::USART_1; - config.stopbits = daisy::UartHandler::Config::StopBits::BITS_1; - config.parity = daisy::UartHandler::Config::Parity::NONE; - config.mode = daisy::UartHandler::Config::Mode::TX_RX; - config.wordlength = daisy::UartHandler::Config::WordLength::BITS_8; - config.pin_config.rx = {DSY_GPIOB, 7}; - config.pin_config.tx = {DSY_GPIOB, 6}; - uart.Init(config); - uart.StartRx(); - #endif - - app_selected = 0; - appdefs[app_selected].load(); - - #ifdef OOPSY_TARGET_HAS_OLED - console_display(); - #endif - - while(1) { - uint32_t t1 = daisy::System::GetNow(); - dt = t1-t; - t = t1; - - // pulse seed LED for status according to CPU usage: - sub_board->SetLed((t % 1000)/10 <= uint32_t(audioCpuUsage)); - - if (app_load_scheduled) { - app_load_scheduled = 0; - appdefs[app_selected].load(); - continue; - } - - // handle app-level code (e.g. for CV/gate outs) - mainloopCallback(t, dt); - #ifdef OOPSY_TARGET_USES_MIDI_UART - // send data if there's something to read: - if (midi_out_readidx != midi_out_writeidx) { - uint32_t size = (OOPSY_MIDI_BUFFER_SIZE + midi_out_writeidx - midi_out_readidx) % OOPSY_MIDI_BUFFER_SIZE; - size = ((midi_out_readidx + size) <= OOPSY_MIDI_BUFFER_SIZE) ? size : OOPSY_MIDI_BUFFER_SIZE - midi_out_readidx; - //for (uint32_t i=0; iPrintLine("the time is"FLT_FMT3"", FLT_VAR3(t/1000.f)); - #endif - #ifdef OOPSY_USE_USB_SERIAL_INPUT - if(update && rx_size > 0) { - // TODO check bytes for a reset message and jump to bootloader - update = false; - log(sumbuff); - } - #endif - - // CLEAR DISPLAY - #ifdef OOPSY_TARGET_HAS_OLED - hardware.display.Fill(false); - #endif - #ifdef OOPSY_TARGET_PETAL - hardware.ClearLeds(); - #endif - - if (menu_button_held_ms > OOPSY_LONG_PRESS_MS) { - is_mode_selecting = 1; - } - #ifdef OOPSY_TARGET_PETAL - // has no mode selection - is_mode_selecting = 0; - #if defined(OOPSY_MULTI_APP) - // multi-app is always in menu mode: - mode = MODE_MENU; - #endif - for(int i = 0; i < 8; i++) { - float white = (i == app_selecting || menu_button_released); - hardware.SetRingLed((daisy::DaisyPetal::RingLed)i, - (i == app_selected || white) * 1.f, - white * 1.f, - (i < app_count) * 0.3f + white * 1.f - ); - } - #endif //OOPSY_TARGET_PETAL - - #ifdef OOPSY_TARGET_VERSIO - // has no mode selection - is_mode_selecting = 0; - #if defined(OOPSY_MULTI_APP) - // multi-app is always in menu mode: - mode = MODE_MENU; - #endif - for(int i = 0; i < 4; i++) { - float white = (i == app_selecting || menu_button_released); - hardware.SetLed(i, - (i == app_selected || white) * 1.f, - white * 1.f, - (i < app_count) * 0.3f + white * 1.f - ); - } - #endif //OOPSY_TARGET_VERSIO - - // Handle encoder increment actions: - if (is_mode_selecting) { - mode += menu_button_incr; - #ifdef OOPSY_TARGET_FIELD - // mode menu rotates infinitely - if (mode >= MODE_COUNT) mode = 0; - if (mode < 0) mode = MODE_COUNT-1; - #else - // mode menu clamps at either end - if (mode >= MODE_COUNT) mode = MODE_COUNT-1; - if (mode < 0) mode = 0; - #endif - #ifdef OOPSY_MULTI_APP - } else if (mode == MODE_MENU) { - #ifdef OOPSY_TARGET_VERSIO - app_selecting = menu_button_incr; - #else - app_selecting += menu_button_incr; - #endif - if (app_selecting >= app_count) app_selecting -= app_count; - if (app_selecting < 0) app_selecting += app_count; - #endif // OOPSY_MULTI_APP - #ifdef OOPSY_TARGET_HAS_OLED - } else if (mode == MODE_SCOPE) { - switch (scope_option) { - case SCOPEOPTION_STYLE: { - scope_style = (scope_style + menu_button_incr) % SCOPESTYLE_COUNT; - } break; - case SCOPEOPTION_SOURCE: { - scope_source = (scope_source + menu_button_incr) % (OOPSY_IO_COUNT*2); - } break; - case SCOPEOPTION_ZOOM: { - scope_zoom = (scope_zoom + menu_button_incr) % OOPSY_SCOPE_MAX_ZOOM; - } break; - } - #ifdef OOPSY_HAS_PARAM_VIEW - } else if (mode == MODE_PARAMS) { - if (!param_is_tweaking) { - param_selected += menu_button_incr; - if (param_selected >= param_count) param_selected = 0; - if (param_selected < 0) param_selected = param_count-1; - } - #endif //OOPSY_HAS_PARAM_VIEW - #endif //OOPSY_TARGET_HAS_OLED - } - - // SHORT PRESS - if (menu_button_released) { - menu_button_released = 0; - if (is_mode_selecting) { - is_mode_selecting = 0; - #ifdef OOPSY_MULTI_APP - } else if (mode == MODE_MENU) { - if (app_selected != app_selecting) { - app_selected = app_selecting; - #ifndef OOPSY_TARGET_HAS_OLED - mode = 0; - #endif - schedule_app_load(app_selected); //appdefs[app_selected].load(); - //continue; - } - #endif - #ifdef OOPSY_TARGET_HAS_OLED - } else if (mode == MODE_SCOPE) { - scope_option = (scope_option + 1) % SCOPEOPTION_COUNT; - #if defined (OOPSY_HAS_PARAM_VIEW) && defined(OOPSY_CAN_PARAM_TWEAK) - } else if (mode == MODE_PARAMS) { - param_is_tweaking = !param_is_tweaking; - #endif //OOPSY_HAS_PARAM_VIEW && OOPSY_CAN_PARAM_TWEAK - #endif //OOPSY_TARGET_HAS_OLED - } - } - - // OLED DISPLAY: - #ifdef OOPSY_TARGET_HAS_OLED - int showstats = 0; - switch(mode) { - #ifdef OOPSY_MULTI_APP - case MODE_MENU: { - showstats = 1; - for (int i=0; i", font, true); - } - if (i < app_count) { - hardware.display.SetCursor(font.FontWidth, font.FontHeight * i); - hardware.display.WriteString((char *)appdefs[i].name, font, i != app_selected); - } - } - } break; - #endif //OOPSY_MULTI_APP - #ifdef OOPSY_HAS_PARAM_VIEW - case MODE_PARAMS: { - char label[console_cols+1]; - // ensure selected parameter is on-screen: - if (param_scroll > param_selected) param_scroll = param_selected; - if (param_scroll < (param_selected - console_rows + 1)) param_scroll = (param_selected - console_rows + 1); - int idx = param_scroll; // offset this for screen-scroll - for (int line=0; lineAudioSampleRate()); - int offset = snprintf(scope_label, console_cols, "%dx %dms", zoomlevel, (int)ceilf(scope_duration)); - hardware.display.SetCursor(0, h - font.FontHeight); - hardware.display.WriteString(scope_label, font, true); - } break; - // for view style, just leave it blank :-) - } - } break; - case MODE_CONSOLE: - { - showstats = 1; - console_display(); - break; - } - default: { - } - } - if (is_mode_selecting) { - hardware.display.DrawRect(0, 0, OOPSY_OLED_DISPLAY_WIDTH-1, OOPSY_OLED_DISPLAY_HEIGHT-1, 1); - } - if (showstats) { - int offset = 0; - #ifdef OOPSY_TARGET_USES_MIDI_UART - offset += snprintf(console_stats+offset, console_cols-offset, "%c%c", midi_in_active ? '<' : ' ', midi_out_active ? '>' : ' '); - midi_in_active = midi_out_active = 0; - #endif - offset += snprintf(console_stats+offset, console_cols-offset, "%02d%%", int(audioCpuUsage)); - // stats: - hardware.display.SetCursor(OOPSY_OLED_DISPLAY_WIDTH - (offset) * font.FontWidth, font.FontHeight * 0); - hardware.display.WriteString(console_stats, font, true); - } - #endif //OOPSY_TARGET_HAS_OLED - menu_button_incr = 0; - - // handle app-level code (e.g. for LED etc.) - displayCallback(t, dt); - - #ifdef OOPSY_TARGET_HAS_OLED - hardware.display.Update(); - #endif //OOPSY_TARGET_HAS_OLED - - #if (OOPSY_TARGET_PETAL) - hardware.UpdateLeds(); - #endif //(OOPSY_TARGET_PETAL) - } // uitimer.ready - - } - return 0; - } - - void schedule_app_load(int which) { - app_selected = app_selecting = which % app_count; - app_load_scheduled = 1; - } - - void audio_preperform(size_t size) { - #ifdef OOPSY_TARGET_USES_MIDI_UART - // fill remainder of midi buffer with non-data: - for (size_t i=midi_in_written; iPressed(); - menu_button_incr += hardware.GetSwitch(1)->FallingEdge(); - menu_button_held_ms = hardware.GetSwitch(0)->TimeHeldMs(); - if (hardware.GetSwitch(0)->FallingEdge()) menu_button_released = 1; - #elif defined(OOPSY_TARGET_VERSIO) - // menu_button_held = hardware.tap.Pressed(); - // menu_button_incr += hardware.GetKnobValue(6) * app_count; - // menu_button_held_ms = hardware.tap.TimeHeldMs(); - // if (hardware.tap_.FallingEdge()) menu_button_released = 1; - #elif defined(OOPSY_TARGET_POD) || defined(OOPSY_TARGET_PETAL) || defined(OOPSY_TARGET_PATCH) - //menu_button_held = hardware.encoder.Pressed(); - menu_button_incr += hardware.encoder.Increment(); - menu_button_held_ms = hardware.encoder.TimeHeldMs(); - if (hardware.encoder.FallingEdge()) menu_button_released = 1; - #endif - } - - void audio_postperform(float **buffers, size_t size) { - #ifdef OOPSY_TARGET_HAS_OLED - if (mode == MODE_SCOPE) { - // selector for scope storage source: - // e.g. for OOPSY_IO_COUNT=4, inputs:outputs as 0123:4567 makes: - // 01, 23, 45, 67 2n:2n+1 i1i2 i3i4 o1o2 o3o4 - // 04, 15, 26, 37 n:n+ch i1o1 i2o2 i3o3 i4o4 - int n = scope_source % OOPSY_IO_COUNT; - float * buf0 = (scope_source < OOPSY_IO_COUNT) ? buffers[2*n ] : buffers[n ]; - float * buf1 = (scope_source < OOPSY_IO_COUNT) ? buffers[2*n+1] : buffers[n+OOPSY_IO_COUNT]; - - // float * buf0 = scope_source ? buffers[0] : buffers[2]; - // float * buf1 = scope_source ? buffers[1] : buffers[3]; - size_t samples = scope_samples(); - if (samples > size) samples=size; - - for (size_t i=0; i pt0 ? pt0 : min0; - max0 = max0 < pt0 ? pt0 : max0; - min1 = min1 > pt1 ? pt1 : min1; - max1 = max1 < pt1 ? pt1 : max1; - } - scope_data[scope_step][0] = (min0); - scope_data[scope_step][1] = (min1); - scope_step++; - scope_data[scope_step][0] = (max0); - scope_data[scope_step][1] = (max1); - scope_step++; - if (scope_step >= OOPSY_OLED_DISPLAY_WIDTH*2) scope_step = 0; - } - } - #endif - blockcount++; - } - - #ifdef OOPSY_TARGET_HAS_OLED - inline int scope_samples() { - // valid zoom sizes: 1, 2, 3, 4, 6, 8, 12, 16, 24 - switch(scope_zoom) { - case 1: case 2: case 3: case 4: return scope_zoom; break; - case 5: return 6; break; - case 6: return 12; break; - case 7: return 16; break; - default: return 24; break; - } - } - - GenDaisy& console_display() { - for (int i=0; i= 0 && i < size && w1 != midi_out_readidx) { - // scale (0.0, 1.0) back to (0, 255) for MIDI bytes - midi_out_data[midi_out_writeidx] = byte; - midi_out_writeidx = w1; - w1 = (midi_out_writeidx+1) % OOPSY_MIDI_BUFFER_SIZE; - i++; - byte = buf[i] * 256.0f; - } - // for (size_t i=0; i= 0.f) { - // // scale (0.0, 1.0) back to (0, 255) for MIDI bytes - // midi_out_data[midi_out_written] = buf[i] * 256.0f; - // midi_out_written++; - // } - // } - } - - void midi_message1(uint8_t byte) { - uint32_t r = (midi_out_readidx + OOPSY_MIDI_BUFFER_SIZE - midi_out_writeidx) % OOPSY_MIDI_BUFFER_SIZE; - if (r > 0 && r <= 1) { log("midi buffer full"); return; } - uint32_t i0 = midi_out_writeidx; - uint32_t i1 = (midi_out_writeidx+1) % OOPSY_MIDI_BUFFER_SIZE; - midi_out_data[i0] = byte; - midi_out_writeidx = i1; - } - - void midi_message2(uint8_t status, uint8_t b1) { - uint32_t r = (midi_out_readidx + OOPSY_MIDI_BUFFER_SIZE - midi_out_writeidx) % OOPSY_MIDI_BUFFER_SIZE; - if (r > 0 && r <= 2) { log("midi buffer full"); return; } - uint32_t i0 = midi_out_writeidx; - uint32_t i1 = (midi_out_writeidx+1) % OOPSY_MIDI_BUFFER_SIZE; - uint32_t i2 = (midi_out_writeidx+2) % OOPSY_MIDI_BUFFER_SIZE; - midi_out_data[i0] = status; - midi_out_data[i1] = b1; - midi_out_writeidx = i2; - } - - void midi_message3(uint8_t status, uint8_t b1, uint8_t b2) { - uint32_t r = (midi_out_readidx + OOPSY_MIDI_BUFFER_SIZE - midi_out_writeidx) % OOPSY_MIDI_BUFFER_SIZE; - if (r > 0 && r <= 3) { log("midi buffer full"); return; } - uint32_t i0 = midi_out_writeidx; - uint32_t i1 = (midi_out_writeidx+1) % OOPSY_MIDI_BUFFER_SIZE; - uint32_t i2 = (midi_out_writeidx+2) % OOPSY_MIDI_BUFFER_SIZE; - uint32_t i3 = (midi_out_writeidx+3) % OOPSY_MIDI_BUFFER_SIZE; - midi_out_data[i0] = status; - midi_out_data[i1] = b1; - midi_out_data[i2] = b2; - midi_out_writeidx = i3; - } - - void midi_nullData(Data& data) { - for (int i=0; i= 0. && midi_out_writeidx != midi_out_readidx) { - // erase it from [data midi] - data.write(-1, midi_data_idx, 0); - // write it to our active outbuffer: - midi_out_data[midi_out_writeidx] = b; - midi_out_writeidx = (midi_out_writeidx+1) % OOPSY_MIDI_BUFFER_SIZE; - // and advance one index in the [data midi] - midi_data_idx++; if (midi_data_idx >= data.dim) midi_data_idx = 0; - b = data.read(midi_data_idx, 0); - } - } - #endif //OOPSY_TARGET_USES_MIDI_UART - - #if (OOPSY_TARGET_FIELD) - void setFieldLedsFromData(Data& data) { - for(long i = 0; i < daisy::DaisyField::LED_LAST && i < data.dim; i++) { - // LED indices run A1..8, B8..1, Knob1..8 - // switch here to re-order the B8-1 to B1-8 - long idx=i; - if (idx > 7 && idx < 16) idx = 23-i; - hardware.led_driver.SetLed(idx, data.read(i, 0)); - } - hardware.led_driver.SwapBuffersAndTransmit(); - }; - #endif - - static void nullAudioCallback(daisy::AudioHandle::InputBuffer ins, daisy::AudioHandle::OutputBuffer outs, size_t size); - - static void nullMainloopCallback(uint32_t t, uint32_t dt) {} - } daisy; - - void GenDaisy::nullAudioCallback(daisy::AudioHandle::InputBuffer ins, daisy::AudioHandle::OutputBuffer outs, size_t size) { - daisy.nullAudioCallbackRunning = true; - // zero audio outs: - for (int i=0; i - struct App { - - static void staticMainloopCallback(uint32_t t, uint32_t dt) { - T& self = *(T *)daisy.app; - self.mainloopCallback(daisy, t, dt); - } - - static void staticDisplayCallback(uint32_t t, uint32_t dt) { - T& self = *(T *)daisy.app; - self.displayCallback(daisy, t, dt); - } - - static void staticAudioCallback(daisy::AudioHandle::InputBuffer hardware_ins, daisy::AudioHandle::OutputBuffer hardware_outs, size_t size) { - uint32_t start = daisy::System::GetUs(); - daisy.audio_preperform(size); - ((T *)daisy.app)->audioCallback(daisy, hardware_ins, hardware_outs, size); - #if (OOPSY_IO_COUNT == 4) - float * buffers[] = { - (float *)hardware_ins[0], (float *)hardware_ins[1], (float *)hardware_ins[2], (float *)hardware_ins[3], - hardware_outs[0], hardware_outs[1], hardware_outs[2], hardware_outs[3]}; - #else - float * buffers[] = {(float *)hardware_ins[0], (float *)hardware_ins[1], hardware_outs[0], hardware_outs[1]}; - #endif - daisy.audio_postperform(buffers, size); - // convert elapsed time (us) to CPU percentage (0-100) of available processing time - // 100 (%) * (0.000001 * used_us) * callbackrateHz - float percent = (daisy::System::GetUs() - start)*0.0001f*daisy.sub_board->AudioCallbackRate(); - percent = percent > 100.f ? 100.f : percent; - // with a falling-only slew to capture spikes, since we care most about worst-case performance - daisy.audioCpuUsage = (percent > daisy.audioCpuUsage) ? percent - : daisy.audioCpuUsage + 0.02f*(percent - daisy.audioCpuUsage); - } - - #if defined(OOPSY_TARGET_HAS_OLED) && defined(OOPSY_HAS_PARAM_VIEW) - static void staticParamCallback(int idx, char * label, int len, bool tweak) { - T& self = *(T *)daisy.app; - self.paramCallback(daisy, idx, label, len, tweak); - } - #endif //defined(OOPSY_TARGET_HAS_OLED) && defined(OOPSY_HAS_PARAM_VIEW) - }; - -}; // oopsy:: +uint32_t sram_used = 0, sram_usable = 0; +uint32_t sdram_used = 0, sdram_usable = 0; +char *sram_pool = nullptr; +char DSY_SDRAM_BSS sdram_pool[OOPSY_SDRAM_SIZE]; + +void init() { + if (!sram_pool) + sram_pool = (char *)malloc(OOPSY_SRAM_SIZE); + // There's no guarantee the allocation will actually be + // of size "OOPSY_SRAM_SIZE," so this just clamps the + // usable space to what it really is. + sram_usable = (0x24080000 - 1024) - ((size_t)sram_pool); + sram_used = 0; + sdram_usable = OOPSY_SDRAM_SIZE; + sdram_used = 0; +} + +void *allocate(uint32_t size) { + if (size < sram_usable) { + void *p = sram_pool + sram_used; + sram_used += size; + sram_usable -= size; + return p; + } else if (size < sdram_usable) { + void *p = sdram_pool + sdram_used; + sdram_used += size; + sdram_usable -= size; + return p; + } + return nullptr; +} + +void memset(void *p, int c, long size) { + char *p2 = (char *)p; + int i; + for (i = 0; i < size; i++, p2++) + *p2 = char(c); +} + +// void genlib_memcpy(void *dst, const void *src, long size) { +// char *s2 = (char *)src; +// char *d2 = (char *)dst; +// int i; +// for (i = 0; i < size; i++, s2++, d2++) +// *d2 = *s2; +// } + +// void test() { +// // memory test: +// size_t allocated = 0; +// size_t sz = 256; +// int i; +// while (sz < 515) { +// sz++; +// void * m = malloc(sz * 1024); +// if (!m) break; +// free(m); +// log("%d: malloced %dk", i, sz); +// i++; +// } +// log("all OK"); +// } + +struct Timer { + int32_t period = OOPSY_DISPLAY_PERIOD_MS, t = OOPSY_DISPLAY_PERIOD_MS; + + bool ready(int32_t dt) { + t += dt; + if (t > period) { + t = 0; + return true; + } + return false; + } +}; + +struct AppDef { + const char *name; + void (*load)(); +}; +typedef enum { +#ifdef OOPSY_TARGET_HAS_OLED + MODE_SCOPE, +#ifdef OOPSY_HAS_PARAM_VIEW + MODE_PARAMS, +#endif + MODE_CONSOLE, +#endif +#ifdef OOPSY_MULTI_APP + MODE_MENU, +#endif + MODE_COUNT +} Mode; + +struct GenDaisy { + + Daisy hardware; +#ifdef OOPSY_SOM_PETAL_SM + daisy::Petal125BSM *som = &hardware.som; +#else +#ifdef OOPSY_SOM_PATCH_SM + daisy::patch_sm::DaisyPatchSM *som = &hardware.som; +#else +#ifdef OOPSY_OLD_JSON + daisy::DaisySeed *som = &hardware.seed; +#else + daisy::DaisySeed *som = &hardware.som; +#endif +#endif +#endif + AppDef *appdefs = nullptr; + + int mode, screensave = 0; + int app_count = 1, app_selected = 0, app_selecting = 0, + app_load_scheduled = 0; + int /*menu_button_held = 0, */ menu_button_released = 0, + menu_button_held_ms = 0, menu_button_incr = 0; + int is_mode_selecting = 0; + int param_count = 0; +#ifdef OOPSY_HAS_PARAM_VIEW + int param_selected = 0, param_is_tweaking = 0, param_scroll = 0; +#endif + + uint32_t t = 0, dt = 10, blockcount = 0; + Timer uitimer; + + // percent (0-100) of available processing time used + float audioCpuUsage = 0; + + void (*mainloopCallback)(uint32_t t, uint32_t dt); + void (*displayCallback)(uint32_t t, uint32_t dt); +#ifdef OOPSY_HAS_PARAM_VIEW + void (*paramCallback)(int idx, char *label, int len, bool tweak); +#endif + void *app = nullptr; + void *gen = nullptr; + bool nullAudioCallbackRunning = false; + +#ifdef OOPSY_TARGET_HAS_OLED + + enum { + SCOPESTYLE_OVERLAY = 0, + SCOPESTYLE_TOPBOTTOM, + SCOPESTYLE_LEFTRIGHT, + SCOPESTYLE_LISSAJOUS, + SCOPESTYLE_COUNT + } ScopeStyles; + + enum { + SCOPEOPTION_STYLE = 0, + SCOPEOPTION_SOURCE, + SCOPEOPTION_ZOOM, + SCOPEOPTION_COUNT + } ScopeOptions; + + FontDef &font = Font_6x8; + uint_fast8_t scope_zoom = 7; + uint_fast8_t scope_step = 0; + uint_fast8_t scope_option = 0, scope_style = SCOPESTYLE_TOPBOTTOM, + scope_source = OOPSY_IO_COUNT / 2; + uint16_t console_cols, console_rows, console_line; + char *console_stats; + char *console_memory; + char **console_lines; + float scope_data[OOPSY_OLED_DISPLAY_WIDTH * 2][2]; // 128 pixels + char scope_label[11]; +#endif // OOPSY_TARGET_HAS_OLED + +#ifdef OOPSY_TARGET_USES_MIDI_UART + + struct MidiNote { + uint8_t chan, pitch, vel, press; + + void init() { + chan = 0; + pitch = 36; + vel = press = 0; + } + + // call at block rate + // (so long as at least velocity out is defined) + void update(GenDaisy &daisy, uint8_t v, uint8_t p = 36, uint8_t c = 0) { + // a change of pitch or chan must stop an ongoing note + if (vel && (p != pitch || c != chan)) { + // send note off to stop old note + vel = 0; + daisy.midi_message3(144 + chan, pitch, vel); + } + pitch = p; + chan = c; + // a change of velocity between zero and nonzero should trigger a note + // on/off + if ((!v) != (!vel)) { + daisy.midi_message3(144 + chan, pitch, v); + } + vel = v; + } + + // call in the throttled section (if a pressure output was defined) + void update_pressure(GenDaisy &daisy, uint8_t pressure) { + if (vel && pressure != press) { + // send pressure + daisy.midi_message3(160 + chan, pitch, pressure); + } + press = pressure; + } + }; + + struct { + uint8_t status = 0; + uint8_t lastbyte = 0; + uint8_t byte[2]; + } midi; + + daisy::UartHandler uart; + uint32_t midi_out_writeidx = 0; + uint32_t midi_out_readidx = 0; + + uint8_t midi_in_written = 0; //, midi_out_written = 0; + uint8_t midi_in_active = 0, midi_out_active = 0; + uint8_t midi_out_data[OOPSY_MIDI_BUFFER_SIZE]; + float midi_in_data[OOPSY_BLOCK_SIZE]; + + // MIDI RX buffer for DMA listen mode (libdaisy v8.0.0) + static uint8_t midi_rx_buffer[256]; + static uint32_t midi_rx_write_idx; + static uint32_t midi_rx_read_idx; + static void MidiRxCallback(uint8_t *data, size_t size, void *context, + daisy::UartHandler::Result result); + int midi_data_idx = 0; + int midi_parse_state = 0; +#endif // OOPSY_TARGET_USES_MIDI_UART + +#ifdef OOPSY_TARGET_USES_SDMMC + struct WavFormatChunk { + uint32_t size; // 16 + uint16_t format; // 1=PCM + uint16_t chans; // e.g. 2 + uint32_t samplerate; // e.g. 44100 + uint32_t bytespersecond; // bytes per second, = sr * bitspersample * chans/8 + uint16_t bytesperframe; // bytes per frame, = bitspersample * chans/8 + uint16_t bitspersample; // e.g. 16 for 16-bit + }; + +// at minimum this should fit one frame of 4 bytes-per-sample x numchans +#define OOPSY_WAV_WORKSPACE_BYTES (256) + + daisy::SdmmcHandler handler; + daisy::FatFSInterface fsi; + + uint8_t workspace[OOPSY_WAV_WORKSPACE_BYTES]; + + void sdcard_init() { + daisy::SdmmcHandler::Config sdconfig; + sdconfig.Defaults(); // 4-bit, 50MHz + // sdconfig.clock_powersave = false; + // sdconfig.speed = daisy::SdmmcHandler::Speed::FAST; + sdconfig.width = daisy::SdmmcHandler::BusWidth::BITS_1; + handler.Init(sdconfig); + fsi.Init(daisy::FatFSInterface::Config::MEDIA_SD); + f_mount(&fsi.GetSDFileSystem(), fsi.GetSDPath(), 1); + } + + // TODO: resizing without wasting memory + int sdcard_load_wav(const char *filename, Data &gendata) { + float *buffer = gendata.mData; + size_t buffer_frames = gendata.dim; + size_t buffer_channels = gendata.channels; + size_t bytesread = 0; + WavFormatChunk format; + uint32_t header[3]; + uint32_t marker, frames, chunksize, frames_per_read, frames_to_read, + frames_read, total_frames_to_read; + uint32_t buffer_index = 0; + size_t bytespersample; + if (f_open(&SDFile, filename, (FA_OPEN_EXISTING | FA_READ)) != FR_OK) { + log("no %s", filename); + return -1; + } + if (f_eof(&SDFile) || + f_read(&SDFile, (void *)&header, sizeof(header), &bytesread) != FR_OK || + header[0] != daisy::kWavFileChunkId || + header[2] != daisy::kWavFileWaveId) + goto badwav; + // find the format chunk: + do { + if (f_eof(&SDFile) || + f_read(&SDFile, (void *)&marker, sizeof(marker), &bytesread) != FR_OK) + break; + } while (marker != daisy::kWavFileSubChunk1Id); + if (f_eof(&SDFile) || + f_read(&SDFile, (void *)&format, sizeof(format), &bytesread) != FR_OK || + format.chans == 0 || format.samplerate == 0 || + format.bitspersample == 0) + goto badwav; + // find the data chunk: + do { + if (f_eof(&SDFile) || + f_read(&SDFile, (void *)&marker, sizeof(marker), &bytesread) != FR_OK) + break; + } while (marker != daisy::kWavFileSubChunk2Id); + bytespersample = format.bytesperframe / format.chans; + if (f_eof(&SDFile) || + f_read(&SDFile, (void *)&chunksize, sizeof(chunksize), &bytesread) != + FR_OK || + format.format != 1 || bytespersample < 2 || bytespersample > 4) + goto badwav; // only 16/24/32-bit PCM, sorry + // make sure we read in (multiples of) whole frames + frames = chunksize / format.bytesperframe; + frames_per_read = OOPSY_WAV_WORKSPACE_BYTES / format.bytesperframe; + frames_to_read = frames_per_read; + total_frames_to_read = buffer_frames; + // log("b=%u c=%u t=%u", buffer_frames, buffer_channels, + // total_frames_to_read); log("f=%u c=%u p=%u", frames, format.chans, + // frames_per_read); log("bp=%u c=%u p=%u", frames_to_read * + // format.bytesperframe); + do { + if (frames_to_read > total_frames_to_read) + frames_to_read = total_frames_to_read; + f_read(&SDFile, workspace, frames_to_read * format.bytesperframe, + &bytesread); + frames_read = bytesread / format.bytesperframe; + // log("_r=%u t=%u", frames_read, frames_to_read); + switch (bytespersample) { + case 2: { // 16 bit + for (size_t f = 0; f < frames_read; f++) { + for (size_t c = 0; c < buffer_channels; c++) { + uint8_t *frame = workspace + f * format.bytesperframe + + (c % format.chans) * bytespersample; + buffer[(buffer_index + f) * buffer_channels + c] = + ((int16_t *)frame)[0] * 0.000030517578125f; + } + } + } break; + case 3: { // 24 bit + for (size_t f = 0; f < frames_read; f++) { + for (size_t c = 0; c < buffer_channels; c++) { + uint8_t *frame = workspace + f * format.bytesperframe + + (c % format.chans) * bytespersample; + int32_t b = (int32_t)(((uint32_t)(frame[0]) << 8) | + ((uint32_t)(frame[1]) << 16) | + ((uint32_t)(frame[2]) << 24)) >> + 8; + buffer[(buffer_index + f) * buffer_channels + c] = + (float)(((double)b) * 0.00000011920928955078125); + } + } + } break; + case 4: { // 32 bit + for (size_t f = 0; f < frames_read; f++) { + for (size_t c = 0; c < buffer_channels; c++) { + uint8_t *frame = workspace + f * format.bytesperframe + + (c % format.chans) * bytespersample; + buffer[(buffer_index + f) * buffer_channels + c] = + ((int32_t *)frame)[0] / 2147483648.f; + } + } + } break; + } + total_frames_to_read -= frames_read; + buffer_index += frames_read; + } while (!f_eof(&SDFile) && bytesread > 0 && total_frames_to_read > 0); + f_close(&SDFile); + log("read %s", filename); + return buffer_index; + badwav: + f_close(&SDFile); + log("bad %s", filename); + return -1; + } +#endif + + template void reset(A &newapp) { + // first, remove callbacks: + mainloopCallback = nullMainloopCallback; + displayCallback = nullMainloopCallback; + nullAudioCallbackRunning = false; + som->ChangeAudioCallback(nullAudioCallback); + while (!nullAudioCallbackRunning) + daisy::System::Delay(1); + // reset memory + oopsy::init(); + // install new app: + app = &newapp; + newapp.init(*this); + // install new callbacks: + mainloopCallback = newapp.staticMainloopCallback; + displayCallback = newapp.staticDisplayCallback; +#if defined(OOPSY_TARGET_HAS_OLED) && defined(OOPSY_HAS_PARAM_VIEW) + paramCallback = newapp.staticParamCallback; +#endif + + som->ChangeAudioCallback(newapp.staticAudioCallback); + log("gen~ %s", appdefs[app_selected].name); + log("SR %dkHz / %dHz", (int)(som->AudioSampleRate() / 1000), + (int)som->AudioCallbackRate()); + { + log("%d%s/%dKB+%d%s/%dMB", + oopsy::sram_used > 1024 ? oopsy::sram_used / 1024 : oopsy::sram_used, + (oopsy::sram_used > 1024 || oopsy::sram_used == 0) ? "" : "B", + OOPSY_SRAM_SIZE / 1024, + oopsy::sdram_used > 1048576 ? oopsy::sdram_used / 1048576 + : oopsy::sdram_used / 1024, + (oopsy::sdram_used > 1048576 || oopsy::sdram_used == 0) ? "" : "KB", + OOPSY_SDRAM_SIZE / 1048576); + // console_display(); + // hardware.display.Update(); + } + + // reset some state: + menu_button_incr = 0; +#if defined(OOPSY_TARGET_SEED) + hardware.menu_rotate = 0; +#endif +#ifdef OOPSY_TARGET_USES_MIDI_UART + midi_out_writeidx = 0; + midi_out_readidx = 0; + midi_data_idx = 0; + midi_in_written = 0; //, midi_out_written = 0; + midi_in_active = 0, midi_out_active = 0; + // reset: + midi_message1(255); + midi_message3(176, 123, 0); +#endif + blockcount = 0; + } + +#ifdef OOPSY_USE_USB_SERIAL_INPUT + static void UsbCallback(uint8_t *buf, uint32_t *len) { + memcpy(sumbuff, buf, *len); + rx_size = *len; + update = true; + } +#endif + + int run(AppDef *appdefs, int count) { + this->appdefs = appdefs; + app_count = count; + mode = 0; + +#ifdef OOPSY_USE_USB_SERIAL_INPUT + som->usb.Init(daisy::UsbHandle::FS_INTERNAL); + daisy::System::Delay(500); + som->usb.SetReceiveCallback(UsbCallback, daisy::UsbHandle::FS_INTERNAL); +#endif + +#ifdef OOPSY_USE_LOGGING + daisy::Logger::StartLog(false); + + // usbhandle SetReceiveCallback(ReceiveCallback cb, UsbPeriph dev); + + // TODO REMOVE THIS HACK WHEN STARTING SERIAL OVER USB DOESN'T FREAK OUT + // WITH AUDIO CALLBACK + daisy::System::Delay(275); +#endif + +#ifdef OOPSY_TARGET_HAS_OLED + console_cols = OOPSY_OLED_DISPLAY_WIDTH / font.FontWidth + + 1; // +1 to accommodate null terminators. + console_rows = OOPSY_OLED_DISPLAY_HEIGHT / font.FontHeight; + console_memory = (char *)calloc(console_cols, console_rows); + console_stats = (char *)calloc(console_cols, 1); + for (int i = 0; i < console_rows; i++) { + console_lines[i] = &console_memory[i * console_cols]; + } + console_line = console_rows - 1; +#endif + + som->StartAudio(nullAudioCallback); + mainloopCallback = nullMainloopCallback; + displayCallback = nullMainloopCallback; + +#ifdef OOPSY_TARGET_USES_SDMMC + sdcard_init(); +#endif + +#ifdef OOPSY_TARGET_USES_MIDI_UART + midi_out_writeidx = 0; + midi_out_readidx = 0; + midi_data_idx = 0; + midi_in_written = 0; //, midi_out_written = 0; + midi_in_active = 0, midi_out_active = 0; + daisy::UartHandler::Config config; + config.baudrate = 31250; + config.periph = daisy::UartHandler::Config::Peripheral::USART_1; + config.stopbits = daisy::UartHandler::Config::StopBits::BITS_1; + config.parity = daisy::UartHandler::Config::Parity::NONE; + config.mode = daisy::UartHandler::Config::Mode::TX_RX; + config.wordlength = daisy::UartHandler::Config::WordLength::BITS_8; + config.pin_config.rx = daisy::Pin(daisy::PORTB, 7); + config.pin_config.tx = daisy::Pin(daisy::PORTB, 6); + uart.Init(config); + // Use DMA listen mode for MIDI reception in v8.0.0 + uart.DmaListenStart(midi_rx_buffer, 256, MidiRxCallback, this); +#endif + + app_selected = 0; + appdefs[app_selected].load(); + +#ifdef OOPSY_TARGET_HAS_OLED + console_display(); +#endif + + while (1) { + uint32_t t1 = daisy::System::GetNow(); + dt = t1 - t; + t = t1; + +// pulse seed LED for status according to CPU usage: +#ifndef OOPSY_SOM_PETAL_SM + som->SetLed((t % 1000) / 10 <= uint32_t(audioCpuUsage)); +#endif + + if (app_load_scheduled) { + app_load_scheduled = 0; + appdefs[app_selected].load(); + continue; + } + + // handle app-level code (e.g. for CV/gate outs) + mainloopCallback(t, dt); +#ifdef OOPSY_TARGET_USES_MIDI_UART + // send data if there's something to read: + if (midi_out_readidx != midi_out_writeidx) { + uint32_t size = + (OOPSY_MIDI_BUFFER_SIZE + midi_out_writeidx - midi_out_readidx) % + OOPSY_MIDI_BUFFER_SIZE; + size = ((midi_out_readidx + size) <= OOPSY_MIDI_BUFFER_SIZE) + ? size + : OOPSY_MIDI_BUFFER_SIZE - midi_out_readidx; + // for (uint32_t i=0; iPrintLine("the time is" FLT_FMT3 "", FLT_VAR3(t / 1000.f)); +#endif +#ifdef OOPSY_USE_USB_SERIAL_INPUT + if (update && rx_size > 0) { + // TODO check bytes for a reset message and jump to bootloader + update = false; + log(sumbuff); + } +#endif + +// CLEAR DISPLAY +#ifdef OOPSY_TARGET_HAS_OLED + hardware.display.Fill(false); +#endif +#ifdef OOPSY_TARGET_PETAL + hardware.led_driver.SetAllTo((uint8_t)0); +#endif + + if (menu_button_held_ms > OOPSY_LONG_PRESS_MS) { + is_mode_selecting = 1; + } +#ifdef OOPSY_TARGET_PETAL + // has no mode selection + is_mode_selecting = 0; +#if defined(OOPSY_MULTI_APP) + // multi-app is always in menu mode: + mode = MODE_MENU; +#endif + for (int i = 0; i < 8; i++) { + float white = (i == app_selecting || menu_button_released); + + SetRingLed(&hardware.led_driver, (daisy::DaisyPetal::RingLed)i, + (i == app_selected || white) * 1.f, white * 1.f, + (i < app_count) * 0.3f + white * 1.f); + } +#endif // OOPSY_TARGET_PETAL + + // #ifdef OOPSY_TARGET_VERSIO + // // has no mode selection + // is_mode_selecting = 0; + // #if defined(OOPSY_MULTI_APP) + // // multi-app is always in menu mode: + // mode = MODE_MENU; + // #endif + // for(int i = 0; i < 4; i++) { + // float white = (i == app_selecting || menu_button_released); + // hardware.SetLed(i, + // (i == app_selected || white) * 1.f, + // white * 1.f, + // (i < app_count) * 0.3f + white * 1.f + // ); + // } + // #endif //OOPSY_TARGET_VERSIO + + // Handle encoder increment actions: + if (is_mode_selecting) { + mode += menu_button_incr; +#ifdef OOPSY_TARGET_FIELD + // mode menu rotates infinitely + if (mode >= MODE_COUNT) + mode = 0; + if (mode < 0) + mode = MODE_COUNT - 1; +#else + // mode menu clamps at either end + if (mode >= MODE_COUNT) + mode = MODE_COUNT - 1; + if (mode < 0) + mode = 0; +#endif +#ifdef OOPSY_MULTI_APP + } else if (mode == MODE_MENU) { +#ifdef OOPSY_TARGET_VERSIO + app_selecting = menu_button_incr; +#else + app_selecting += menu_button_incr; +#endif + if (app_selecting >= app_count) + app_selecting -= app_count; + if (app_selecting < 0) + app_selecting += app_count; +#endif // OOPSY_MULTI_APP +#ifdef OOPSY_TARGET_HAS_OLED + } else if (mode == MODE_SCOPE) { + switch (scope_option) { + case SCOPEOPTION_STYLE: { + scope_style = (scope_style + menu_button_incr) % SCOPESTYLE_COUNT; + } break; + case SCOPEOPTION_SOURCE: { + scope_source = + (scope_source + menu_button_incr) % (OOPSY_IO_COUNT * 2); + } break; + case SCOPEOPTION_ZOOM: { + scope_zoom = (scope_zoom + menu_button_incr) % OOPSY_SCOPE_MAX_ZOOM; + } break; + } +#ifdef OOPSY_HAS_PARAM_VIEW + } else if (mode == MODE_PARAMS) { + if (!param_is_tweaking) { + param_selected += menu_button_incr; + if (param_selected >= param_count) + param_selected = 0; + if (param_selected < 0) + param_selected = param_count - 1; + } +#endif // OOPSY_HAS_PARAM_VIEW +#endif // OOPSY_TARGET_HAS_OLED + } + + // SHORT PRESS + if (menu_button_released) { + menu_button_released = 0; + if (is_mode_selecting) { + is_mode_selecting = 0; +#ifdef OOPSY_MULTI_APP + } else if (mode == MODE_MENU) { + if (app_selected != app_selecting) { + app_selected = app_selecting; +#ifndef OOPSY_TARGET_HAS_OLED + mode = 0; +#endif + schedule_app_load(app_selected); // appdefs[app_selected].load(); + // continue; + } +#endif +#ifdef OOPSY_TARGET_HAS_OLED + } else if (mode == MODE_SCOPE) { + scope_option = (scope_option + 1) % SCOPEOPTION_COUNT; +#if defined(OOPSY_HAS_PARAM_VIEW) && defined(OOPSY_CAN_PARAM_TWEAK) + } else if (mode == MODE_PARAMS) { + param_is_tweaking = !param_is_tweaking; +#endif // OOPSY_HAS_PARAM_VIEW && OOPSY_CAN_PARAM_TWEAK +#endif // OOPSY_TARGET_HAS_OLED + } + } + +// OLED DISPLAY: +#ifdef OOPSY_TARGET_HAS_OLED + int showstats = 0; + switch (mode) { +#ifdef OOPSY_MULTI_APP + case MODE_MENU: { + showstats = 1; + for (int i = 0; i < console_rows; i++) { + if (i == app_selecting) { + hardware.display.SetCursor(0, font.FontHeight * i); + hardware.display.WriteString((char *)">", font, true); + } + if (i < app_count) { + hardware.display.SetCursor(font.FontWidth, font.FontHeight * i); + hardware.display.WriteString((char *)appdefs[i].name, font, + i != app_selected); + } + } + } break; +#endif // OOPSY_MULTI_APP +#ifdef OOPSY_HAS_PARAM_VIEW + case MODE_PARAMS: { + char label[console_cols + 1]; + // ensure selected parameter is on-screen: + if (param_scroll > param_selected) + param_scroll = param_selected; + if (param_scroll < (param_selected - console_rows + 1)) + param_scroll = (param_selected - console_rows + 1); + int idx = param_scroll; // offset this for screen-scroll + for (int line = 0; line < console_rows && idx < param_count; + line++, idx++) { + paramCallback(idx, label, console_cols, + param_is_tweaking && idx == param_selected); + hardware.display.SetCursor(0, font.FontHeight * line); + hardware.display.WriteString(label, font, (param_selected != idx)); + } + } break; +#endif // OOPSY_HAS_PARAM_VIEW + case MODE_SCOPE: { + showstats = 1; + uint8_t h = OOPSY_OLED_DISPLAY_HEIGHT; + uint8_t w2 = OOPSY_OLED_DISPLAY_WIDTH / 2, + w4 = OOPSY_OLED_DISPLAY_WIDTH / 4; + uint8_t h2 = h / 2, h4 = h / 4; + size_t zoomlevel = scope_samples(); + hardware.display.Fill(false); + + // stereo views: + switch (scope_style) { + case SCOPESTYLE_OVERLAY: { + // stereo overlay: + for (uint_fast8_t i = 0; i < OOPSY_OLED_DISPLAY_WIDTH; i++) { + int j = i * 2; + hardware.display.DrawLine(i, (1.f - scope_data[j][0]) * h2, i, + (1.f - scope_data[j + 1][0]) * h2, 1); + hardware.display.DrawLine(i, (1.f - scope_data[j][1]) * h2, i, + (1.f - scope_data[j + 1][1]) * h2, 1); + } + } break; + case SCOPESTYLE_TOPBOTTOM: { + // stereo top-bottom + for (uint_fast8_t i = 0; i < OOPSY_OLED_DISPLAY_WIDTH; i++) { + int j = i * 2; + hardware.display.DrawLine(i, (1.f - scope_data[j][0]) * h4, i, + (1.f - scope_data[j + 1][0]) * h4, 1); + hardware.display.DrawLine( + i, (1.f - scope_data[j][1]) * h4 + h2, i, + (1.f - scope_data[j + 1][1]) * h4 + h2, 1); + } + } break; + case SCOPESTYLE_LEFTRIGHT: { + // stereo L/R: + for (uint_fast8_t i = 0; i < w2; i++) { + int j = i * 4; + hardware.display.DrawLine(i, (1.f - scope_data[j][0]) * h2, i, + (1.f - scope_data[j + 1][0]) * h2, 1); + hardware.display.DrawLine(i + w2, (1.f - scope_data[j][1]) * h2, + i + w2, + (1.f - scope_data[j + 1][1]) * h2, 1); + } + } break; + default: { + for (uint_fast8_t i = 0; i < OOPSY_OLED_DISPLAY_WIDTH; i++) { + int j = i * 2; + hardware.display.DrawPixel(w2 + h2 * scope_data[j][0], + h2 + h2 * scope_data[j][1], 1); + } + + // for (uint_fast8_t i=0; iAudioSampleRate()); + int offset = snprintf(scope_label, console_cols, "%dx %dms", + zoomlevel, (int)ceilf(scope_duration)); + hardware.display.SetCursor(0, h - font.FontHeight); + hardware.display.WriteString(scope_label, font, true); + } break; + // for view style, just leave it blank :-) + } + } break; + case MODE_CONSOLE: { + showstats = 1; + console_display(); + break; + } + default: { + } + } + if (is_mode_selecting) { + hardware.display.DrawRect(0, 0, OOPSY_OLED_DISPLAY_WIDTH - 1, + OOPSY_OLED_DISPLAY_HEIGHT - 1, 1); + } + if (showstats) { + int offset = 0; +#ifdef OOPSY_TARGET_USES_MIDI_UART + offset += + snprintf(console_stats + offset, console_cols - offset, "%c%c", + midi_in_active ? '<' : ' ', midi_out_active ? '>' : ' '); + midi_in_active = midi_out_active = 0; +#endif + offset += snprintf(console_stats + offset, console_cols - offset, + "%02d%%", int(audioCpuUsage)); + // stats: + hardware.display.SetCursor(OOPSY_OLED_DISPLAY_WIDTH - + (offset)*font.FontWidth, + font.FontHeight * 0); + hardware.display.WriteString(console_stats, font, true); + } +#endif // OOPSY_TARGET_HAS_OLED + menu_button_incr = 0; + + // handle app-level code (e.g. for LED etc.) + displayCallback(t, dt); + +#ifdef OOPSY_TARGET_HAS_OLED + hardware.display.Update(); +#endif // OOPSY_TARGET_HAS_OLED + + // Call hardware loop processing (LED drivers, etc.) + hardware.LoopProcess(); + +#if (OOPSY_TARGET_PETAL) + hardware.led_driver.SwapBuffersAndTransmit(); +#endif //(OOPSY_TARGET_PETAL) + } // uitimer.ready + } + return 0; + } + + void schedule_app_load(int which) { + app_selected = app_selecting = which % app_count; + app_load_scheduled = 1; + } + + void audio_preperform(size_t size) { +#ifdef OOPSY_TARGET_USES_MIDI_UART + // fill remainder of midi buffer with non-data: + for (size_t i = midi_in_written; i < size; i++) + midi_in_data[i] = -0.1f; + // done with midi input: + midi_in_written = 0; +#endif + + hardware.ProcessAllControls(); + +#if defined(OOPSY_TARGET_SEED) + menu_button_incr += hardware.menu_rotate; + menu_button_held_ms = hardware.menu_hold; + if (hardware.menu_click) + menu_button_released = hardware.menu_click; +#elif defined(OOPSY_TARGET_FIELD) + // menu_button_held = hardware.GetSwitch(0)->Pressed(); + menu_button_incr += hardware.sw2.FallingEdge(); + menu_button_held_ms = hardware.sw1.TimeHeldMs(); + if (hardware.sw1.FallingEdge()) + menu_button_released = 1; +#elif defined(OOPSY_TARGET_VERSIO) + // menu_button_held = hardware.tap.Pressed(); + // menu_button_incr += hardware.GetKnobValue(6) * app_count; + // menu_button_held_ms = hardware.tap.TimeHeldMs(); + // if (hardware.tap_.FallingEdge()) menu_button_released = 1; +#elif defined(OOPSY_TARGET_POD) || defined(OOPSY_TARGET_PETAL) || \ + defined(OOPSY_TARGET_PATCH) + // menu_button_held = hardware.encoder.Pressed(); + menu_button_incr += hardware.encoder.Increment(); + menu_button_held_ms = hardware.encoder.TimeHeldMs(); + if (hardware.encoder.FallingEdge()) + menu_button_released = 1; +#endif + } + + void audio_postperform(float **buffers, size_t size) { +#ifdef OOPSY_TARGET_HAS_OLED + if (mode == MODE_SCOPE) { + // selector for scope storage source: + // e.g. for OOPSY_IO_COUNT=4, inputs:outputs as 0123:4567 makes: + // 01, 23, 45, 67 2n:2n+1 i1i2 i3i4 o1o2 o3o4 + // 04, 15, 26, 37 n:n+ch i1o1 i2o2 i3o3 i4o4 + int n = scope_source % OOPSY_IO_COUNT; + float *buf0 = + (scope_source < OOPSY_IO_COUNT) ? buffers[2 * n] : buffers[n]; + float *buf1 = (scope_source < OOPSY_IO_COUNT) + ? buffers[2 * n + 1] + : buffers[n + OOPSY_IO_COUNT]; + + // float * buf0 = scope_source ? buffers[0] : buffers[2]; + // float * buf1 = scope_source ? buffers[1] : buffers[3]; + size_t samples = scope_samples(); + if (samples > size) + samples = size; + + for (size_t i = 0; i < size / samples; i++) { + float min0 = 10.f, max0 = -10.f; + float min1 = 10.f, max1 = -10.f; + for (size_t j = 0; j < samples; j++) { + float pt0 = *buf0++; + float pt1 = *buf1++; + min0 = min0 > pt0 ? pt0 : min0; + max0 = max0 < pt0 ? pt0 : max0; + min1 = min1 > pt1 ? pt1 : min1; + max1 = max1 < pt1 ? pt1 : max1; + } + scope_data[scope_step][0] = (min0); + scope_data[scope_step][1] = (min1); + scope_step++; + scope_data[scope_step][0] = (max0); + scope_data[scope_step][1] = (max1); + scope_step++; + if (scope_step >= OOPSY_OLED_DISPLAY_WIDTH * 2) + scope_step = 0; + } + } +#endif + // Call hardware post-processing (shift register updates, debouncing, etc.) + hardware.PostProcess(); + + blockcount++; + } + +#ifdef OOPSY_TARGET_HAS_OLED + inline int scope_samples() { + // valid zoom sizes: 1, 2, 3, 4, 6, 8, 12, 16, 24 + switch (scope_zoom) { + case 1: + case 2: + case 3: + case 4: + return scope_zoom; + break; + case 5: + return 6; + break; + case 6: + return 12; + break; + case 7: + return 16; + break; + default: + return 24; + break; + } + } + + GenDaisy &console_display() { + for (int i = 0; i < console_rows; i++) { + hardware.display.SetCursor(0, font.FontHeight * i); + hardware.display.WriteString( + console_lines[(i + console_line) % console_rows], font, true); + } + return *this; + } +#endif // OOPSY_TARGET_HAS_OLED + + GenDaisy &log(const char *fmt, ...) { +#ifdef OOPSY_TARGET_HAS_OLED + va_list argptr; + va_start(argptr, fmt); + vsnprintf(console_lines[console_line], console_cols, fmt, argptr); + va_end(argptr); + console_line = (console_line + 1) % console_rows; +#endif + return *this; + } + +#if OOPSY_TARGET_USES_MIDI_UART + void midi_postperform(float *buf, size_t size) { + size_t i = 0; + int8_t byte = buf[i] * 256.0f; + uint32_t w1 = (midi_out_writeidx + 1) % OOPSY_MIDI_BUFFER_SIZE; + while (byte >= 0 && i < size && w1 != midi_out_readidx) { + // scale (0.0, 1.0) back to (0, 255) for MIDI bytes + midi_out_data[midi_out_writeidx] = byte; + midi_out_writeidx = w1; + w1 = (midi_out_writeidx + 1) % OOPSY_MIDI_BUFFER_SIZE; + i++; + byte = buf[i] * 256.0f; + } + // for (size_t i=0; i= 0.f) { + // // scale (0.0, 1.0) back to (0, 255) for MIDI bytes + // midi_out_data[midi_out_written] = buf[i] * 256.0f; + // midi_out_written++; + // } + // } + } + + void midi_message1(uint8_t byte) { + uint32_t r = + (midi_out_readidx + OOPSY_MIDI_BUFFER_SIZE - midi_out_writeidx) % + OOPSY_MIDI_BUFFER_SIZE; + if (r > 0 && r <= 1) { + log("midi buffer full"); + return; + } + uint32_t i0 = midi_out_writeidx; + uint32_t i1 = (midi_out_writeidx + 1) % OOPSY_MIDI_BUFFER_SIZE; + midi_out_data[i0] = byte; + midi_out_writeidx = i1; + } + + void midi_message2(uint8_t status, uint8_t b1) { + uint32_t r = + (midi_out_readidx + OOPSY_MIDI_BUFFER_SIZE - midi_out_writeidx) % + OOPSY_MIDI_BUFFER_SIZE; + if (r > 0 && r <= 2) { + log("midi buffer full"); + return; + } + uint32_t i0 = midi_out_writeidx; + uint32_t i1 = (midi_out_writeidx + 1) % OOPSY_MIDI_BUFFER_SIZE; + uint32_t i2 = (midi_out_writeidx + 2) % OOPSY_MIDI_BUFFER_SIZE; + midi_out_data[i0] = status; + midi_out_data[i1] = b1; + midi_out_writeidx = i2; + } + + void midi_message3(uint8_t status, uint8_t b1, uint8_t b2) { + uint32_t r = + (midi_out_readidx + OOPSY_MIDI_BUFFER_SIZE - midi_out_writeidx) % + OOPSY_MIDI_BUFFER_SIZE; + if (r > 0 && r <= 3) { + log("midi buffer full"); + return; + } + uint32_t i0 = midi_out_writeidx; + uint32_t i1 = (midi_out_writeidx + 1) % OOPSY_MIDI_BUFFER_SIZE; + uint32_t i2 = (midi_out_writeidx + 2) % OOPSY_MIDI_BUFFER_SIZE; + uint32_t i3 = (midi_out_writeidx + 3) % OOPSY_MIDI_BUFFER_SIZE; + midi_out_data[i0] = status; + midi_out_data[i1] = b1; + midi_out_data[i2] = b2; + midi_out_writeidx = i3; + } + + void midi_nullData(Data &data) { + for (int i = 0; i < data.dim; i++) { + data.write(-1, i, 0); + } + } + + void midi_fromData(Data &data) { + double b = data.read(midi_data_idx, 0); + while (b >= 0. && midi_out_writeidx != midi_out_readidx) { + // erase it from [data midi] + data.write(-1, midi_data_idx, 0); + // write it to our active outbuffer: + midi_out_data[midi_out_writeidx] = b; + midi_out_writeidx = (midi_out_writeidx + 1) % OOPSY_MIDI_BUFFER_SIZE; + // and advance one index in the [data midi] + midi_data_idx++; + if (midi_data_idx >= data.dim) + midi_data_idx = 0; + b = data.read(midi_data_idx, 0); + } + } +#endif // OOPSY_TARGET_USES_MIDI_UART + +// TODO -- need better way to handle this to avoid hardcoding +#if (OOPSY_TARGET_FIELD) + void setFieldLedsFromData(Data &data) { + for (long i = 0; i < daisy::DaisyField::LED_LAST && i < data.dim; i++) { + // LED indices run A1..8, B8..1, Knob1..8 + // switch here to re-order the B8-1 to B1-8 + long idx = i; + if (idx > 7 && idx < 16) + idx = 23 - i; + hardware.led_driver.SetLed(idx, data.read(i, 0)); + } + // hardware.led_driver.SwapBuffersAndTransmit(); // now handled by hardware + // class + }; +#endif + + static void nullAudioCallback(daisy::AudioHandle::InputBuffer ins, + daisy::AudioHandle::OutputBuffer outs, + size_t size); + + static void nullMainloopCallback(uint32_t t, uint32_t dt) {} +} daisy; + +void GenDaisy::nullAudioCallback(daisy::AudioHandle::InputBuffer ins, + daisy::AudioHandle::OutputBuffer outs, + size_t size) { + daisy.nullAudioCallbackRunning = true; + // zero audio outs: + for (int i = 0; i < OOPSY_IO_COUNT; i++) { + memset(outs[i], 0, sizeof(float) * size); + } +} + +#ifdef OOPSY_TARGET_USES_MIDI_UART +// MIDI RX callback for DMA listen mode (libdaisy v8.0.0) +uint8_t GenDaisy::midi_rx_buffer[256]; +uint32_t GenDaisy::midi_rx_write_idx = 0; +uint32_t GenDaisy::midi_rx_read_idx = 0; + +void GenDaisy::MidiRxCallback(uint8_t *data, size_t size, void *context, + daisy::UartHandler::Result result) { + if (result != daisy::UartHandler::Result::OK) + return; + GenDaisy *self = static_cast(context); + for (size_t i = 0; i < size; i++) { + uint32_t next_idx = (self->midi_rx_write_idx + 1) % 256; + if (next_idx != self->midi_rx_read_idx) { + self->midi_rx_buffer[self->midi_rx_write_idx] = data[i]; + self->midi_rx_write_idx = next_idx; + } + } +} +#endif + +// Curiously-recurring template to make App definitions simpler: +template struct App { + + static void staticMainloopCallback(uint32_t t, uint32_t dt) { + T &self = *(T *)daisy.app; + self.mainloopCallback(daisy, t, dt); + } + + static void staticDisplayCallback(uint32_t t, uint32_t dt) { + T &self = *(T *)daisy.app; + self.displayCallback(daisy, t, dt); + } + + static void + staticAudioCallback(daisy::AudioHandle::InputBuffer hardware_ins, + daisy::AudioHandle::OutputBuffer hardware_outs, + size_t size) { + uint32_t start = daisy::System::GetUs(); + daisy.audio_preperform(size); + ((T *)daisy.app)->audioCallback(daisy, hardware_ins, hardware_outs, size); +#if (OOPSY_IO_COUNT == 4) + float *buffers[] = {(float *)hardware_ins[0], (float *)hardware_ins[1], + (float *)hardware_ins[2], (float *)hardware_ins[3], + hardware_outs[0], hardware_outs[1], + hardware_outs[2], hardware_outs[3]}; +#else + float *buffers[] = {(float *)hardware_ins[0], (float *)hardware_ins[1], + hardware_outs[0], hardware_outs[1]}; +#endif + daisy.audio_postperform(buffers, size); + // convert elapsed time (us) to CPU percentage (0-100) of available + // processing time 100 (%) * (0.000001 * used_us) * callbackrateHz + float percent = (daisy::System::GetUs() - start) * 0.0001f * + daisy.som->AudioCallbackRate(); + percent = percent > 100.f ? 100.f : percent; + // with a falling-only slew to capture spikes, since we care most about + // worst-case performance + daisy.audioCpuUsage = + (percent > daisy.audioCpuUsage) + ? percent + : daisy.audioCpuUsage + 0.02f * (percent - daisy.audioCpuUsage); + } + +#if defined(OOPSY_TARGET_HAS_OLED) && defined(OOPSY_HAS_PARAM_VIEW) + static void staticParamCallback(int idx, char *label, int len, bool tweak) { + T &self = *(T *)daisy.app; + self.paramCallback(daisy, idx, label, len, tweak); + } +#endif // defined(OOPSY_TARGET_HAS_OLED) && defined(OOPSY_HAS_PARAM_VIEW) +}; + +}; // namespace oopsy void genlib_report_error(const char *s) { oopsy::daisy.log(s); } void genlib_report_message(const char *s) { oopsy::daisy.log(s); } -unsigned long genlib_ticks() { - return 0; //daisy::System::GetTick(); +unsigned long genlib_ticks() { + return 0; // daisy::System::GetTick(); } t_ptr genlib_sysmem_newptr(t_ptr_size size) { - return (t_ptr)oopsy::allocate(size); + return (t_ptr)oopsy::allocate(size); } t_ptr genlib_sysmem_newptrclear(t_ptr_size size) { - t_ptr p = genlib_sysmem_newptr(size); - if (p) oopsy::memset(p, 0, size); - return p; + t_ptr p = genlib_sysmem_newptr(size); + if (p) + oopsy::memset(p, 0, size); + return p; } - -#endif //GENLIB_DAISY_H +#endif // GENLIB_DAISY_H diff --git a/source/json2daisy.js b/source/json2daisy.js new file mode 100644 index 0000000..35e3ca8 --- /dev/null +++ b/source/json2daisy.js @@ -0,0 +1,608 @@ +#!/usr/bin/env node + +// TODO -- we'll reimplement the filter / map stuff here if just for the exclusion capabilities + +const assert = require("assert"); +const path = require("path"); +const seed_defs = require(path.join(__dirname, "component_defs.json")); +const patchsm_defs = require(path.join(__dirname, "component_defs_patchsm.json")); +const petalsm_defs = require(path.join(__dirname, "component_defs_petalsm.json")); + +var global_definitions; + +// .filter for objects that returns array +Object.filter = (obj, predicate) => + Object.keys(obj).filter(key => predicate(obj[key])) + .map(key => obj[key]); + +function generateCodecs(external_codecs) +{ + codec_string = ` + // External Codec Initialization + daisy::SaiHandle::Config sai_config[${1 + external_codecs.length}]; + + // Internal Codec + if(som.CheckBoardVersion() == daisy::DaisySeed::BoardVersion::DAISY_SEED_1_1) + { + sai_config[0].pin_config.sa = {DSY_GPIOE, 6}; + sai_config[0].pin_config.sb = {DSY_GPIOE, 3}; + sai_config[0].a_dir = daisy::SaiHandle::Config::Direction::RECEIVE; + sai_config[0].b_dir = daisy::SaiHandle::Config::Direction::TRANSMIT; + } + else + { + sai_config[0].pin_config.sa = {DSY_GPIOE, 6}; + sai_config[0].pin_config.sb = {DSY_GPIOE, 3}; + sai_config[0].a_dir = daisy::SaiHandle::Config::Direction::TRANSMIT; + sai_config[0].b_dir = daisy::SaiHandle::Config::Direction::RECEIVE; + } + sai_config[0].periph = daisy::SaiHandle::Config::Peripheral::SAI_1; + sai_config[0].sr = daisy::SaiHandle::Config::SampleRate::SAI_48KHZ; + sai_config[0].bit_depth = daisy::SaiHandle::Config::BitDepth::SAI_24BIT; + sai_config[0].a_sync = daisy::SaiHandle::Config::Sync::MASTER; + sai_config[0].b_sync = daisy::SaiHandle::Config::Sync::SLAVE; + sai_config[0].pin_config.fs = {DSY_GPIOE, 4}; + sai_config[0].pin_config.mclk = {DSY_GPIOE, 2}; + sai_config[0].pin_config.sck = {DSY_GPIOE, 5}; + ` + + for (let i = 0; i < external_codecs.length; i++) + { + codec_string += ` + sai_config[${i + 1}].periph = daisy::SaiHandle::Config::Peripheral::${external_codecs[i].periph}; + sai_config[${i + 1}].sr = daisy::SaiHandle::Config::SampleRate::SAI_48KHZ; + sai_config[${i + 1}].bit_depth = daisy::SaiHandle::Config::BitDepth::SAI_24BIT; + sai_config[${i + 1}].a_sync = daisy::SaiHandle::Config::Sync::${external_codecs[i].a_sync}; + sai_config[${i + 1}].b_sync = daisy::SaiHandle::Config::Sync::${external_codecs[i].b_sync}; + sai_config[${i + 1}].a_dir = daisy::SaiHandle::Config::Direction::${external_codecs[i].a_dir}; + sai_config[${i + 1}].b_dir = daisy::SaiHandle::Config::Direction::${external_codecs[i].b_dir}; + sai_config[${i + 1}].pin_config.fs = som.GetPin(${external_codecs[i].pin.fs}); + sai_config[${i + 1}].pin_config.mclk = som.GetPin(${external_codecs[i].pin.mclk}); + sai_config[${i + 1}].pin_config.sck = som.GetPin(${external_codecs[i].pin.sck}); + sai_config[${i + 1}].pin_config.sa = som.GetPin(${external_codecs[i].pin.sa}); + sai_config[${i + 1}].pin_config.sb = som.GetPin(${external_codecs[i].pin.sb}); + `; + } + + codec_string += ` + daisy::SaiHandle sai_handle[${external_codecs.length + 1}]; + sai_handle[0].Init(sai_config[0]); + `; + + for (let i = 0; i < external_codecs.length; i++) + { + codec_string += ` + sai_handle[${i + 1}].Init(sai_config[${i + 1}]); + `; + } + + codec_string += ` + dsy_gpio_pin codec_reset_pin = som.GetPin(29); + daisy::Ak4556::Init(codec_reset_pin); + + daisy::AudioHandle::Config cfg; + cfg.blocksize = 48; + cfg.samplerate = daisy::SaiHandle::Config::SampleRate::SAI_48KHZ; + cfg.postgain = 0.5f; + som.audio_handle.Init( + cfg, + sai_handle[0]`; + + for (let i = 0; i < external_codecs.length; i++) + { + codec_string += ",\n "; + codec_string += `sai_handle[${i + 1}]`; + } + + codec_string += ` + ); + `; + + return codec_string; +} + +function stringFormatMap(template, formatMap) +{ + if (typeof template === 'undefined') + return ''; + const format_match = /{\s*([^{}\s]*)\s*}/g; + const open_curly = /{{/g; + const close_curly = /}}/g; + let pass1 = template.replace(open_curly, () => { + return '{' + }); + let pass2 = pass1.replace(close_curly, () => { + return '}' + }); + let pass3 = pass2.replace(format_match, (substring, value, index) => { + return value in formatMap ? formatMap[value] : ''; + }); + return pass3; +} + +exports.format_map = stringFormatMap; + +function map_load(key, item) +{ + item.name = key; + assert(item.component in global_definitions, `Undefined component kind "${item.component}"`); + component = global_definitions[item.component]; + + for (property in component) + { + if (!(property in item)) + item[property] = component[property]; + } + + return item; +} + +function filter_match(sequence, key, match, key_exclude = null, match_exclude = null) +{ + if (key_exclude !== null && match_exclude !== null) + { + return Object.filter(sequence, item => key in item && item[key] == match && ((item[key_exclude] || null) != match_exclude)); + } + else + return Object.filter(sequence, item => key in item && item[key] == match); +} + +function filter_matches(sequence, key, matches, key_exclude=null, match_exclude=null) +{ + if (key_exclude !== null && match_exclude !== null) + { + return Object.filter(sequence, item => { + let items_key = item[key] || ''; + let items_key_exclude = item[key_exclude] || ''; + return matches.includes(items_key) && items_key_exclude != match_exclude; + }); + } + else + { + return Object.filter(sequence, item => { + let items_key = item[key] || ''; + return matches.includes(items_key); + }); + } +} + +function filter_has(sequence, key, key_exclude=null, match_exclude=null) +{ + if (key_exclude !== null && match_exclude !== null) + { + return Object.filter(sequence, item => { + let items_key_exclude = item[key_exclude] || ''; + return key in item && items_key_exclude != match_exclude; + }); + } + else + { + return Object.filter(sequence, item => key in item); + } +} + +function filter_map_init(set, key, match, key_exclude=null, match_exclude=null) +{ + filtered = filter_match(set, key, match, key_exclude, match_exclude); + return filtered.map(item => stringFormatMap(item.map_init, item)).join("\n "); +} + +function filter_map_set(set, key, match, key_exclude=null, match_exclude=null) +{ + filtered = filter_match(set, key, match, key_exclude, match_exclude); + return filtered.map(item => stringFormatMap(stringFormatMap(item.mapping[0].set, item.mapping[0].name, item))).join("\n "); +} + +function filter_map_ctrl(set, key, matches, init_key, key_exclude=null, match_exclude=null) +{ + set = filter_matches(set, key, matches, key_exclude, match_exclude); + set = set.map((item, i) => Object.assign(item, item, {i: i})); + return set.map(item => stringFormatMap(item[init_key], item)).join("\n "); +} + +function filter_map_template(set, name, key_exclude=null, match_exclude=null) +{ + filtered = filter_has(set, name, key_exclude, match_exclude); + return filtered.map(item => stringFormatMap(item[name], item)).join("\n "); +} + +function flatten_pin_dicts(comp) +{ + flattened = {}; + Object.assign(flattened, comp); // maybe not actually necessary to copy + if ('pin' in comp && typeof comp.pin === 'object') + { + for (property in comp.pin) + { + flattened[`pin_${property}`] = comp.pin[property]; + } + } + return flattened; +} + +function flatten_index_dicts(comp) +{ + flattened = {}; + Object.assign(flattened, comp); // maybe not actually necessary to copy + if ('index' in comp && typeof comp.index === 'object') + { + for (property in comp.index) + { + flattened[`index_${property}`] = comp.index[property]; + } + } + return flattened; +} + +exports.generate_header = function generate_header(board_description_object, target_path) +{ + let target = board_description_object; + + let components = target.components; + let parents = target.parents || {}; + + for (let comp in parents) + { + parents[comp].is_parent = true; + } + Object.assign(components, components, parents); + + som = target.som || 'seed'; + + let temp_defs = { + seed: seed_defs, + patch_sm: patchsm_defs, + petal_125b_sm: petalsm_defs, + }; + + assert(som in temp_defs, `Unkown som "${som}"`); + + definitions = temp_defs[som]; + global_definitions = definitions; + + for (let comp in components) + { + components[comp] = map_load(comp, components[comp]); + components[comp] = flatten_pin_dicts(components[comp]); + components[comp] = flatten_index_dicts(components[comp]); + } + + target.components = components; + target.name = target.name || ''; // For now we'll allow it to be nothing + target.aliases = target.aliases || {}; + + if ("display" in target) + { + // shouldn't this only be done if the display property is empty? + target.display = { + driver: "daisy::SSD130x4WireSpi128x64Driver", + config: [], + dim: [128, 64], + }; + + target.defines.OOPSY_TARGET_HAS_OLED = 1; + target.defines.OOPSY_OLED_DISPLAY_WIDTH = target.display.dim[0] + target.defines.OOPSY_OLED_DISPLAY_HEIGHT = target.display.dim[1] + } + + let has_display = target.defines.OOPSY_TARGET_HAS_OLED || false; + + let replacements = {} + replacements.name = target.name; + replacements.som = som; + replacements.external_codecs = target.external_codecs || []; + + const classes = { + seed: 'daisy::DaisySeed', + patch_sm: 'daisy::patch_sm::DaisyPatchSM', + petal_125b_sm: 'daisy::Petal125BSM' + }; + + replacements.som_class = classes[som]; + + replacements.target_name = target.name; // TODO -- redundant? + replacements.init = filter_map_template(components, 'init', 'is_default', true); + + replacements.cd4021 = filter_map_init(components, 'component', 'CD4021', key_exclude='is_default', match_exclude=true); + replacements.i2c = filter_map_init(components, 'component', 'i2c', key_exclude='is_default', match_exclude=true); + replacements.pca9685 = filter_map_init(components, 'component', 'PCA9685', key_exclude='is_default', match_exclude=true); + replacements.switch = filter_map_init(components, 'component', 'Switch', key_exclude='is_default', match_exclude=true); + replacements.gatein = filter_map_init(components, 'component', 'GateIn', key_exclude='is_default', match_exclude=true); + replacements.encoder = filter_map_init(components, 'component', 'Encoder', key_exclude='is_default', match_exclude=true); + replacements.switch3 = filter_map_init(components, 'component', 'Switch3', key_exclude='is_default', match_exclude=true); + replacements.analogcount = filter_matches(components, 'component', ['AnalogControl', 'AnalogControlBipolar', 'CD4051'], key_exclude='is_default', match_exclude=true).length; + + replacements.init_single = filter_map_ctrl(components, 'component', ['AnalogControl', 'AnalogControlBipolar', 'CD4051'], 'init_single', key_exclude='is_default', match_exclude=true); + replacements.ctrl_init = filter_map_ctrl(components, 'component', ['AnalogControl', 'AnalogControlBipolar'], 'map_init', key_exclude='is_default', match_exclude=true); + + replacements.ctrl_mux_init = filter_map_init(components, 'component', 'CD4051AnalogControl', key_exclude='is_default', match_exclude=true); + + replacements.led = filter_map_init(components, 'component', 'Led', key_exclude='is_default', match_exclude=true); + replacements.rgbled = filter_map_init(components, 'component', 'RgbLed', key_exclude='is_default', match_exclude=true); + replacements.gateout = filter_map_init(components, 'component', 'GateOut', key_exclude='is_default', match_exclude=true); + replacements.dachandle = filter_map_init(components, 'component', 'CVOuts', key_exclude='is_default', match_exclude=true); + + replacements.MotorShield = filter_map_init(components, 'component', 'MotorShield', key_exclude='is_default', match_exclude=true); + replacements.StepperMotor = filter_map_init(components, 'component', 'StepperMotor', key_exclude='is_default', match_exclude=true); + replacements.DcMotor = filter_map_init(components, 'component', 'DcMotor', key_exclude='is_default', match_exclude=true); + replacements.Bme280 = filter_map_init(components, 'component', 'Bme280', key_exclude='is_default', match_exclude=true); + replacements.HallSensor = filter_map_init(components, 'component', 'HallSensor', key_exclude='is_default', match_exclude=true); + replacements.Tlv493d = filter_map_init(components, 'component', 'Tlv493d', key_exclude='is_default', match_exclude=true); + replacements.Mpr121 = filter_map_init(components, 'component', 'Mpr121', key_exclude='is_default', match_exclude=true); + replacements.Apds9960 = filter_map_init(components, 'component', 'Apds9960', key_exclude='is_default', match_exclude=true); + replacements.Bmp390 = filter_map_init(components, 'component', 'Bmp390', key_exclude='is_default', match_exclude=true); + replacements.Vl53l1x = filter_map_init(components, 'component', 'Vl53l1x', key_exclude='is_default', match_exclude=true); + replacements.Vl53l0x = filter_map_init(components, 'component', 'Vl53l0x', key_exclude='is_default', match_exclude=true); + replacements.NeoTrellis = filter_map_init(components, 'component', 'NeoTrellis', key_exclude='is_default', match_exclude=true); + replacements.NeoTrellisLeds = filter_map_init(components, 'component', 'NeoTrellisLeds', key_exclude='is_default', match_exclude=true); + replacements.Bno055 = filter_map_init(components, 'component', 'Bno055', key_exclude='is_default', match_exclude=true); + replacements.Icm20948 = filter_map_init(components, 'component', 'Icm20948', key_exclude='is_default', match_exclude=true); + replacements.Dps310 = filter_map_init(components, 'component', 'Dps310', key_exclude='is_default', match_exclude=true); + + replacements.CodeClass = filter_map_init(components, 'component', 'CodeClass', key_exclude='is_default', match_exclude=true); + + replacements.display = !(has_display) ? '' : ` + daisy::OledDisplay<${target.display.driver}>::Config display_config; + display_config.driver_config.transport_config.Defaults(); + display.Init(display_config); + display.Fill(0); + display.Update(); + `; + + // mangle CodeClass process calls into the correct syntax + let process_fields = ['process', 'loopprocess', 'postprocess', 'display']; + for (let component in components) + { + let comp_obj = components[component]; + if (comp_obj.component == 'CodeClass') + { + for (let field of process_fields) + { + if (field in comp_obj && comp_obj[field]) + comp_obj[field] = `{name}.${comp_obj[field]}();`; + } + } + } + + // Provide default getters and setters for CodeInput and CodeOutput components + for (let component in components) + { + let comp_obj = components[component]; + if (comp_obj.component == 'CodeInput') + { + if (!comp_obj.setter) + comp_obj.setter = component; + } + else if (comp_obj.component == 'CodeOutput') + { + if (!comp_obj.getter) + comp_obj.getter = component; + } + } + + replacements.process = filter_map_template(components, 'process', key_exclude='is_default', match_exclude=true); + // There's also this after {process}. I don't see any meta in the defaults json at this time. Is this needed? + // ${components.filter((e) => e.meta).map((e) => e.meta.map(m=>`${template(m, e)}`).join("")).join("")} + replacements.loopprocess = filter_map_template(components, 'loopprocess', key_exclude='is_default', match_exclude=true); + + replacements.postprocess = filter_map_template(components, 'postprocess', key_exclude='is_default', match_exclude=true); + replacements.displayprocess = filter_map_template(components, 'display', key_exclude='is_default', match_exclude=true); + replacements.hidupdaterates = filter_map_template(components, 'updaterate', key_exclude='is_default', match_exclude=true); + + component_decls = Object.filter(components, item => !(item.is_default || false)); + component_decls = component_decls.filter(item => 'typename' in item && item.typename != ""); + replacements.comps = component_decls.map(item => `${stringFormatMap(item.typename, item)} ${item.name}`).join(";\n ") + ';'; + non_class_decls = component_decls.filter(item => 'non_class_decl' in item); + replacements.non_class_declarations = non_class_decls.map(item => stringFormatMap(item.non_class_decl, item)).join("\n"); + + headers = Object.filter(components, item => 'header' in item); + abs_headers = headers.map(item => path.isAbsolute(item.header) ? item.header : + path.normalize(path.join(path.dirname(target_path), item.header))); + include_paths = abs_headers.map(item => path.dirname(item)); + replacements.headers = abs_headers.map(item => `#include "${path.basename(item)}"`).join("\n"); + + replacements.dispdec = 'display' in target ? `daisy::OledDisplay<${target.display.driver}> display;` : ""; + + let header = ` +#ifndef __JSON2DAISY_${replacements.name.toUpperCase()}_H__ +#define __JSON2DAISY_${replacements.name.toUpperCase()}_H__ + +#include "daisy_${replacements.som}.h" +${replacements.som == 'seed' ? '#include "dev/codec_ak4556.h"' : ''} +${has_display ? '#include "dev/oled_ssd130x.h"' : ''} +${replacements.headers} + +#define ANALOG_COUNT ${replacements.analogcount} + +namespace json2daisy { + +${replacements.non_class_declarations} + +${replacements.name != '' ? `struct Daisy${replacements.name[0].toUpperCase()}${replacements.name.slice(1)} {` + : `struct Daisy {`} + + /** Initializes the board according to the JSON board description + * \\param boost boosts the clock speed from 400 to 480 MHz + */ + void Init(bool boost=true) + { + ${replacements.som == 'seed' ? `som.Configure(); + som.Init(boost);` : `som.Init();`} + ${replacements.init} + ${replacements.i2c != '' ? '// i2c\n ' + replacements.i2c : ''} + ${replacements.pca9685 != '' ? '// LED Drivers\n ' + replacements.pca9685 : ''} + ${replacements.switch != '' ? '// Switches\n ' + replacements.switch : ''} + ${replacements.switch3 != '' ? '// SPDT Switches\n ' + replacements.switch3 : ''} + ${replacements.cd4021 != '' ? '// Muxes\n ' + replacements.cd4021 : ''} + ${replacements.gatein != '' ? '// Gate ins\n ' + replacements.gatein : ''} + ${replacements.encoder != '' ? '// Rotary encoders\n ' + replacements.encoder : ''} + ${replacements.init_single != '' ? '// Single channel ADC initialization\n ' + replacements.init_single : ''} + ${replacements.som == 'seed' && replacements.analogcount ? 'som.adc.Init(cfg, ANALOG_COUNT);' : ''} + ${replacements.ctrl_init != '' ? '// AnalogControl objects\n ' + replacements.ctrl_init : ''} + ${replacements.ctrl_mux_init != '' ? '// Multiplexed AnlogControl objects\n ' + replacements.ctrl_mux_init : ''} + ${replacements.led != '' ? '// LEDs\n ' + replacements.led : ''} + ${replacements.rgbled != '' ? '// RBG LEDs \n ' + replacements.rgbled : ''} + ${replacements.gateout != '' ? '// Gate outs\n ' + replacements.gateout : ''} + ${replacements.dachandle != '' ? '// DAC\n ' + replacements.dachandle : ''} + ${replacements.display != '' ? '// Display\n ' + replacements.display : ''} + + ${replacements.MotorShield != '' ? '// Motor Shield\n ' + replacements.MotorShield : ''} + ${replacements.StepperMotor != '' ? '// Stepper Motor\n ' + replacements.StepperMotor : ''} + ${replacements.DcMotor != '' ? '// DC Motor\n ' + replacements.DcMotor : ''} + ${replacements.Bme280 != '' ? '// BME sensor\n ' + replacements.Bme280 : ''} + ${replacements.HallSensor != '' ? '// Hall Effect Sensor\n ' + replacements.HallSensor : ''} + ${replacements.Tlv493d != '' ? '// TLV Sensor\n ' + replacements.Tlv493d : ''} + ${replacements.Mpr121 != '' ? '// MPR Sensor\n ' + replacements.Mpr121 : ''} + ${replacements.Apds9960 != '' ? '// APDS Sensor\n ' + replacements.Apds9960 : ''} + ${replacements.Bmp390 != '' ? '// BMP Sensor\n ' + replacements.Bmp390 : ''} + ${replacements.Vl53l1x != '' ? '// VL53L1X Sensor\n ' + replacements.Vl53l1x : ''} + ${replacements.Vl53l0x != '' ? '// VL53L0X Sensor\n ' + replacements.Vl53l0x : ''} + ${replacements.NeoTrellis != '' ? '// Neo Trellis\n ' + replacements.NeoTrellis : ''} + ${replacements.NeoTrellisLeds != '' ? '// NeoTrellis LEDs\n ' + replacements.NeoTrellisLeds : ''} + ${replacements.Bno055 != '' ? '// BNO Sensor\n ' + replacements.Bno055 : ''} + ${replacements.Icm20948 != '' ? '// Icm20948 Sensor\n ' + replacements.Icm20948 : ''} + ${replacements.Dps310 != '' ? '// Dps310 Sensor\n ' + replacements.Dps310 : ''} + + ${replacements.CodeClass != '' ? '// Custom classes\n ' + replacements.CodeClass : ''} + + ${replacements.external_codecs.length == 0 ? '' : generateCodecs(replacements.external_codecs)} + + ${replacements.som == 'seed' ? 'som.adc.Start();' : ''} + } + + /** Handles all the controls processing that needs to occur at the block rate + * + */ + void ProcessAllControls() + { + ${replacements.process} + ${replacements.som == 'patch_sm' || replacements.som == 'petal_125b_sm' ? 'som.ProcessAllControls();' : ''} + } + + /** Handles all the maintenance processing. This should be run last within the audio callback. + * + */ + void PostProcess() + { + ${replacements.postprocess} + ${replacements.som == 'petal_125b_sm' ? 'som.UpdateLeds();' : ''} + } + + /** Handles processing that shouldn't occur in the audio block, such as blocking transfers + * + */ + void LoopProcess() + { + ${replacements.loopprocess} + } + + /** Handles display-related processing + * + */ + void Display() + { + + } + + /** Sets the audio sample rate + * \\param sample_rate the new sample rate in Hz + */ + void SetAudioSampleRate(size_t sample_rate) + { + ${som == 'patch_sm' ? 'som.SetAudioSampleRate(sample_rate);' : + `daisy::SaiHandle::Config::SampleRate enum_rate; + if (sample_rate >= 96000) + enum_rate = daisy::SaiHandle::Config::SampleRate::SAI_96KHZ; + else if (sample_rate >= 48000) + enum_rate = daisy::SaiHandle::Config::SampleRate::SAI_48KHZ; + else if (sample_rate >= 32000) + enum_rate = daisy::SaiHandle::Config::SampleRate::SAI_32KHZ; + else if (sample_rate >= 16000) + enum_rate = daisy::SaiHandle::Config::SampleRate::SAI_16KHZ; + else + enum_rate = daisy::SaiHandle::Config::SampleRate::SAI_8KHZ; + som.SetAudioSampleRate(enum_rate); + `} + ${replacements.hidupdaterates} + } + + /** Sets the audio sample rate + * \\param sample_rate the new sample rate as an enum + */ + void SetAudioSampleRate(daisy::SaiHandle::Config::SampleRate sample_rate) + { + ${som == 'seed' || som == 'petal_125b_sm' ? 'som.SetAudioSampleRate(sample_rate);' : + `size_t hz_rate; + switch (sample_rate) + { + case (daisy::SaiHandle::Config::SampleRate::SAI_96KHZ): + hz_rate = 96000; + break; + default: + case (daisy::SaiHandle::Config::SampleRate::SAI_48KHZ): + hz_rate = 48000; + break; + case (daisy::SaiHandle::Config::SampleRate::SAI_32KHZ): + hz_rate = 32000; + break; + case (daisy::SaiHandle::Config::SampleRate::SAI_16KHZ): + hz_rate = 16000; + break; + case (daisy::SaiHandle::Config::SampleRate::SAI_8KHZ): + hz_rate = 8000; + break; + } + som.SetAudioSampleRate(hz_rate); + `} + ${replacements.hidupdaterates} + } + + /** Sets the audio block size + * \\param block_size the new block size in words + */ + inline void SetAudioBlockSize(size_t block_size) + { + som.SetAudioBlockSize(block_size); + } + + /** Starts up the audio callback process with the given callback + * + */ + inline void StartAudio(daisy::AudioHandle::AudioCallback cb) + { + som.StartAudio(cb); + } + + /** This is the board's "System On Module" + */ + ${replacements.som_class} som; + ${replacements.som == 'seed' ? 'daisy::AdcChannelConfig cfg[ANALOG_COUNT];' : ''} + + // I/O Components + ${replacements.comps} + ${replacements.dispdec} + + // Menu variables + int menu_click = 0, menu_hold = 0, menu_rotate = 0; +}; + +} // namespace json2daisy + +#endif // __JSON2DAISY_${replacements.name.toUpperCase()}_H__ +`; + + let audio_info = target.audio || null; + let audio_channels = audio_info != null ? audio_info.channels || 2 : 2; + + let board_info = { + header: header, + name: target.name, + components: components, + aliases: target.aliases, + channels: audio_channels, + includes: include_paths, + }; + + return board_info; + +} \ No newline at end of file diff --git a/source/libdaisy b/source/libdaisy index 121074f..c6887c6 160000 --- a/source/libdaisy +++ b/source/libdaisy @@ -1 +1 @@ -Subproject commit 121074f7fb4503fc4f81b0f4cf112208991c15ea +Subproject commit c6887c69c9d38a54bc5ba15bc89e4c2256c04895 diff --git a/source/oopsy.js b/source/oopsy.js index cead987..2ecb64d 100755 --- a/source/oopsy.js +++ b/source/oopsy.js @@ -39,6 +39,9 @@ const fs = require("fs"), assert = require("assert"); const {exec, execSync, spawn} = require("child_process"); +const json2daisy = require(path.join(__dirname, "json2daisy.js")); +const daisy_glue = require(path.join(__dirname, "daisy_glue.js")); + // returns the path `str` with posix path formatting: function posixify_path(str) { return str.split(path.sep).join(path.posix.sep); @@ -469,6 +472,7 @@ function run() { case "versio": target = arg; break; case "bluemchen": target_path = path.join(__dirname, "seed.bluemchen.json"); break; case "nehcmeulb": target_path = path.join(__dirname, "seed.nehcmeulb.json"); break; + case "cosmolab": target_path = path.join(__dirname, "cosmolab.json"); break; case "watch": watch=true; break; @@ -536,23 +540,45 @@ function run() { let OOPSY_TARGET_SEED = 0 + let valid_soms = ['seed', 'patch_sm', 'petal_125b_sm']; + let valid_app_type = ['BOOT_NONE', 'BOOT_SRAM', 'BOOT_QPSI']; + let som = 'seed'; + + let old_json = false; + // configure target: if (!target && !target_path) target = "patch"; if (!target_path) { target_path = path.join(__dirname, `daisy.${target}.json`); + old_json = true; } else { - OOPSY_TARGET_SEED = 1 + // TODO -- should a seed target even really exist? Custom boards + // (and even prototypes / breadboards) should really be defined with a JSON file + // OOPSY_TARGET_SEED = 1 target = path.parse(target_path).name.replace(".", "_") + // som_match = path.parse(target_path).name.match(/([A-Za-z_0-9\-]+)\./) + // assert(som_match != null, `Daisy SOM undefined. Provide the SOM as in the following: "som.MyBoard.json"`); + // assert(valid_soms.includes(som_match[1]), `unkown SOM ${som_match[1]}. Valid SOMs: ${valid_soms.join(', ')}`); + // som = som_match[1]; } console.log(`Target ${target} configured in path ${target_path}`) assert(fs.existsSync(target_path), `couldn't find target configuration file ${target_path}`); const hardware = JSON.parse(fs.readFileSync(target_path, "utf8")); hardware.max_apps = hardware.max_apps || 1 + // hardware.som = som; + + // Ensure som is valid + assert(valid_soms.includes(hardware.som), `unkown SOM ${hardware.som}. Valid SOMs: ${valid_soms.join(', ')}`); + + // ensure app type is valid + hardware.app_type = hardware.app_type || "BOOT_NONE"; + assert(valid_app_type.includes(hardware.app_type), `unkown app type ${hardware.app_type}. Valid types: ${valid_app_type.join(', ')}`); // The following is compatibility code, so that the new JSON structure will generate the old JSON structure // At the point that the old one can be retired (because e.g. Patch, Petal etc can be defined in the new format) // this script should be revised to eliminate the old workflow { + hardware.som = hardware.som || "seed"; hardware.inputs = hardware.inputs || {} hardware.outputs = hardware.outputs || {} hardware.datahandlers = hardware.datahandlers || {} @@ -565,33 +591,47 @@ function run() { hardware.defines = hardware.defines || {} hardware.struct = ""; + let tempname = hardware.name; + hardware.name = ''; + let board_info = json2daisy.generate_header(hardware, target_path); + hardware.name = tempname; + + hardware.struct = board_info.header; + hardware.components = board_info.components; + hardware.aliases = board_info.aliases; + hardware.includes = board_info.includes; + if (hardware.components) { - hardware.struct = generate_target_struct(hardware); // generate IO - for (let component of hardware.components) { - + for (let comp in hardware.components) { + let component = hardware.components[comp]; // meta-elements are handled separately if (component.meta) { - + } else { // else it is available for gen mapping: for (let mapping of component.mapping) { - let name = template(mapping.name, component); + // let name = template(mapping.name, component); + component.class_name = 'hardware'; + component.name_upper = component.name.toUpperCase(); + let name = json2daisy.format_map(mapping.name, component); + component.value = name; if (mapping.get) { // an input hardware.inputs[name] = { - code: template(mapping.get, component), + code: json2daisy.format_map(mapping.get, component), automap: component.automap && name == component.name, range: mapping.range, - where: mapping.where + where: mapping.where, + permit_scale: mapping.permit_scale != undefined ? mapping.permit_scale : true } hardware.labels.params[name] = name } if (mapping.set) { // an output hardware.outputs[name] = { - code: template(mapping.set, component), + code: json2daisy.format_map(mapping.set, component), automap: component.automap && name == component.name, range: mapping.range, where: mapping.where || "audio" @@ -603,6 +643,9 @@ function run() { } } + if (old_json) + hardware.defines.OOPSY_OLD_JSON = 1 + for (let alias in hardware.aliases) { let map = hardware.aliases[alias] if (hardware.labels.params[map]) hardware.labels.params[alias] = map @@ -685,11 +728,26 @@ function run() { const makefile_path = path.join(build_path, `Makefile`) const bin_path = path.join(build_path, "build", build_name+".bin"); const maincpp_path = path.join(build_path, `${build_name}_${target}.cpp`); + const includes = (hardware.includes || []).map( + item => `-I"${posixify_path(path.relative(build_path, item))}"`); + + // Add petal_sm source only if needed + let cpp_sources = posixify_path(path.relative(build_path, maincpp_path).replace(" ", "\\ ")); + let cpp_includes = `-I"${posixify_path(path.relative(build_path, path.join(__dirname, "gen_dsp")))}"`; + + if (hardware.som == 'petal_125b_sm') { + cpp_sources += ` \\\n${posixify_path(path.relative(build_path, path.join(__dirname, "petal_sm", "daisy_petal_125b_sm.cpp")))}`; + cpp_includes += ` \\\n-I${posixify_path(path.relative(build_path, path.join(__dirname, "petal_sm")))}`; + } + fs.writeFileSync(makefile_path, ` # Project Name TARGET = ${build_name} +# App type +APP_TYPE = ${hardware.app_type || "BOOT_NONE"} # Sources -- note, won't work with paths with spaces -CPP_SOURCES = ${posixify_path(path.relative(build_path, maincpp_path).replace(" ", "\\ "))} +CPP_SOURCES = ${cpp_sources} +${includes.length > 0 ? `C_INCLUDES = ${includes.join('\\\n')}` : ``} # Library Locations LIBDAISY_DIR = ${(posixify_path(path.relative(build_path, path.join(__dirname, "libdaisy"))).replace(" ", "\\ "))} ${hardware.defines.OOPSY_TARGET_USES_SDMMC ? `USE_FATFS = 1`:``} @@ -699,7 +757,7 @@ OPT = -O3 SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core include $(SYSTEM_FILES_DIR)/Makefile # Include the gen_dsp files -CFLAGS+=-I"${posixify_path(path.relative(build_path, path.join(__dirname, "gen_dsp")))}" +CFLAGS+=${cpp_includes} # Silence irritating warnings: CFLAGS+=-O3 -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable CPPFLAGS+=-O3 -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable @@ -746,6 +804,8 @@ ${Object.keys(hardware.defines).map(k=>` #define ${k} (${hardware.defines[k]})`).join("")} ${hardware.struct} +using json2daisy::Daisy; + ${hardware.inserts.filter(o => o.where == "header").map(o => o.code).join("\n")} #include "../genlib_daisy.h" #include "../genlib_daisy.cpp" @@ -1550,7 +1610,11 @@ struct App_${name} : public oopsy::App { #ifdef OOPSY_TARGET_PATCH_SM daisy.gen = ${name}::create(daisy.hardware.AudioSampleRate(), daisy.hardware.AudioBlockSize()); #else + #ifdef OOPSY_OLD_JSON daisy.gen = ${name}::create(daisy.hardware.seed.AudioSampleRate(), daisy.hardware.seed.AudioBlockSize()); + #else + daisy.gen = ${name}::create(daisy.som->AudioSampleRate(), daisy.som->AudioBlockSize()); + #endif #endif ${name}::State& gen = *(${name}::State *)daisy.gen; @@ -1682,17 +1746,19 @@ struct App_${name} : public oopsy::App { .map(node=>` ${interpolate(node.config.code, node)}`).join("")} ${defines.OOPSY_TARGET_USES_MIDI_UART ? ` - while(daisy.uart.Readable()) { - uint8_t byte = daisy.uart.PopRx(); + // Read MIDI bytes from DMA buffer (libdaisy v8.0.0) + while(daisy.midi_rx_read_idx != daisy.midi_rx_write_idx) { + uint8_t byte = daisy.midi_rx_buffer[daisy.midi_rx_read_idx]; + daisy.midi_rx_read_idx = (daisy.midi_rx_read_idx + 1) % 256; if (byte >= 128) { // status byte ${gen.params .map(name=>nodes[name]) .filter(node => node.where == "midi_status") .map(node=>node.code) - .concat(`if (byte == 0xFF) { // reset event -> go to bootloader + .concat(` if (byte == 0xFF) { // reset event -> go to bootloader daisy.log("reboot"); - daisy::System::ResetToBootloader(); - } + daisy::System::ResetToBootloader(daisy::System::BootloaderMode::STM); + } if (byte <= 240 || byte == 247) { daisy.midi.status = byte; daisy.midi.lastbyte = 255; // means 'no bytes received' diff --git a/templates/oopsy_cosmolab.maxpat b/templates/oopsy_cosmolab.maxpat new file mode 100644 index 0000000..215d52d --- /dev/null +++ b/templates/oopsy_cosmolab.maxpat @@ -0,0 +1,625 @@ +{ + "patcher" : { + "fileversion" : 1, + "appversion" : { + "major" : 8, + "minor" : 1, + "revision" : 11, + "architecture" : "x64", + "modernui" : 1 + } +, + "classnamespace" : "box", + "rect" : [ 647.0, 204.0, 546.0, 483.0 ], + "bglocked" : 0, + "openinpresentation" : 0, + "default_fontsize" : 12.0, + "default_fontface" : 0, + "default_fontname" : "Arial", + "gridonopen" : 1, + "gridsize" : [ 15.0, 15.0 ], + "gridsnaponopen" : 1, + "objectsnaponopen" : 1, + "statusbarvisible" : 2, + "toolbarvisible" : 1, + "lefttoolbarpinned" : 0, + "toptoolbarpinned" : 0, + "righttoolbarpinned" : 0, + "bottomtoolbarpinned" : 0, + "toolbars_unpinned_last_save" : 0, + "tallnewobj" : 0, + "boxanimatetime" : 200, + "enablehscroll" : 1, + "enablevscroll" : 1, + "devicewidth" : 0.0, + "description" : "", + "digest" : "", + "tags" : "", + "style" : "", + "subpatcher_template" : "oopsy_cosmolab", + "assistshowspatchername" : 0, + "boxes" : [ { + "box" : { + "args" : [ "cosmolab" ], + "bgmode" : 0, + "border" : 0, + "clickthrough" : 0, + "enablehscroll" : 0, + "enablevscroll" : 0, + "id" : "obj-1", + "lockeddragscroll" : 0, + "maxclass" : "bpatcher", + "name" : "oopsy.maxpat", + "numinlets" : 1, + "numoutlets" : 0, + "offset" : [ 0.0, 0.0 ], + "patching_rect" : [ 215.0, 230.0, 171.0, 171.0 ], + "viewvisibility" : 1 + } + + } +, { + "box" : { + "id" : "obj-2", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 18.0, 69.0, 325.0, 20.0 ], + "text" : "Template for Cosmolab by Faselunare" + } + + } +, { + "box" : { + "fontname" : "Arial Italic", + "fontsize" : 18.0, + "id" : "obj-24", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 18.0, 34.715827338129543, 325.0, 27.0 ], + "text" : "Template for Cosmolab" + } + + } +, { + "box" : { + "id" : "obj-13", + "maxclass" : "ezdac~", + "numinlets" : 2, + "numoutlets" : 0, + "patching_rect" : [ 40.0, 385.0, 45.0, 45.0 ] + } + + } +, { + "box" : { + "id" : "obj-11", + "lastchannelcount" : 0, + "maxclass" : "live.gain~", + "numinlets" : 2, + "numoutlets" : 5, + "outlettype" : [ "signal", "signal", "", "float", "list" ], + "parameter_enable" : 1, + "patching_rect" : [ 40.0, 279.0, 44.0, 90.0 ], + "saved_attribute_attributes" : { + "valueof" : { + "parameter_longname" : "live.gain~", + "parameter_mmax" : 6.0, + "parameter_mmin" : -70.0, + "parameter_shortname" : "live.gain~", + "parameter_type" : 0, + "parameter_unitstyle" : 4 + } + + } +, + "varname" : "live.gain~" + } + + } +, { + "box" : { + "fontsize" : 24.0, + "id" : "obj-3", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 2, + "outlettype" : [ "signal", "signal" ], + "patcher" : { + "fileversion" : 1, + "appversion" : { + "major" : 8, + "minor" : 1, + "revision" : 11, + "architecture" : "x64", + "modernui" : 1 + } +, + "classnamespace" : "dsp.gen", + "rect" : [ 84.0, 102.0, 587.0, 553.0 ], + "bglocked" : 0, + "openinpresentation" : 0, + "default_fontsize" : 12.0, + "default_fontface" : 0, + "default_fontname" : "Arial", + "gridonopen" : 1, + "gridsize" : [ 15.0, 15.0 ], + "gridsnaponopen" : 1, + "objectsnaponopen" : 1, + "statusbarvisible" : 2, + "toolbarvisible" : 1, + "lefttoolbarpinned" : 0, + "toptoolbarpinned" : 0, + "righttoolbarpinned" : 0, + "bottomtoolbarpinned" : 0, + "toolbars_unpinned_last_save" : 0, + "tallnewobj" : 0, + "boxanimatetime" : 200, + "enablehscroll" : 1, + "enablevscroll" : 1, + "devicewidth" : 0.0, + "description" : "", + "digest" : "", + "tags" : "", + "style" : "", + "subpatcher_template" : "", + "assistshowspatchername" : 0, + "boxes" : [ { + "box" : { + "id" : "obj-19", + "maxclass" : "newobj", + "numinlets" : 3, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 192.5, 367.0, 53.0, 22.0 ], + "text" : "mix" + } + + } +, { + "box" : { + "id" : "obj-18", + "maxclass" : "newobj", + "numinlets" : 3, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 137.5, 367.0, 53.0, 22.0 ], + "text" : "mix" + } + + } +, { + "box" : { + "id" : "obj-16", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 269.5, 333.0, 172.0, 22.0 ], + "text" : "param knob2 @min 0 @max 1" + } + + } +, { + "box" : { + "id" : "obj-17", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 254.5, 309.0, 172.0, 22.0 ], + "text" : "param knob1 @min 0 @max 1" + } + + } +, { + "box" : { + "id" : "obj-14", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 350.25, 245.0, 103.0, 22.0 ], + "text" : "scale 0 1 400 800" + } + + } +, { + "box" : { + "id" : "obj-15", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 350.25, 219.0, 158.0, 22.0 ], + "text" : "param cv2 @min 0 @max 1" + } + + } +, { + "box" : { + "id" : "obj-13", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "" ], + "patching_rect" : [ 209.5, 277.0, 36.0, 22.0 ], + "text" : "cycle" + } + + } +, { + "box" : { + "id" : "obj-10", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "" ], + "patching_rect" : [ 154.5, 277.0, 36.0, 22.0 ], + "text" : "cycle" + } + + } +, { + "box" : { + "id" : "obj-8", + "maxclass" : "newobj", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 229.5, 213.0, 103.0, 22.0 ], + "text" : "scale 0 1 400 800" + } + + } +, { + "box" : { + "id" : "obj-58", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 29.5, 395.0, 69.0, 20.0 ], + "text" : "OUTPUTS" + } + + } +, { + "box" : { + "id" : "obj-59", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 29.5, 141.0, 69.0, 20.0 ], + "text" : "INPUTS" + } + + } +, { + "box" : { + "id" : "obj-2", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 229.5, 187.0, 158.0, 22.0 ], + "text" : "param cv1 @min 0 @max 1" + } + + } +, { + "box" : { + "id" : "obj-11", + "maxclass" : "comment", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 137.5, 106.0, 65.0, 20.0 ], + "text" : "Audio IOs" + } + + } +, { + "box" : { + "id" : "obj-67", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 192.5, 141.0, 28.0, 22.0 ], + "text" : "in 2" + } + + } +, { + "box" : { + "id" : "obj-5", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 192.5, 395.0, 35.0, 22.0 ], + "text" : "out 2" + } + + } +, { + "box" : { + "id" : "obj-1", + "maxclass" : "newobj", + "numinlets" : 0, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 137.5, 141.0, 28.0, 22.0 ], + "text" : "in 1" + } + + } +, { + "box" : { + "id" : "obj-68", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 0, + "patching_rect" : [ 137.5, 395.0, 35.0, 22.0 ], + "text" : "out 1" + } + + } + ], + "lines" : [ { + "patchline" : { + "destination" : [ "obj-18", 0 ], + "source" : [ "obj-1", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-18", 1 ], + "source" : [ "obj-10", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-19", 1 ], + "source" : [ "obj-13", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-13", 0 ], + "source" : [ "obj-14", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-14", 0 ], + "source" : [ "obj-15", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-19", 2 ], + "source" : [ "obj-16", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-18", 2 ], + "source" : [ "obj-17", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-68", 0 ], + "source" : [ "obj-18", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-5", 0 ], + "source" : [ "obj-19", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-8", 0 ], + "source" : [ "obj-2", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-19", 0 ], + "source" : [ "obj-67", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-10", 0 ], + "source" : [ "obj-8", 0 ] + } + + } + ] + } +, + "patching_rect" : [ 40.0, 230.0, 144.0, 35.0 ], + "saved_object_attributes" : { + "exportfolder" : "macOS:/Users/corvus/Documents/Max 8/Packages/Oopsy/templates/", + "exportname" : "oopsy_cosmolab" + } +, + "text" : "gen~", + "varname" : "oopsy_cosmolab" + } + + } +, { + "box" : { + "attr" : "knob1", + "id" : "obj-4", + "maxclass" : "attrui", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 40.0, 135.0, 150.0, 22.0 ] + } + + } +, { + "box" : { + "attr" : "knob2", + "id" : "obj-5", + "maxclass" : "attrui", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 193.0, 135.0, 150.0, 22.0 ] + } + + } +, { + "box" : { + "attr" : "cv1", + "id" : "obj-6", + "maxclass" : "attrui", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 40.0, 159.0, 150.0, 22.0 ] + } + + } +, { + "box" : { + "attr" : "cv2", + "id" : "obj-7", + "maxclass" : "attrui", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 193.0, 159.0, 150.0, 22.0 ] + } + + } + ], + "lines" : [ { + "patchline" : { + "destination" : [ "obj-13", 1 ], + "source" : [ "obj-11", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-13", 0 ], + "source" : [ "obj-11", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-11", 1 ], + "source" : [ "obj-3", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-11", 0 ], + "source" : [ "obj-3", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-3", 0 ], + "source" : [ "obj-4", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-3", 0 ], + "source" : [ "obj-5", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-3", 0 ], + "source" : [ "obj-6", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-3", 0 ], + "source" : [ "obj-7", 0 ] + } + + } + ], + "parameters" : { + "obj-11" : [ "live.gain~", "live.gain~", 0 ], + "obj-1::obj-32" : [ "live.text[6]", "FILTER", 0 ], + "obj-1::obj-33" : [ "live.text[4]", "FILTER", 0 ], + "obj-1::obj-34" : [ "live.text[5]", "FILTER", 0 ], + "parameterbanks" : { + + } +, + "parameter_overrides" : { + "obj-1::obj-32" : { + "parameter_longname" : "live.text[6]" + } +, + "obj-1::obj-33" : { + "parameter_longname" : "live.text[4]" + } +, + "obj-1::obj-34" : { + "parameter_longname" : "live.text[5]" + } + + } +, + "inherited_shortname" : 1 + } +, + "dependency_cache" : [ { + "name" : "oopsy.maxpat", + "bootpath" : "~/Documents/Max 8/Packages/Oopsy/patchers", + "patcherrelativepath" : "../patchers", + "type" : "JSON", + "implicit" : 1 + } +, { + "name" : "oopsy.snoop.js", + "bootpath" : "~/Documents/Max 8/Packages/Oopsy/javascript", + "patcherrelativepath" : "../javascript", + "type" : "TEXT", + "implicit" : 1 + } +, { + "name" : "oopsy.node4max.js", + "bootpath" : "~/Documents/Max 8/Packages/Oopsy/javascript", + "patcherrelativepath" : "../javascript", + "type" : "TEXT", + "implicit" : 1 + } + ], + "autosave" : 0 + } + +} +