magnetophon / DigiDrie

A monster monophonic synth, written in faust.
GNU Affero General Public License v3.0
21 stars 0 forks source link

[Question] How `master` parameters work? #1

Closed ryukau closed 4 years ago

ryukau commented 4 years ago

As title said, how master parameters work?

Also it seems like DigiDiagram.dsp could be used to generate block diagram. How to use it?

magnetophon commented 4 years ago

I added an explanation of those knobs to the README. I found it hard to explain. Is it clear enough?

Feel free to change anything in the README, I invited you for full access to the repo. Also for when you want to add C++ code.

The macro system is currently not so practical. To make it usable we can do 2 things, please let me know which one you think is best:

1) You add a way to copy all settings from one of the macros or from the "masters-amount" knobs, to any of the macros or "masters->amount" knobs.

2) I change the macro system like this

DigiDiagram was broken; I fixed it. Do: faust -lang ocpp -t 0 -time -svg -f 1 DigiDiagram.dsp -o -drf /dev/null && xdg-open DigiDiagram-svg/process.svg to look at the diagram.

ryukau commented 4 years ago

I now get why I was confusing. When at least 1 macro is set to 0, the value of masters->amount->* are not used.

I don't see the use case of master->amount knobs. I think it's simpler to add cross fading between 4 macros. So at least 1 set of parameters are always present.

I think option 1 is better.

magnetophon commented 4 years ago

Yeah, it's confusing, I agree. One solution would be to crossfade between 4 macros, as you say. That would mean a fixed number of macros. At the moment I can change the number of macros at compile time. That allows me to adapt the synth to when there is more or less CPU available. Maybe we could even make it so that we compile 16 different versions, and you do some pointer magic so the user can choose at runtime how many macros are needed/possible.

Let's say we want to keep the compile-time-variable number of macros and want to remove the amount knobs: what should happen when all the macros are at 0?

I don't see any other options then either a fixed number of macros, or leaving it as it is, and I don't like either option. Can you think of a third option?

I think option 1 is better.

OK, cool, looking forward to using the synth with the copy function!

I also thought about removing the masters->envelope and LFO's knobs and having only macros as modulation sources. I would increase the number of macros to 8 or 12 then.

What do you think?

ryukau commented 4 years ago

Yeah, it's confusing, I agree. One solution would be to crossfade between 4 macros, as you say. That would mean a fixed number of macros. At the moment I can change the number of macros at compile time. That allows me to adapt the synth to when there is more or less CPU available. Maybe we could even make it so that we compile 16 different versions, and you do some pointer magic so the user can choose at runtime how many macros are needed/possible.

Switching between 16 versions are probably too much. There's also SIMD dispatching, so the number of code path becomes 16 * (number of supported SIMD). I'd prefer to have fixed number of macros.

Let's say we want to keep the compile-time-variable number of macros and want to remove the amount knobs: what should happen when all the macros are at 0?

To me, just setting all parameter to 0 is intuitive. This is likely solved with the default parameter and some indicator in UI.

I don't see any other options then either a fixed number of macros, or leaving it as it is, and I don't like either option. Can you think of a third option?

While it kills flexibility, it's easier to use a fixed number of macros. I have an impression that parameters can't be added/removed dynamically on LV2 and VST 2/3. So behind the scene, plugin must prepare a fixed number of parameters, and hide the unused parameters on UI.

I also thought about removing the masters->envelope and LFO's knobs and having only macros as modulation sources. I would increase the number of macros to 8 or 12 then.

What do you think?

This might be more intuitive, but I can't see how to apply LFO/Envelope after the change. Could you elaborate?

magnetophon commented 4 years ago

To me, just setting all parameter to 0 is intuitive. This is likely solved with the default parameter and some indicator in UI.

Hmm, that's an option, but it doesn't feel right to me. Let's think some more.

So behind the scene, plugin must prepare a fixed number of parameters

Yeah, I think this is the winning argument for fixed number of macros. Not sure if it should be 4 in a crossfade arrangement though.

This might be more intuitive, but I can't see how to apply LFO/Envelope after the change. Could you elaborate?

Did you not see the envelope and LFO knobs on the macros tab?

ryukau commented 4 years ago

Hmm, that's an option, but it doesn't feel right to me. Let's think some more.

