From 8503e38fea1bffdbb1b5d6c798513fe39e0d9a18 Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 02:07:41 +0100 Subject: [PATCH 01/21] Add Cosmolab board support - Add cosmolab.json board configuration - Add cosmolab case in oopsy.js - Add cosmolab mapping in oopsy-objectmappings.txt - Add cosmolab to board selection menu - Add oopsy_cosmolab.maxpat template --- init/oopsy-objectmappings.txt | 2 + patchers/oopsy.maxpat | 2 +- source/cosmolab.json | 585 ++++++++++++++++++++++++++++++ source/oopsy.js | 1 + templates/oopsy_cosmolab.maxpat | 625 ++++++++++++++++++++++++++++++++ 5 files changed, 1214 insertions(+), 1 deletion(-) create mode 100644 source/cosmolab.json create mode 100644 templates/oopsy_cosmolab.maxpat 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.json b/source/cosmolab.json new file mode 100644 index 0000000..2a002d8 --- /dev/null +++ b/source/cosmolab.json @@ -0,0 +1,585 @@ +{ + "vendor": "faselunare", + "name": "cosmolab", + "version": "0.1.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" + }, + "cvout1": { + "component": "CVOuts" + }, + "cvout2": { + "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/oopsy.js b/source/oopsy.js index cead987..204ff41 100755 --- a/source/oopsy.js +++ b/source/oopsy.js @@ -469,6 +469,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; 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 + } + +} + From 7a7b56382aa538d6b297cee03aac94f0e7f966b4 Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 02:08:03 +0100 Subject: [PATCH 02/21] Add .DS_Store to gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 521fbc2..c4cbe93 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,4 @@ help/*.h help/*.json templates/*.cpp templates/*.h -templates/*.json \ No newline at end of file +templates/*.json.DS_Store From 2373da18d944d9a788753c4583039ec124d37c38 Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 22:59:16 +0100 Subject: [PATCH 03/21] Fix .gitignore: properly ignore .DS_Store files --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c4cbe93..7065f53 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,7 @@ help/*.h help/*.json templates/*.cpp templates/*.h -templates/*.json.DS_Store +templates/*.json +.DS_Store +*.DS_Store +**/.DS_Store From 0843b3a36d8b5924ed5511fc438b560e80f5b5b6 Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:20:21 +0100 Subject: [PATCH 04/21] Update libdaisy submodule to v8.0.0 --- source/libdaisy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From e3a052d55057d26b8586c6df831464fa8f6cea5c Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:25:50 +0100 Subject: [PATCH 05/21] Add missing component definitions and json2daisy files - Add component_defs.json with CD4021Switch and other component definitions - Add component_defs_patchsm.json and component_defs_petalsm.json - Add json2daisy.js for JSON-based board configuration - Add daisy_glue.js helper functions These files are required for cosmolab.json and other JSON-based board configurations. --- source/component_defs.json | 1467 ++++++++++++++++++++++++++++ source/component_defs_patchsm.json | 262 +++++ source/component_defs_petalsm.json | 154 +++ source/daisy_glue.js | 255 +++++ source/json2daisy.js | 608 ++++++++++++ 5 files changed, 2746 insertions(+) create mode 100644 source/component_defs.json create mode 100644 source/component_defs_patchsm.json create mode 100644 source/component_defs_petalsm.json create mode 100644 source/daisy_glue.js create mode 100644 source/json2daisy.js diff --git a/source/component_defs.json b/source/component_defs.json new file mode 100644 index 0000000..762b004 --- /dev/null +++ b/source/component_defs.json @@ -0,0 +1,1467 @@ +{ + "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::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(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": "dsy_gpio_pin {name}_pin = som.GetPin({pin});\n {name}.Init(&{name}_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": "{name}.pin = som.GetPin({pin});\n {name}.mode = {mode};\n {name}.pull = {pull};\n dsy_gpio_init(&{name});", + "typename": "dsy_gpio", + "direction": "out", + "pin": "a", + "mode": "DSY_GPIO_MODE_OUTPUT_PP", + "pull": "DSY_GPIO_NOPULL", + "mapping": [ + { + "name": "{name}", + "set": "dsy_gpio_write(&{class_name}.{name}, {value});" + } + ] + }, + "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 + } + ] + }, + "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/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/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 From 0c9e48231e8918a54f555fc55ee0961e4fa00541 Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:27:05 +0100 Subject: [PATCH 06/21] Update oopsy.js to use json2daisy for component definitions - Add require statements for json2daisy.js and daisy_glue.js - Replace generate_target_struct with json2daisy.generate_header - Use json2daisy.format_map for component mapping - Add 'using json2daisy::Daisy;' in generated C++ code - Add hardware.includes support in Makefile - Add APP_TYPE and petal_sm support in Makefile This enables support for CD4021Switch and other components defined in component_defs.json --- source/oopsy.js | 62 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/source/oopsy.js b/source/oopsy.js index 204ff41..ac8a12f 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); @@ -537,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 || {} @@ -566,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" @@ -604,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 @@ -747,6 +789,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" From 2d1ff05648fef696a175450307b9fccf9f98070f Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:27:16 +0100 Subject: [PATCH 07/21] Fix Makefile generation: add hardware.includes and APP_TYPE support --- source/oopsy.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source/oopsy.js b/source/oopsy.js index ac8a12f..2fa3718 100755 --- a/source/oopsy.js +++ b/source/oopsy.js @@ -728,11 +728,17 @@ 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))}"`); 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 = ${posixify_path(path.relative(build_path, maincpp_path).replace(" ", "\\ "))} \\ +${posixify_path(path.relative(build_path, path.join(__dirname, "petal_sm", "daisy_petal_125b_sm.cpp")))} +${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`:``} @@ -742,7 +748,8 @@ 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+=-I"${posixify_path(path.relative(build_path, path.join(__dirname, "gen_dsp")))}" \\ +-I${posixify_path(path.relative(build_path, path.join(__dirname, "petal_sm")))} # 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 From e913e647ef68afde0f97a6bcf8482c9757d6c009 Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:30:09 +0100 Subject: [PATCH 08/21] Update genlib_daisy.h and component_defs.json for libdaisy v8.0.0 compatibility - Update genlib_daisy.h to use hardware.som instead of hardware.seed - Replace sub_board with som pointer - Update component_defs.json to use GPIO::Pull::PULLUP instead of Switch::PULL_UP - Add support for OOPSY_OLD_JSON flag for backward compatibility --- source/genlib_daisy.h | 104 ++++++++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 40 deletions(-) diff --git a/source/genlib_daisy.h b/source/genlib_daisy.h index 527c7f7..7be7646 100644 --- a/source/genlib_daisy.h +++ b/source/genlib_daisy.h @@ -39,6 +39,15 @@ 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 ////////////////////////// #define OOPSY_MIDI_BUFFER_SIZE (1024) @@ -46,7 +55,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 @@ -61,7 +70,10 @@ namespace oopsy { void init() { if (!sram_pool) sram_pool = (char *)malloc(OOPSY_SRAM_SIZE); - sram_usable = 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; @@ -149,10 +161,18 @@ namespace oopsy { struct GenDaisy { Daisy hardware; - #ifdef OOPSY_TARGET_PATCH_SM - Daisy *sub_board = &hardware; + #ifdef OOPSY_SOM_PETAL_SM + daisy::Petal125BSM *som = &hardware.som; #else - daisy::DaisySeed *sub_board = &hardware.seed; + #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; @@ -397,7 +417,7 @@ namespace oopsy { mainloopCallback = nullMainloopCallback; displayCallback = nullMainloopCallback; nullAudioCallbackRunning = false; - sub_board->ChangeAudioCallback(nullAudioCallback); + som->ChangeAudioCallback(nullAudioCallback); while (!nullAudioCallbackRunning) daisy::System::Delay(1); // reset memory oopsy::init(); @@ -411,9 +431,9 @@ namespace oopsy { paramCallback = newapp.staticParamCallback; #endif - sub_board->ChangeAudioCallback(newapp.staticAudioCallback); + som->ChangeAudioCallback(newapp.staticAudioCallback); log("gen~ %s", appdefs[app_selected].name); - log("SR %dkHz / %dHz", (int)(sub_board->AudioSampleRate()/1000), (int)sub_board->AudioCallbackRate()); + 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, @@ -458,9 +478,9 @@ namespace oopsy { mode = 0; #ifdef OOPSY_USE_USB_SERIAL_INPUT - sub_board->usb.Init(daisy::UsbHandle::FS_INTERNAL); + som->usb.Init(daisy::UsbHandle::FS_INTERNAL); daisy::System::Delay(500); - sub_board->usb.SetReceiveCallback(UsbCallback, daisy::UsbHandle::FS_INTERNAL); + som->usb.SetReceiveCallback(UsbCallback, daisy::UsbHandle::FS_INTERNAL); #endif #ifdef OOPSY_USE_LOGGING @@ -483,8 +503,7 @@ namespace oopsy { console_line = console_rows-1; #endif - sub_board->adc.Start(); - sub_board->StartAudio(nullAudioCallback); + som->StartAudio(nullAudioCallback); mainloopCallback = nullMainloopCallback; displayCallback = nullMainloopCallback; @@ -524,7 +543,9 @@ namespace oopsy { t = t1; // pulse seed LED for status according to CPU usage: - sub_board->SetLed((t % 1000)/10 <= uint32_t(audioCpuUsage)); + #ifndef OOPSY_SOM_PETAL_SM + som->SetLed((t % 1000)/10 <= uint32_t(audioCpuUsage)); + #endif if (app_load_scheduled) { app_load_scheduled = 0; @@ -552,7 +573,7 @@ namespace oopsy { if (uitimer.ready(dt)) { #ifdef OOPSY_USE_LOGGING - sub_board->PrintLine("the time is"FLT_FMT3"", FLT_VAR3(t/1000.f)); + som->PrintLine("the time is"FLT_FMT3"", FLT_VAR3(t/1000.f)); #endif #ifdef OOPSY_USE_USB_SERIAL_INPUT if(update && rx_size > 0) { @@ -567,7 +588,7 @@ namespace oopsy { hardware.display.Fill(false); #endif #ifdef OOPSY_TARGET_PETAL - hardware.ClearLeds(); + hardware.led_driver.SetAllTo((uint8_t) 0); #endif if (menu_button_held_ms > OOPSY_LONG_PRESS_MS) { @@ -582,7 +603,8 @@ namespace oopsy { #endif for(int i = 0; i < 8; i++) { float white = (i == app_selecting || menu_button_released); - hardware.SetRingLed((daisy::DaisyPetal::RingLed)i, + + 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 @@ -590,22 +612,22 @@ namespace oopsy { } #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 + // #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) { @@ -797,7 +819,7 @@ namespace oopsy { } break; case SCOPEOPTION_ZOOM: { // each pixel is zoom samples; zoom/samplerate seconds - float scope_duration = OOPSY_OLED_DISPLAY_WIDTH*(1000.f*zoomlevel/sub_board->AudioSampleRate()); + float scope_duration = OOPSY_OLED_DISPLAY_WIDTH*(1000.f*zoomlevel/som->AudioSampleRate()); 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); @@ -839,7 +861,7 @@ namespace oopsy { #endif //OOPSY_TARGET_HAS_OLED #if (OOPSY_TARGET_PETAL) - hardware.UpdateLeds(); + hardware.led_driver.SwapBuffersAndTransmit(); #endif //(OOPSY_TARGET_PETAL) } // uitimer.ready @@ -868,9 +890,9 @@ namespace oopsy { 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.GetSwitch(1)->FallingEdge(); - menu_button_held_ms = hardware.GetSwitch(0)->TimeHeldMs(); - if (hardware.GetSwitch(0)->FallingEdge()) menu_button_released = 1; + 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; @@ -1032,16 +1054,18 @@ namespace oopsy { } #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; + if (idx > 7 && idx < 16) + idx = 23 - i; hardware.led_driver.SetLed(idx, data.read(i, 0)); } - hardware.led_driver.SwapBuffersAndTransmit(); + // hardware.led_driver.SwapBuffersAndTransmit(); // now handled by hardware class }; #endif @@ -1087,7 +1111,7 @@ namespace oopsy { 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(); + 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 From c20c342ed83bd5ff75f215967fb0fa5b96a41fa1 Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:33:13 +0100 Subject: [PATCH 09/21] Update UartHandler API for libdaisy v8.0.0 - Replace StartRx() with DmaListenStart() for MIDI reception - Replace Readable()/PopRx() with DMA buffer reading - Update Pin syntax from {DSY_GPIOB, 7} to Pin(DSY_GPIOB, 7) - Add MidiRxCallback for DMA circular buffer handling --- source/genlib_daisy.h | 14 +++++++++++--- source/oopsy.js | 6 ++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/source/genlib_daisy.h b/source/genlib_daisy.h index 7be7646..6c6b878 100644 --- a/source/genlib_daisy.h +++ b/source/genlib_daisy.h @@ -282,6 +282,12 @@ namespace oopsy { 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 @@ -524,10 +530,12 @@ namespace oopsy { 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}; + config.pin_config.rx = daisy::Pin(daisy::DSY_GPIOB, 7); + config.pin_config.tx = daisy::Pin(daisy::DSY_GPIOB, 6); uart.Init(config); - uart.StartRx(); + // Use DMA listen mode for MIDI reception in v8.0.0 + static uint8_t midi_rx_buffer[256]; + uart.DmaListenStart(midi_rx_buffer, 256, MidiRxCallback, this); #endif app_selected = 0; diff --git a/source/oopsy.js b/source/oopsy.js index 2fa3718..e052b33 100755 --- a/source/oopsy.js +++ b/source/oopsy.js @@ -1734,8 +1734,10 @@ 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]) From 8208385eda8e650bdee36797c4733967709fd1aa Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:34:14 +0100 Subject: [PATCH 10/21] Add MIDI RX callback implementation for libdaisy v8.0.0 DMA listen mode --- source/genlib_daisy.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/source/genlib_daisy.h b/source/genlib_daisy.h index 6c6b878..714d22e 100644 --- a/source/genlib_daisy.h +++ b/source/genlib_daisy.h @@ -1090,6 +1090,24 @@ namespace oopsy { } } + #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 From b2f810290322de8a90f25271e5b3428f730673cd Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:34:27 +0100 Subject: [PATCH 11/21] Fix: use class static midi_rx_buffer instead of local variable --- source/genlib_daisy.h | 1 - 1 file changed, 1 deletion(-) diff --git a/source/genlib_daisy.h b/source/genlib_daisy.h index 714d22e..2ca5c05 100644 --- a/source/genlib_daisy.h +++ b/source/genlib_daisy.h @@ -534,7 +534,6 @@ namespace oopsy { config.pin_config.tx = daisy::Pin(daisy::DSY_GPIOB, 6); uart.Init(config); // Use DMA listen mode for MIDI reception in v8.0.0 - static uint8_t midi_rx_buffer[256]; uart.DmaListenStart(midi_rx_buffer, 256, MidiRxCallback, this); #endif From 2b14b9bf6edfdbc0afef9eebdcb0041124cc21c6 Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:35:38 +0100 Subject: [PATCH 12/21] Fix component definitions for libdaisy v8.0.0 - Update GateIn: use Pin directly instead of dsy_gpio_pin pointer - Update GateOut: use GPIO class with Config instead of dsy_gpio - Update Switch: use GPIO::Pull::PULLUP instead of Switch::PULL_UP - Fix Pin constructor: use GPIOPort::B instead of DSY_GPIOB --- source/component_defs.json | 10 ++++------ source/genlib_daisy.h | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/source/component_defs.json b/source/component_defs.json index 762b004..98893c7 100644 --- a/source/component_defs.json +++ b/source/component_defs.json @@ -104,7 +104,7 @@ ] }, "GateIn": { - "map_init": "dsy_gpio_pin {name}_pin = som.GetPin({pin});\n {name}.Init(&{name}_pin, {invert});", + "map_init": "{name}.Init(som.GetPin({pin}), {invert});", "typename": "daisy::GateIn", "direction": "in", "pin": "a", @@ -198,16 +198,14 @@ ] }, "GateOut": { - "map_init": "{name}.pin = som.GetPin({pin});\n {name}.mode = {mode};\n {name}.pull = {pull};\n dsy_gpio_init(&{name});", - "typename": "dsy_gpio", + "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", - "mode": "DSY_GPIO_MODE_OUTPUT_PP", - "pull": "DSY_GPIO_NOPULL", "mapping": [ { "name": "{name}", - "set": "dsy_gpio_write(&{class_name}.{name}, {value});" + "set": "{class_name}.{name}.Write({value} > 0.f ? true : false);" } ] }, diff --git a/source/genlib_daisy.h b/source/genlib_daisy.h index 2ca5c05..695ef0f 100644 --- a/source/genlib_daisy.h +++ b/source/genlib_daisy.h @@ -530,8 +530,8 @@ namespace oopsy { 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::DSY_GPIOB, 7); - config.pin_config.tx = daisy::Pin(daisy::DSY_GPIOB, 6); + config.pin_config.rx = daisy::Pin(daisy::GPIOPort::B, 7); + config.pin_config.tx = daisy::Pin(daisy::GPIOPort::B, 6); uart.Init(config); // Use DMA listen mode for MIDI reception in v8.0.0 uart.DmaListenStart(midi_rx_buffer, 256, MidiRxCallback, this); From 335db1c7f7bf3372c71b09f2ca3f488f9325fab9 Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:35:49 +0100 Subject: [PATCH 13/21] Fix hardware.seed references and Switch PULL_UP for libdaisy v8.0.0 - Replace hardware.seed.AudioSampleRate() with som->AudioSampleRate() - Replace hardware.seed.AudioBlockSize() with som->AudioBlockSize() - Update Switch PULL_UP to GPIO::Pull::PULLUP - Add OOPSY_OLD_JSON compatibility check --- source/oopsy.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/oopsy.js b/source/oopsy.js index e052b33..e785ad3 100755 --- a/source/oopsy.js +++ b/source/oopsy.js @@ -1602,7 +1602,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; From 80177583cf0926cfe7c1d95f0a74720ae11fe15f Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:35:56 +0100 Subject: [PATCH 14/21] Fix Switch PULL_UP to GPIO::Pull::PULLUP for libdaisy v8.0.0 --- source/component_defs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/component_defs.json b/source/component_defs.json index 98893c7..394dccd 100644 --- a/source/component_defs.json +++ b/source/component_defs.json @@ -6,7 +6,7 @@ "pin": "a", "type": "daisy::Switch::TYPE_MOMENTARY", "polarity": "daisy::Switch::POLARITY_INVERTED", - "pull": "daisy::Switch::PULL_UP", + "pull": "daisy::GPIO::Pull::PULLUP", "process": "{name}.Debounce();", "updaterate": "{name}.SetUpdateRate(som.AudioCallbackRate());", "mapping": [ From dc0fb42dbb5ea3f6f7cc21203d8bbaa51befe746 Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:40:41 +0100 Subject: [PATCH 15/21] Fix GPIOPort enum: use PORTB instead of GPIOPort::B --- source/genlib_daisy.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/genlib_daisy.h b/source/genlib_daisy.h index 695ef0f..940e9a0 100644 --- a/source/genlib_daisy.h +++ b/source/genlib_daisy.h @@ -530,8 +530,8 @@ namespace oopsy { 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::GPIOPort::B, 7); - config.pin_config.tx = daisy::Pin(daisy::GPIOPort::B, 6); + 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); From 040ed200feb8fbb3903b541766fccfd51aa2e57d Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:42:29 +0100 Subject: [PATCH 16/21] Fix Makefile: only include petal_sm source when som is petal_125b_sm --- source/oopsy.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/source/oopsy.js b/source/oopsy.js index e785ad3..33a00ec 100755 --- a/source/oopsy.js +++ b/source/oopsy.js @@ -730,14 +730,23 @@ function run() { 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(" ", "\\ "))} \\ -${posixify_path(path.relative(build_path, path.join(__dirname, "petal_sm", "daisy_petal_125b_sm.cpp")))} +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(" ", "\\ "))} @@ -748,8 +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")))}" \\ --I${posixify_path(path.relative(build_path, path.join(__dirname, "petal_sm")))} +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 From 47765a3b6082c9ccfd2fe95117a593b85394b67e Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:42:35 +0100 Subject: [PATCH 17/21] Fix ResetToBootloader: add BootloaderMode parameter --- source/oopsy.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/oopsy.js b/source/oopsy.js index 33a00ec..2ecb64d 100755 --- a/source/oopsy.js +++ b/source/oopsy.js @@ -1755,10 +1755,10 @@ struct App_${name} : public oopsy::App { .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' From a6a94a9366cce06779d90b7d5566cf37dee0364c Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:52:05 +0100 Subject: [PATCH 18/21] Add comprehensive changelog for libdaisy v8.0.0 update and Cosmolab support - Document all breaking changes and API migrations - Explain why each change was necessary - Add Cosmolab board feature list - Include migration notes and disclaimer - Credit Francesco Mulassano (Faselunare) for the update --- README.md | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/README.md b/README.md index 12997bc..b98150f 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 2024) + +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) From 78c513df07af31b079e4db8bdd7e06bd6b305482 Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Fri, 5 Dec 2025 23:53:47 +0100 Subject: [PATCH 19/21] Fix date: December 2025 instead of 2024 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b98150f..fdbd4c1 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ For details of the licensing terms of code exported from gen~ see https://suppor ## Changelog -### libdaisy v8.0.0 Update (December 2024) +### 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. From a47e245a1bfea06f4e7449a0886be177fd8e146e Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Mon, 8 Dec 2025 15:29:16 +0100 Subject: [PATCH 20/21] feat: Add Cosmolab board support with libDaisy v8.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major Changes: - Added CD4021SwitchInverted component for hardware with pull-up resistors - Fixed PCA9685 LED driver support via LoopProcess() call - Updated to libDaisy v8.0.0 (from v5.x) - Added Cosmolab board definition (cosmolab.json v1.0.0) Technical Details: - LoopProcess() fix enables I2C peripherals (PCA9685 LEDs, etc.) - CD4021SwitchInverted supports inverted button polarity - Follows libDaisy POLARITY_INVERTED pattern - Fully backward compatible Hardware Support: - 32 LED-backlit buttons via CD4021 shift registers - 48 LEDs total via PCA9685 I2C drivers (32 buttons + 16 for knobs) - 8 potentiometers with dual LED feedback - 4 CV in, 2 CV out, 1 trig in, 1 trig out (Eurorack) - MIDI I/O, OLED display (64x32) Documentation: - COSMOLAB_README.md - Complete technical documentation - CHANGELOG.md - Version history - COSMOLAB_BOARDS_README.md - Board selection guide Tested: ✅ All features verified on actual Cosmolab hardware v1 Version: 1.0.0 --- .gitignore | 3 + CHANGELOG.md | 68 + COSMOLAB_README.md | 220 +++ source/COSMOLAB_BOARDS_README.md | 96 ++ source/component_defs.json | 25 + source/cosmolab.json | 15 +- source/genlib_daisy.h | 2370 ++++++++++++++++-------------- 7 files changed, 1680 insertions(+), 1117 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 COSMOLAB_README.md create mode 100644 source/COSMOLAB_BOARDS_README.md diff --git a/.gitignore b/.gitignore index 7065f53..24f65ae 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ templates/*.json .DS_Store *.DS_Store **/.DS_Store + +# Cosmolab hardware v1 (prototypes only, not for distribution) +source/cosmolab_hw_v1.json 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/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 index 394dccd..8743ba9 100644 --- a/source/component_defs.json +++ b/source/component_defs.json @@ -333,6 +333,31 @@ } ] }, + "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": "", diff --git a/source/cosmolab.json b/source/cosmolab.json index 2a002d8..d0e0220 100644 --- a/source/cosmolab.json +++ b/source/cosmolab.json @@ -1,17 +1,20 @@ { "vendor": "faselunare", "name": "cosmolab", - "version": "0.1.0", + "version": "1.0.0", "som": "seed", "defines": { "OOPSY_TARGET_HAS_OLED": 1, - "OOPSY_TARGET_HAS_MIDI_INPUT": 1, - "OOPSY_TARGET_HAS_MIDI_OUTPUT": 1 + "OOPSY_TARGET_HAS_MIDI_INPUT": 1, + "OOPSY_TARGET_HAS_MIDI_OUTPUT": 1 }, "display": { - "driver": "daisy::SSD130xI2c64x32Driver", - "dim": [64, 32] - }, + "driver": "daisy::SSD130xI2c64x32Driver", + "dim": [ + 64, + 32 + ] + }, "parents": { "i2c": { "component": "i2c", diff --git a/source/genlib_daisy.h b/source/genlib_daisy.h index 940e9a0..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,9 @@ 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 @@ -63,1112 +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); - // 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> 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; - 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; iStartAudio(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", 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.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; iPressed(); - 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 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 - - // 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(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) - }; - -}; // 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 From db072c513a6193b28521ebab5118aa4cd7a0f7ea Mon Sep 17 00:00:00 2001 From: Francesco Mulassano Date: Mon, 8 Dec 2025 23:36:05 +0100 Subject: [PATCH 21/21] Fix CV outs --- .gitignore | 3 +++ examples/knobled.maxpat | 59 +++++++++++++++++++++++++++++++++++++++++ source/cosmolab.json | 5 +--- 3 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 examples/knobled.maxpat diff --git a/.gitignore b/.gitignore index 24f65ae..a1d2079 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ templates/*.json # 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/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/source/cosmolab.json b/source/cosmolab.json index d0e0220..6b9d9b0 100644 --- a/source/cosmolab.json +++ b/source/cosmolab.json @@ -117,10 +117,7 @@ "index": 7, "parent": "pot_mux" }, - "cvout1": { - "component": "CVOuts" - }, - "cvout2": { + "cvout": { "component": "CVOuts" }, "gatein": {