Skip to content

Commit

Permalink
Merge pull request #1 from bassmanitram/fix/ui-ordering
Browse files Browse the repository at this point in the history
Fix/UI ordering
  • Loading branch information
bassmanitram authored May 15, 2024
2 parents 68f8f7e + 8942ea3 commit 80d36cb
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 85 deletions.
89 changes: 45 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ For now, you'll need to be using Linux to have the tools work for you, though th
why, in the future, we can't get the tools running on Windows and Mac.

## Installation
Download the [latest version of the SDK bundle](../../releases/download/v0.2.0/faust-stratus-v0.2.0.zip) to your local computer
Download the [latest version of the SDK bundle](../../releases/download/v0.2.1/faust-stratus-v0.2.1.zip) to your local computer
(NOT the Stratus pedal) and un-zip it into a folder of your choosing.

You can install this SDK in either of two places - or both!
Expand Down Expand Up @@ -41,27 +41,6 @@ I'm not going to detail that process - Faust itself has some very complete doc,
workshops, that will help you - _but_ there are certain principles that will help you build a Faust DSP
file that is easily adapted to the Stratus:

* **You get one input signal and must produce only one output signal**

If you don't know what that means, read the Faust doc - but the Stratus is a mono pedal.

_Within_ the algorithm you can split the signal any number of times you need, but you
MUST merge it all back to one signal on output.

Here's a "blend" pattern that might help explain that:

```
...
blend = vslider("[1]Blend", 0.5, 0, 1, 0.01);
weird_effect = ...;
// Splits the single input signal in two, applies the weird effect to one, scales the two signals
// based upon the blend control such that blend = 0 gives only clean, blend = 1 gives only weird,
// and anything in between ... well, blends the two appropriately - and finally merges the internal
// signals back to one
process = _ <: (weird_effect : *(blend)),*(1-blend) :> _;
```

* **Represent Stratus knobs using `vslider` and/or `hslider`**.

This toolkit will recognize `hslider` and `vslider` UI components as Stratus *knobs*. For a nice UI
Expand All @@ -80,44 +59,66 @@ file that is easily adapted to the Stratus:
to apply the necessary scaling of the values in your algorithm. When you move to a UI that is dedicated
to your effect, you can have the knobs defined as you want and can remove the algorithmic scaling.

* **Represent Stratus switches using either `checkbox`, or `nvalue`**
* **Represent Stratus switches using `button`, `checkbox`, or `nvalue`**

While a rarer component of a Stratus UI, switches are also supported by this toolkit. Stratus supports
both 2-state (on/off) and 3-state (on/mid/off) switches. Technically this toolkit doesn't distinguish
between the two - it's up to the Stratus firmware to use the switches correctly.

To represent switches in the Faust IDE UI and the Faust DSP file, you can use:

* `button`, or
* `checkbox`, or
* `nvalue`, with a minimum value of 0, a maximum value of 1 or 2, and a step value of 1.

Again, organize them as you will in the Faust IDE UI, but only these two controls will be recognized
Again, organize them as you will in the Faust IDE UI, but only these controls will be recognized

* **ORDER your sliders and switches correctly according to your Stratus UI**
* **Mark your sliders and switches correctly according to your Stratus UI**

Without some hinting, Faust will "order" your controls in the code in alphabetical order by label.
As-is, your sliders, buttons, checkboxes, and/or nvalues will **not** be "attached" to the equivalent
knobs and switches in the Stratus UI.

This is highly unlikely to be what you want.
To do _that_ you must exploit a feature of Faust UI labels - adding metadata.

However, Faust _does_ take hints that cause the controls to be programmatically declared in the
order you want no matter their label or how they appear in the IDE. You do this via the label
(so, er, perhaps "...no matter their label...", above, is a little misleading :)).

Such a hint has the syntax:
This is how you do it:

```
vslider("[0]This is my totally ignored label for knob 0", ...)
vslider("This is my totally ignored label for my first knob[stratus:0]", ...)
```

With (evidently) the `[n]` bit providing the ordering you want.
More generally, you add the `stratus` key with a value between 0 and 9
for knobs, and between 0 and 4 for switches (if you aren't familiar with
zero-based indices, welcome to that world!).

Obviously, those indices indicate to which control on the Stratus effect UI
a specific Faust control pertains. Stratus knobs themselves are numbered
left-to-right, top-to-bottom. Switches too.

If you try to assign the same control number to two different Faust controls
then the second control will be ignored (here I mean two different controls
of the same "class" - knob and switch numbering are independent of each other).

* **You get one input signal and must produce only one output signal**