Currently, macro envelope and LFO are not multiplied by macro->mix. I think keeping this behavior is one way to solve this issue. So if modulation is not working, user will look into macro, then turn up some of the parameter there.

Not sure if it should be 4 in a crossfade arrangement though.

It's just ad-hoc idea I came up with. So there probably is better way.

Did you not see the envelope and LFO knobs on the macros tab?

I was missing it. I agree with removing the masters->envelope and LFO.

magnetophon commented 4 years ago

Currently, macro envelope and LFO are not multiplied by macro->mix. I think keeping this behavior is one way to solve this issue. So if modulation is not working, user will look into macro, then turn up some of the parameter there.

I don't understand, sorry.

ryukau commented 4 years ago

macro->env and macro->LFO is added to a macro signal. I think this is nice, because "change something in macro, so that modulation is enabled" is intuitive to me.

In other words, even if all macro->mix is 0, there's a way to enable modulation. And I like this behavior.

magnetophon commented 4 years ago

I agree with removing the masters->envelope and LFO.

Do you mean just the masters? I was thinking about also removing modulation sources -> envelopes and modulation sources -> LFO's so the only way to set or modulate a synth parameter would be via a macro.

This would only be practical to use if we/you can come up with a good GUI to make clear what is happening.

ryukau commented 4 years ago

I was thinking about also removing modulation sources -> envelopes and modulation sources -> LFO's so the only way to set or modulate a synth parameter would be via a macro.

I'm OK with this. Just to make sure, you are thinking some thing like this image?

Untitled

This would only be practical to use if we/you can come up with a good GUI to make clear what is happening.

I tried Helm and Surge, and if I understand correctly, the idea of macros will fit to their kind of UI.

Just in case, my current goal is to integrate generated code and to expose all parameters. I'll discuss issue around polishing GUI after that.

magnetophon commented 4 years ago

Yes, that is what I mean. A big downside is that it gets more complex to do a simple "modulate this parameter with this LFO", since you need to set up a macro: copy settings from macro 0 to macro 1, then make macro 1 move with the LFO you want, then change the "macro 1 of that parameter" Maybe a good GUI can make that quick to do, but I have no idea what that would look like.

Ideas?

Not sure what to do with amount yet: 1) Leave it in:

magnetophon commented 4 years ago

I removed modulation sources -> envelopes, modulation sources -> LFO's and the master parameters. macro 0 is now the "main" setting. I increased the number of macros to 8.

In the GUI version, I imagine the main screen has knobs for all macro 0 parameters, and there are almost identical looking screens for the other macros.

A big advantage is that the CPU usage dropped dramatically: DigiFaustMidi.dsp went from 52% to 26% with default compile options! This also shows how hard it is to predict total CPU usage by benchmarking components.

ryukau commented 4 years ago

A big advantage is that the CPU usage dropped dramatically: DigiFaustMidi.dsp went from 52% to 26% with default compile options!

This is great!

This also shows how hard it is to predict total CPU usage by benchmarking components.

Procedure I used in C++ is:

  1. Gather function call count with callgrind.
  2. Separate intensively called function.
  3. Make change to the separated function, and take a benchmark of the function.

Function call count is lacking now, so I can only put my hands on obvious one like math.h primitives.

ryukau commented 4 years ago

For master->amount, leaving the master->amount and simply adding macro signal to the value of master->amount is also an option.

A big downside is that it gets more complex to do a simple "modulate this parameter with this LFO", since you need to set up a macro: copy settings from macro 0 to macro 1, then make macro 1 move with the LFO you want, then change the "macro 1 of that parameter"

I'm not sure about "copy settings from macro 0 to macro 1" part. Couldn't same thing be done with the following procedure?

  1. Turn off macro 0 amount of parameter A.
  2. Turn up macro N amount of parameter A.
  3. Turn up LFO/Env amount of macro N.

This is basically master->amount fading off done by hand, but the point is user knows what they are doing because it's explicit procedure.

magnetophon commented 4 years ago

For master->amount, leaving the master->amount and simply adding macro signal to the value of master->amount is also an option.

Isn't the difference at that point just the name of the parameter? "macro 0" vs master->amount?

I'm not sure about "copy settings from macro 0 to macro 1" part.

The above procedure would indeed modulate parameter A, but it would also modulate every other parameter that is not the same in marcro 0 and macro N, hence the copy step.

