Oopsy & Daisy Seed : hardware mapping

Hi everyone!
Received my Daisy Seeds a couple of days ago and am blown away by how great they are and how good the software integration is with Oopsy. Fantastic work.

I’m not yet very clear on how to setup custom JSON files inside Oopsy to map hardware elements for a barebones Seed : I’ve been able to compile and run things using the “patch” target, but the knob mappings get weird past the first two ADCs :slight_smile:

Is there any guidance or tutorial material on how to create a custom mapping target with Oopsy / its NodeJS scripts?

Thanks!

1 Like

Hi,

Custom JSON files are still a work-in-progress (Oopsy is still in beta…) so there’s no documentation on that yet. There’s probably a few assumptions in the genlib_daisy.h file and oopsy.js to work around yet. We’d need to add a “custom” target option in the Max interface, ensure genlib_daisy.h can work with the daisy_seed.h directly, and fill in the gaps of what a custom JSON can contain. Part of my challenge is not knowing what kinds of code would be needed to be added for a custom seed platform; it’s a less familiar area for me. But I’d be happy to work through ideas if you can help. I added an issue for this on the github (issue 34) – would link it here directly but the forum settings won’t let me

Graham

Hi Graham, thanks for the info and for opening the issue.
I’m haven’t really dived into the sources yet, but I’d be happy to help wherever I can.
Feel free to get in touch!
James

Looking over the scripty files involved that “compensate” for the different hardware implementations it doesn’t look too difficult to recreate one based on another.
I, for one, will be trying to kludge MIDI IN onto the Petal and a Terrarium pedal PCB.