If you don't know what that means, read the Faust doc - but the Stratus is a mono pedal.

_Within_ the algorithm you can split the signal any number of times you need, but you
MUST merge it all back to one signal on output.

In case it's not obvious, `n` should be a non-negative integer, but the `n` value itself doesn't
actually matter to this toolkit - only the ordering that it enforces is taken into account.

Note that Stratus knobs themselves are numbered left-to-right, top-to-bottom. Switches too.
Note also that within the toolkit the ordering of knobs is independent of the type of slider
you declared to represent the knob, and the same for switches - _and_ that the orderings of
knobs and of switches are independent of each other.
Here's a "blend" pattern that might help explain that:

```
...
// The blend control is the SECOND knob on the Stratus UI
blend = vslider("Blend[stratus:1]", 0.5, 0, 1, 0.01);
weird_effect = ...;
// Splits the single input signal in two, applies the weird effect to one, scales the two signals
// based upon the blend control such that blend = 0 gives only clean, blend = 1 gives only weird,
// and anything in between ... well, blends the two appropriately - and finally merges the internal
// signals back to one
process = _ <: (weird_effect : *(blend)),*(1-blend) :> _;
```

* **Declare the UUID of your Stratus effect in the DSP code**

Expand Down Expand Up @@ -231,13 +232,13 @@ version file - and any existing effect files for the same UUID will have been ba
(it's up to _you_ to clean those up occasionally).

## Using the Python wrapper
If you want to use the [Python interface](./stratus.py) to test the effect without having to load it up in
If you want to use the Python interface to test your effects without having to load them up into
the pedal itself, you simply need to add the `--stratusc` option to the `faust2stratus` command line.

The python interface is installed on the Stratus by the `install-sdk` command, so you can even do that
The Python interface is installed on the Stratus by the `install-sdk` command, so you can even do that
on the Stratus itself (it has Python installed already).

But this is probably of more use in a local build, where you can devise tests for your effect that closely
emulate how the Stratus will interact with your effect without actually having to build the UI yet.

See the delivered [test script](../tester.py) for examples of how this can be done.
See the delivered [test script](.tests/tester.py) for examples of how this can be done.
147 changes: 117 additions & 30 deletions srcs/faust-stratus/architecture/stratus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,65 +55,155 @@ class Meta
};

//
// Our implementation of the Faust UI interface
//
// hsliders and vsliders are knobs
// checkboxes, and nentries with min=0, max=1 or 2, and step=1 are switches
// A "control set" is used to manage the switches and knobs of a
// Stratus UI and how they map to the actual values in the Faust
// algorithm
//
class UiControlSet {
protected:
//
// The maximum number of items in the set (MAXKNOBS or MAXSWITCHES)
//
Uint max;

//
// Controls with ordering metadata get set immediately
// unordered stuff is inserted afterwards!
//
FAUSTFLOAT** controlValues;
Uint controlCount = 0;

//
// Add a control to the set
//
// if the provided index is equal to or greater than 0, and less than the
// max value for the set, AND it's indicated slot NULL, we set that slot and
// increment the number that we have set.
//
// Otherwise, the control is ignored
//
void add_control(FAUSTFLOAT* control, int index) {
if (index >= 0 && index < max && controlValues[index] == nullptr) {
controlValues[index] = control;
controlCount++;
}
}

//
// Return a value if the index is valid and the pointer in that slot
// is valid
//
FAUSTFLOAT getValue(Uint i) {
return (i < max && controlValues[i] != nullptr) ? *(controlValues[i]) : 0;
}

//
// Set a value if the index is valid and the pointer in that slot
// is valid
//
void setValue(Uint i, FAUSTFLOAT value) {
if (i < max && controlValues[i] != nullptr) *(controlValues[i]) = value;
}

UiControlSet(Uint max) {
this->max = max;
controlValues = new FAUSTFLOAT*[max];
for (int i = 0; i < max; ++i) {
controlValues[i] = nullptr;
}
}

~UiControlSet() {
delete[] controlValues;
}

friend class UI;
friend class Stratus;
};