ryukau commented 4 years ago

Isn't the difference at that point just the name of the parameter? "macro 0" vs master->amount?

You are right. I'm just putting an idea.

The above procedure would indeed modulate parameter A, but it would also modulate every other parameter that is not the same in marcro 0 and macro N, hence the copy step.

Perhaps we are thinking about different default parameters? I'm thinking about only macro 0 amount to be 1, and other macro amounts to be 0, in default.

magnetophon commented 4 years ago

Perhaps we are thinking about different default parameters? I'm thinking about only macro 0 amount to be 1, and other macro amounts to be 0, in default.

Same here. The way it works now is that tab macros->macro 0 doesn't even have a slider, it is just the default when all other 'tab macro` sliders are at 0.

I just wanted to say that when you have tweaked a few modulation sources->macro 0 knobs, and then you want to modulate just one of them, you have to copy the settings to macro 1 or else all of the changes between macro 0 and macro 1 will be modulated.

ryukau commented 4 years ago

I now tested latest master, and got your point.

In my opinion, macro 0 should be same as other macros. I also expected:

Back to the leave/remove amount post, I don't think it's weird when macro 0 has same controls as other macros. An analogy is that some FM synths allows me to set all operator gains to 0. This behavior makes it easier to try different modulations. If one of the operator gain is fixed to 1, I have to copy parameters to different operator, like you said.

magnetophon commented 4 years ago

In my opinion, macro 0 should be same as other macros.

I get why that is a good thing. The issue is: what happens when all macros->macro 0-7->macro sliders are at 0?

I think your suggestion of having default parameters doesn't solve the problem, but just moves it to a different place. It would create a "virtual macro -1", that also doesn't have a master slider, just like the current macro 0 When you modulate between macro -1 and macro 0, the knobs move between an unknown position (the default: macro -1) and the settings of macro 0.

I think the current behavior is the best compromise we have so far, but I'm open to other suggestions or more arguments against it. I agree it's not 100% intuitive the way it looks now, but when we put all the macro 0 knobs in one window, and don't call them macro 0 but just main or so, it will be much clearer.

ryukau commented 4 years ago

When all macro outputs are 0, I expect all parameters to be set to neutral. I'm perceiving a macro as just a modulation source. I'm thinking something like this:

// m0 is short for macro0.
// m0dc is the value of `macros->macroN0->macro`.
// m*mod is the value of `modulation sources` of a parameter.

m0out = m0dc
  + m0env0 * env0out + /* ... */
  + m0lfo0 * lfo0out + /* ... */;

// Same for macro1 to macro7.

parameter =
  + m0mod * m0out
  + m1mod * m1out
  + /* ... */
  + m7mod * m7out
  : max(paramMin) : min(paramMax);

I couldn't see why macro -1 is necessary. Probably, I'm missing the aim of macro or some implementation details here.

magnetophon commented 4 years ago

When all macro outputs are 0, I expect all parameters to be set to neutral.

"set to neutral" is what I called virtual macro -1. So it's not a real macro but it would behave like the current macro 0: it is what you hear when all macros are at value 0.

If I implement this, the result is the same as what we currently have, except you cannot see what value the knobs go to when all macros are at 0.

How is that an improvement?

ryukau commented 4 years ago

Isn't that means virtual macro -1 already exists in current implementation, and fallbacks to 0? Why it specifically becomes problem when macro 0 behaves as same as other macros?

If we care about implicit parameter, how about the crossfade between macro 0 and other macros? Also gain normalization to the sum of LFO/envelope is implicit (I like this one though).

I alraedy pointed out the difference of my idea to current implementation. It's easier to try different modulation if macro 0 is not special. Another advantage is learning cost. Less thing to remember, which leads to less mistake.

Because of sheer amount of parameters, I'd like to keep everyting consistent and uniform as possible. Consistency leads to less branching, so less codes and less tests. I'm the one who have to do the plumbing of UI. Maintainability is high priority to me.

Just to note that I'm not complaining about the number of parameters. For example, L-R offset almost doubles the number of parameters. However it's consistent. So I'm OK with it.

magnetophon commented 4 years ago

I think we are misunderstanding each other.

Can we have a realtime chat somewhere? IRC/discord/slack/skype?

ryukau commented 4 years ago

Yeah, chat is likely faster. But, I don't have any of those used/installed. Which one is the easiest to join? I'll setup. If you have google account, hangouts is easiest to me.

magnetophon commented 4 years ago

I fully agree it would be nice if all macros behave the same: much easier to understand.

In the current implementation, macro 0 is special, because that is how I solved the "all macros to 0 problem".

Your solution to that problem is to cross-fade to a set of values that the user can not see and can not edit. Because it is a set of the same parameters as the macros, I called it "a macro". Because the user can not see the values of that macro, I called it a "virtual" macro.

So my solution has a special macro, that is special for 2 reasons:

Your solution also has one special "macro", that is special for 3 reasons:

I think that doesn't really make it easier for new users.

I imagine these 2 cases if you will:

1) A user opens "your version of the synth" for the first time, and tweaks the settings a bit (I assume the first page the user lands on has all the macro 0 knobs, right?). Then he goes to the macros page and tweaks the first slider: macros -> macro 0. It has a default value of 1. When the slider is at 0 he hears the default values we have chosen. (a sine-wave, right?)

He thinks to himself: why am I hearing this, and will have to look it up somewhere.

2) Now another new user opens up mine for the first time. He lands on an overview of all macro 0 knobs and tweaks them a bit, same as in your case. Then he goes to the macros page and tweaks the first slider: macros -> macro 1. It has a default value of 0. When the slider is at 1 he hears the settings of macro 1, (if he didn't tweak them; a sine-wave) So far the user experience is almost identical.

He thinks to himself: why am I hearing this and notices a page/button called macro 1.
He clicks it and sees a page almost identical to the landing page. He starts to tweak the knobs and directly hears the result, since he left macros->macro 1 at 1.

Of course both cases could be improved a bit by showing an explaining popup when the user tweaks a macros->macro x slider.

I used a new user as an example here, but I think our main focus should be an advanced user. For an advanced user, the 2 versions are almost the same in terms of mental overhead and they are identical in CPU usage, but your solution can morph to a fixed sine-wave, and mine can morph to one more fully tweak-able sound. The number of macros has quite an influence on the CPU usage (try it, it's one variable called "nrMacros", defined on one of the last lines of `lib/DigiDrie.lib). So "losing" one macro to a sine-wave is quite wasteful.