For the folks asking about custom Seed-based projects (i.e. not Patch/Field/Pod/Petal/Versio etc.), initial support for this is now in the dev branch of the github repo. Progress is being tracked in the issue tracker (https://github.com/electro-smith/oopsy/issues/34) and wiki (https://github.com/electro-smith/oopsy/wiki/Custom-Seed-Targets).
Fair warnings: I don’t have a custom seed project to really test this on so it would have to be a community effort to work out if anything is missing and what would need to be documented. Moreover: this capacity is extremely alpha, as the JSON configuration format itself is likely to change to accommodate needs as they are found.

5 Likes

Wow, thanks! I just had a quick look into it. I’m starting to understand the mechanics of the code generation and where the JSON elements go (your wiki helps a lot), but I’m still not quite there.

If I understand correctly, I should be able to define some hardware elements (ADC configuration, etc) using the JSON “inserts” and define knob and other behaviour relying only on “daisy_seed.h”, similarly to what is done for the Seed DaisyExamples (such as Osc: https://github.com/electro-smith/DaisyExamples/blob/master/seed/Osc/Osc.cpp), without having to create a custom C equivalent to, for instance, daisy_petal.h/.cpp ?

This is what I tried to do (code below - simply setting up 3 knobs on the first 3 ADC pins), but something is wrong: the code compiles OK and flashes onto the Daisy but the knobs have no effect. Am I missing something really obvious here?

{
“defines”: {
“OOPSY_TARGET_SEED”: 1
},
“labels”: {
“params”: {
“knob1”: “kn1”,
“knob2”: “kn2”,
“knob3”: “kn3”
},
“outs”: {},
“datas”: {}
},
“inputs”: {
“kn1”: {
“automap”: true,
“code”: “hardware.seed.adc.GetFloat(0);”
},
“kn2”: {
“automap”: true,
“code”: “hardware.seed.adc.GetFloat(1);”
},
“kn3”: {
“automap”: true,
“code”: “hardware.seed.adc.GetFloat(2);”
}

},
“outputs”: {},
“datahandlers”: {},
“inserts”: [
{ “where”: “init”, “code”: “AdcChannelConfig cfg[3];” },
{ “where”: “init”, “code”: “cfg[0].InitSingle(oopsy::daisy.hardware.seed.GetPin(21));” },
{ “where”: “init”, “code”: “cfg[1].InitSingle(oopsy::daisy.hardware.seed.GetPin(22));” },
{ “where”: “init”, “code”: “cfg[2].InitSingle(oopsy::daisy.hardware.seed.GetPin(23));” },
{ “where”: “init”, “code”: “oopsy::daisy.hardware.seed.adc.Init(cfg, 3);” },
{ “where”: “header”, “code”: “#include “daisy_seed.h””},
{ “where”: “header”, “code”: “using namespace daisy;”}
],
“max_apps”: 1
}

Yes that’s the general idea.

I tried your JSON here, and (other than weird formatting issues from the forum post) it generates good looking code AFAICT. I also compared it to the basic DaisySeed Knob example (https://github.com/electro-smith/DaisyExamples/blob/master/seed/Knob/Knob.cpp) and it looks quite comparable. I’m not sure why this wouldn’t be working, it all looks good from here.

Here’s the JSON formatted for anyone following along:

{
	"defines": {
	  "OOPSY_TARGET_SEED": 1
	},
	"labels": {
	  "params": {
		"knob1": "kn1",
		"knob2": "kn2",
		"knob3": "kn3"
	  },
	  "outs": {},
	  "datas": {}
	},
	"inputs": {
	  "kn1": {
		"automap": true,
		"code": "hardware.seed.adc.GetFloat(0);"
	  },
	  "kn2": {
		"automap": true,
		"code": "hardware.seed.adc.GetFloat(1);"
	  },
	  "kn3": {
		"automap": true,
		"code": "hardware.seed.adc.GetFloat(2);"
	  }
	},
	"outputs": {},
	"datahandlers": {},
	"inserts": [
	  {
		"where": "init",
		"code": "AdcChannelConfig cfg[3];"
	  },
	  {
		"where": "init",
		"code": "cfg[0].InitSingle(oopsy::daisy.hardware.seed.GetPin(21));"
	  },
	  {
		"where": "init",
		"code": "cfg[1].InitSingle(oopsy::daisy.hardware.seed.GetPin(22));"
	  },
	  {
		"where": "init",
		"code": "cfg[2].InitSingle(oopsy::daisy.hardware.seed.GetPin(23));"
	  },
	  {
		"where": "init",
		"code": "oopsy::daisy.hardware.seed.adc.Init(cfg, 3);"
	  },
	  {
		"where": "header",
		"code": "#include \"daisy_seed.h\""
	  },
	  {
		"where": "header",
		"code": "using namespace daisy;"
	  }
	],
	"max_apps": 1
}

The generated code looks like this:


#define OOPSY_TARGET_SEED (1)
#define OOPSY_IO_COUNT (2)
#include "daisy_seed.h"
using namespace daisy;
#include "../genlib_daisy.h"
#include "../genlib_daisy.cpp"
#include "../../graham/custom_2dtest.cpp"

struct App_custom_2dtest : public oopsy::App<App_custom_2dtest> {
	float gen_param_knob1;	
	void init(oopsy::GenDaisy& daisy) {
		daisy.gen = custom_2dtest::create(daisy.hardware.seed.AudioSampleRate(), daisy.hardware.seed.AudioBlockSize());
		custom_2dtest::State& gen = *(custom_2dtest::State *)daisy.gen;
		daisy.param_count = 1;
		gen_param_knob1 = 0.f;
	}

	void mainloopCallback(oopsy::GenDaisy& daisy, uint32_t t, uint32_t dt) {
		Daisy& hardware = daisy.hardware;
		custom_2dtest::State& gen = *(custom_2dtest::State *)daisy.gen;
	}

	void displayCallback(oopsy::GenDaisy& daisy, uint32_t t, uint32_t dt) {
		Daisy& hardware = daisy.hardware;
		custom_2dtest::State& gen = *(custom_2dtest::State *)daisy.gen;
	}

	void audioCallback(oopsy::GenDaisy& daisy, float **hardware_ins, float **hardware_outs, size_t size) {
		Daisy& hardware = daisy.hardware;
		custom_2dtest::State& gen = *(custom_2dtest::State *)daisy.gen;
		float kn1 = hardware.seed.adc.GetFloat(0);
		gen_param_knob1 = (float)(kn1*1.f + 0.f);
		gen.set_knob1(gen_param_knob1);
		float * dsy_in1 = hardware_ins[0];
		float * dsy_in2 = hardware_ins[1];
		float * dsy_out1 = hardware_outs[0];
		float * dsy_out2 = hardware_outs[1];
		// in1:
		float * inputs[] = { dsy_in1 }; 
		// out1:
		float * outputs[] = { dsy_out1 };
		gen.perform(inputs, outputs, size);
		memcpy(dsy_out2, dsy_out1, sizeof(float)*size);
	}	
};

// store apps in a union to re-use memory, since only one app is active at once:
union {
	App_custom_2dtest app_custom_2dtest;
} apps;

oopsy::AppDef appdefs[] = {
	{"custom_2dtest", []()->void { oopsy::daisy.reset(apps.app_custom_2dtest); } },
};

int main(void) {
	oopsy::daisy.hardware.Init(); 
	oopsy::daisy.hardware.seed.SetAudioSampleRate(daisy::SaiHandle::Config::SampleRate::SAI_48KHZ);
	AdcChannelConfig cfg[3];
	cfg[0].InitSingle(oopsy::daisy.hardware.seed.GetPin(21));
	cfg[1].InitSingle(oopsy::daisy.hardware.seed.GetPin(22));
	cfg[2].InitSingle(oopsy::daisy.hardware.seed.GetPin(23));
	oopsy::daisy.hardware.seed.adc.Init(cfg, 3);
	// insert custom hardware initialization here
	return oopsy::daisy.run(appdefs, 1);
}
2 Likes

Just to rule out hardware, it’s worth mentioning that one common issue when doing things with the Seed outside of a breakout board is to forget to connect the AGND pin to the DGND pin. This would result in the ADC pins not reading correctly (or at all).

Hi, thanks for your answers and sorry for the late reply (the notifications got filtered by my inbox).
My hardware configuration seemed OK, as I can run my code using the “patch” target and the first two ADC pots respond correctly, so I went back to try the “Osc” seed example again and found out my (stupid) mistake: my JSON was using the pin numbers corresponding to the actual hardware pins and not the “libDaisy” pins ! So the first ADCs would be 15 and 16 and not 22 and 23 (I actually got that wrong by 1 in my first JSON map too).
All is good now, some pot behaviour seems reversed compared to the “patch” target but I can easily switch that around on the breadboard. Only issue now is getting a clean decoupled 5v to the analog parts of my circuit, I’m waiting on an isolated DC/DC converter to try and reduce digital noise (saw this in another post in the forum, and on the “petal” schematics).
Thanks for your help, I’ll be more than happy to beta test more aspects of the custom seed Oopsy target as it progresses !

I’m getting to work on a Terrarium JSON this week.
Apparently PedalPCB has plans for another two or three Daisy boards “coming soon”
SO MANY THINGS!!!

/edit: found the Terrarium pin out out Github

1 Like

Hey sorry, having a beginners question -

i’d like to map the pod encoder’s value to a (for example) selector object in gen~. Means, everytime when i switch encoder there’s value x + 1, or x - 1 incoming to the selector and switching between the inputs
i tried using latch with the accum object but unfortunately couldn’t get any useful results.
thanks!

Hi Sandro,

[param encoder] should give you +1 or -1 signal when the encoder is turned. There is a gotcha here though: because the analogue inputs are sampled at block rate (normally 48 samples), that will actually result in a step function. To get a single-sample trigger out of it, you could route with a [change] into [eqp] like this:

Screen Shot 2021-01-23 at 10.00.10 AM

[eqp] is short for “equal-pass”: the input passes through if both inlets are equal, else it passes zero. [change] reports 1 when the input is rising, -1 when falling, and zero otherwise. So, in the frame that the [param encoder] step function goes from 0 to 1, the change also does the same, and eqp passes 1 through. In the next frame, the step function is flat, the [change] outputs zero, and thus so does [eqp]. And vice versa for -1.

Perhaps I should make an abstraction for this in oopsy, it’s likely a common need.

Then, routing this into a [+=] or [accum] (they are the same object, just different names) will give you an ‘endless encoder’ kind of value. The 2nd inlet of [+=] will reset the value to zero.

Great! exactly what i’ve been looking for, and thanks for the good explanation - yesterday i achieved some success by: param encoder - /50 - and feeding it into the accum object - round. but unfortunately i just could step up and not down - finally i know the right way:D but anyway learned so much by figuring out this problem!

sorry, one more question: in theory backwards movement should be recognized by change and giving us -1, this going to accum should substract 1, and make a step back, yes? i’m asking cause my encoder just puts out +1, means i can only step forward not backward, did i miss something to add backwards stepping? best and thanks

Yes, it should. It looks like there’s a bug in Oopsy here – let me get that fixed up.

OK just pushed a fix to the main branch in git. If you don’t want to do the git thing, the fix is very simple:

  1. Change [param encoder] to [param encoder @min -1 @max 1]
  2. Edit the /source/daisy.pos.json file on line 49

from:
"getter": "(hardware.encoder.Increment())"

to:

"getter": "(hardware.encoder.Increment()*0.5f+0.5f)"

2 Likes

great! will try it out later, thanks for the support :smiley:

hello! i’m in the process of puzzling custom breadboard setups for my daisy seeds, and i’m starting to hit my head on what i can do with the “petal” target in oopsy. have you made any progress with your project, lobster?

Hey,
I got my project working the way I wanted by creating a custom JSON file as Graham pointed out above, he made a nice clear wiki about how you can do that here: Custom Seed Targets · electro-smith/oopsy Wiki · GitHub.

I haven’t really messed around with settings other than mapping knobs and switches yet (I’m trying to figure out other aspects of my electronics design), but starting from the custom JSON example and cross-referencing that with Daisy C++ examples that include what you’re trying to do should be quite easy, you just need to figure out where the JSON dicts insert code into the oopsy-generated C++.

2 Likes

hey! i’ve been puzzling after this for a while now, and while the daisy examples are well-documented enough for someone who has experience with C++, they’re not entirely legible to me. maybe i’m missing something major, but i can’t find anywhere in any of the github repositories how to use the code.

can you tell me where to find a reference to understand your JSON file? specifically what the

AdcChannelConfig cfg[3]

bit does? i’m digging through the Doxygen PDF for libdaisy and it’s not helping at all.

sorry to be asking simple questions; this is my first foray into any programming beyond Max/MSP/gen~ and it’s a bit of a learning curve.