class UI {
private:
int nextIndex = -1;
FAUSTFLOAT* nextControl = nullptr;

void add_knob(FAUSTFLOAT* slider) {
if (knobCount < MAXKNOBS) {
knobs[knobCount] = slider;
knobCount++;
if (slider == nextControl) {
knobs->add_control(slider, nextIndex);
}
}

void add_switch(FAUSTFLOAT* swtch) {
if (switchCount < MAXSWITCHES) {
switches[switchCount] = swtch;
switchCount++;
if (swtch == nextControl) {
switches->add_control(swtch, nextIndex);
}
}

void reset_declare_state() {
nextIndex = -1;
nextControl = nullptr;
}

protected:
FAUSTFLOAT* switches[MAXSWITCHES];
Uint switchCount = 0;
FAUSTFLOAT* knobs[MAXKNOBS];
Uint knobCount = 0;
UiControlSet* knobs;
UiControlSet* switches;

public:
UI() {}
UI() {
knobs = new UiControlSet(MAXKNOBS);
switches = new UiControlSet(MAXSWITCHES);
reset_declare_state();
}
~UI();

void openTabBox(const char* label) {};
void openHorizontalBox(const char* label) {};
void openVerticalBox(const char* label) {};
void closeBox() {};
void addSoundfile(const char* label, const char* filename, void** sf_zone) {};

void addButton(const char* label, FAUSTFLOAT* zone) {
this->add_switch(zone);
reset_declare_state();
};
void addCheckButton(const char* label, FAUSTFLOAT* zone) {
this->add_switch(zone);
reset_declare_state();
};
void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) {
// printf("VSLIDER: %s %p %f %f %f %f\n",label,zone,init,min,max,step);
this->add_knob(zone);
reset_declare_state();
};
void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) {
// printf("HSLIDER: %s %p %f %f %f %f\n",label,zone,init,min,max,step);
this->add_knob(zone);
reset_declare_state();
};
void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) {
FAUSTFLOAT steps = (max - min)/step;
if (min == 0 && (max == 1 || max == 2) && step == 1) {
this->add_switch(zone);
}
reset_declare_state();
};
void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) {};
void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) {};
void addSoundfile(const char* label, const char* filename, void** sf_zone) {};
void declare(FAUSTFLOAT* slider, const char* key, const char* val) {}
void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) {
reset_declare_state();
};
void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) {
reset_declare_state();
};

//
// The "declare" state machine!
//
// We look for the "stratus" key and a single decimal digit as the value
//
void declare(FAUSTFLOAT* control, const char* key, const char* val) {
if (control != nullptr && strcmp("stratus",key) == 0 && strlen(val) == 1 && val[0] >= '0' && val[0] <= '9') {
nextControl = control;
nextIndex = val[0] - '0';
}
}

friend class Stratus;
};
Expand Down Expand Up @@ -191,7 +281,6 @@ struct Stratus
int fSampleRate = 44100;
Stratus()
{
faustUi = new UI;
faustMeta = new Meta;
faust = new FAUSTCLASS;
faust->metadata(faustMeta);
Expand All @@ -200,6 +289,8 @@ struct Stratus
version = faustMeta->version;

faust->init(fSampleRate);

faustUi = new UI;
faust->buildUserInterface(faustUi);

stompSwitch = DOWN;
Expand Down Expand Up @@ -245,35 +336,31 @@ struct Stratus
}

Uint getKnobCount() {
return this->faustUi->knobCount;
return this->faustUi->knobs->controlCount;
}

void setKnob(int num, float knobVal)
{
if (num < this->faustUi->knobCount) {
*(this->faustUi->knobs[num]) = knobVal;
}
this->faustUi->knobs->setValue(num, knobVal);
}

float getKnob(int in)
{
return in < this->faustUi->knobCount ? *this->faustUi->knobs[in] : 0.5;
return this->faustUi->knobs->getValue(in);
}

Uint getSwitchCount() {
return this->faustUi->switchCount;
return this->faustUi->switches->controlCount;
}

void setSwitch(int num, SWITCH_STATE switchVal)
{
if (num < this->faustUi->switchCount) {
*(this->faustUi->switches[num]) = switchVal;
}
this->faustUi->switches->setValue(num, switchVal);
}

SWITCH_STATE getSwitch(int in)
{
Uint switchVal = in < this->faustUi->switchCount ? *this->faustUi->switches[in] : 0;
Uint switchVal = this->faustUi->switches->getValue(in);
return switchStateFromValue(switchVal < 3 ? switchVal : 0);
}

Expand Down
1 change: 0 additions & 1 deletion srcs/faust-stratus/bin/faust2stratus
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ for opt in "$@"; do
done
[[ ${#opts} -gt 0 ]] && FAUST_OPTS=$(printf "%q " "${arr[@]}")


CPP_OPTS="-fPIC -shared -O3 -g "

arch=$(gcc -march=native -Q --help=target|grep '^ -marc' | awk '{print $2}')
Expand Down
Loading

0 comments on commit 80d36cb

Please sign in to comment.