Consistency leads to less branching, so less codes and less tests. I'm the one who have to do the plumbing of UI. Maintainability is high priority to me.

Fully agreed! Thanks again for doing this and also for being so patient with me! :)

As I said above: I think both versions are almost equally consistent.

As far as I can tell, the only difference in the amount of work/maintenance for you is that you have to make one more page of parameters. That page is almost identical to the other macro pages, so it should be almost no work, right?

magnetophon commented 4 years ago

Let's meet on https://meet.jit.si/DigiDrie then.

ryukau commented 4 years ago

For the record, another idea from the chat:

22:12
ok, so one idea: changes front page to one of the macro 1-7
and treat macro 0 as special fallback

I used a new user as an example here, but I think our main focus should be an advanced user.

I agree on this.

The number of macros has quite an influence on the CPU usage (try it, it's one variable called "nrMacros", defined on one of the last lines of `lib/DigiDrie.lib). So "losing" one macro to a sine-wave is quite wasteful.

I reduced nrMacros to 3, but DSP load didn't change on my environment. It was almost same as `nrMacros = 8;.

magnetophon commented 4 years ago

To be precise, I think the front page should be macro 1 and the default value of macros->macro 1 should be one and the others zero. That way 'macro 0` becomes a special fallback. That is on your todo list, right?

I reduced nrMacros to 3, but DSP load didn't change on my environment. It was almost same as `nrMacros = 8;.

I tested badly, I didn't make proper notes, sorry.

ryukau commented 4 years ago

To be precise, I think the front page should be macro 1 and the default value of macros->macro 1 should be one and the others zero. That way 'macro 0` becomes a special fallback. That is on your todo list, right?

Yes, this is on my todo. I'll make macros->macro 1 to front.

magnetophon commented 4 years ago

Now that we have chosen the 3D cross-fade, I think we can close this issue. I just skimmed trough it again, and I don't seen any open questions, correct?

ryukau commented 4 years ago

Yes, this issue is done. Thanks for your explanation!