Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding holdAndJoin in signals.lib ? #16

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

bornej
Copy link

@bornej bornej commented Aug 4, 2019

A function that holds its input value when receiving a off trigger.
When receiving a on trigger, the function waits for the input value to join the held value. Once the input joins the ouput value, the function is bypassed.

The function solve the problem mentioned in the following use case:
When working with multi-timbral synthesizers, some parameters
might be shared between different synth voices (filter cutoff, resonance, ...).
It is common that such a parameter will be controlled by a physical potentiometer sending Midi CC. Depending on which synthesizer voice has the focus, the user might set
the potentiometer to different positions.
When switching back to a voice, the parameters values might "jump"
abruptly because the potentiometers physical positions do not match the parameters values anymore.

See documentation for usage, illustration, example...

…lue when receiving a :off: trigger.

When receiving a 🔛 trigger, the function waits for the input value to join the held value.
Once the input joins the ouput value, the function is bypassed.
@bornej
Copy link
Author

bornej commented Aug 4, 2019

I'm very new to faust, but I greatly appreciate it.
I thought this function might be useful to other midi controller users.
It's also my first attempt to contribute to a project... So I hope I'm doing things right...
I tried to follow your recommendations regarding documentation, ran ./generateDoc, tested and used the function.
Since faust is new to me, I'm a bit worried that I might have overlooked some things...
Also I mixed lambda, infix, ..., notations. I don't know if it's quite right.

I don't know if you could be interested by this...

Anyway, thank you very much for your work.
Best regards,
Jonathan.

@sletz
Copy link
Member

sletz commented Sep 16, 2019

Do you have a test Faust DSP program to demonstrate the use of holdAndJoin ? Thanks.

@bornej
Copy link
Author

bornej commented Sep 16, 2019

Yes!

import("stdfaust.lib");

holdOrJoin(i, b) = joined_h(i, b) , i : select2((b == 1) & (_))
with {
     // Ensure we do not sample a new value if the old value hasn't
     // been joined.
     joined_h(i,b) = joined(i,b) ~ holder(i)
     with {
       joined(i,b,h,thru) =
       ((b == 1) & (i == h)) , (b == 0) : ba.on_and_off, thru;
     };
     // sample if in hold mode AND previous sampled value was joined
     holder(i,j) = i : ba.sAndH((b == 1) & (j == 1)) <: _,_;
};

in = hslider("in", 0, 0, 127, 1);

// When unchecked the function holds a value sampled from input
// When checked the function wait for the input to join 
// the held value and then release the hold.
b = checkbox("Hold/Join");
process = in, b : holdOrJoin : hbargraph("out", 0, 127);

I, Hope this do the job.
I've also put it in the documentation...

The first version was ... well... a poor beginner attempt and
let's face it a real mess... Sorry about that. Things are cleaner now I think.
Also there might be a more straightforward way to get this result that
I've missed.

@sletz
Copy link
Member

sletz commented Sep 16, 2019

I mean better: a real MIDI based Faust DSP use-case as you describe in the documentation:


// this function solves the particular use case of controlling
// Multitimbral synthesizer parameters via midi controllers.

I would like to estimate if this holdOrJoin is a new and real need, or if it can be done with existing functions or writing the Faust DSP code another way.

@bornej
Copy link
Author

bornej commented Sep 16, 2019

I know what you mean.

Thinking about it, maybe I'm just the only one needing this thing...
I realize now that the use case is quite specific.
And maybe it's not that relevant outside of this use case...

Still when I proposed this initially I though this could really serve a purpose
and I couldn't find a way in faust to do this more easily.
I mean, I do use it everyday and this function literally saved my life but
I don't know if it will be useful to others.

I'll verify the function is relevant with other pieces of hardware,
other use context...
I'll try to produce an explicit example and come back to you...
If I can't find an example well, ... We will see...

@sletz
Copy link
Member

sletz commented Sep 16, 2019

So:

  • have you already written a Faust DSP program that has this need?
  • well the purpose of libraries is to share code that is useful for the developer community. Or course it il always better to avoid code duplication and add only real new things
  • so I need to better understand the use-case, and real Faust DSP code would really help

@bornej
Copy link
Author

bornej commented Sep 16, 2019

  • yes

  • I dug quite deep into the manual, the libraries, the examples,... I
    couldn't a function or a straightforward way to achieve this. I might have overlooked
    things since I'm still learning faust (and sometimes not the brightest ^^).

  • My work related to this is here
    A documented exemple here.

The implementation on those examples is a bit specific to the NordDrum
Hardware and the way it handles Midi.
I will try to provide you a simpler, straightforward example illustrating this...

If you are curious about what I'm doing and why I initially needed this function...
Basically I use the NordDrum
to control mi-faust physical models.

The ND3P has Six voices, one voice per pad and I assign one mi-faust model per pad.

There is only one potentiometer on the ND3P front panel.
To tweak parameters, you select the voice you want to edit
then the parameter you want to edit and you use the knob...

This means the parameters of my models are all controlled by the same physical knob.

I needed a midi join function ensuring that parameters value don't "jump"
when switching from a voice to another.

@bornej
Copy link
Author

bornej commented Sep 16, 2019

I cut everything down.
It was clumsy, unclear, confusing.
This function does a simple thing, it should be simple to explain.

  • Renamed to releaseAfterJoin
  • Removed use case from documentation, added unnecessary comlpexity.
    (I will provide a real midi use case example but outside the doc)
  • The example now shows how releaseAfterJoin compare to sAndH.
  • Changed parameters order to match sAndH

@bornej
Copy link
Author

bornej commented Sep 16, 2019

Could you tell me if this example and reworked documentation bring some more light into what the function does?
(I'm not forgetting the use case...)

import("stdfaust.lib");

releaseAfterJoin(b, i) = joined_h(i, b) , i : select2((b == 1) & (_))
with {
     // Ensure we do not sample a new value if the old value hasn't
     // been joined.
     joined_h(i,b) = joined(i,b) ~ holder(i)
     with {
       joined(i,b,h,thru) =
       ((b == 1) & (i == h)) , (b == 0) : ba.on_and_off, thru;
     };
     // sample if in hold mode AND previous sampled value was joined
     holder(i,j) = i : ba.sAndH((b == 1) & (j == 1)) <: _,_;
};


in = hslider("input", 0, 0, 127, 1);
b = (checkbox("Release/Hold")+1) %2;// Start from release state
out_sAndH = ba.sAndH(b,in);
out_releaseAfterJoin = releaseAfterJoin(b,in);
process = (out_sAndH : hbargraph("sAndH output", 0, 127)), (out_releaseAfterJoin : hbargraph("releaseAfterJoin output", 0, 127)) ;

@sletz
Copy link
Member

sletz commented Sep 17, 2019

In the new implementation:

  • is there a reason why the internal joined_h(i, b) has reverse parameter order compared to the main releaseAfterJoin(b, i) ?
  • could internal holder(i,j) we moved in the internal, with {....} section ?
  • and having a real MIDI based simpler use-case will help

@bornej
Copy link
Author

bornej commented Sep 17, 2019

  • fixed

  • done

  • See next :A real use case with midi control. hope this helps

// One potentiometer controlling filter cutoff for two synth voices.

import("stdfaust.lib");

releaseAfterJoin(b, i) = joined_h(b, i) , i : select2((b == 1) & (_))
with {
     // Ensure we do not sample a new value if the old value hasn't
     // been joined.
     joined_h(b,i) = joined(b,i) ~ holder(i)
     with {
       joined(b,i,h,thru) =
       ((b == 1) & (i == h)) , (b == 0) : ba.on_and_off, thru;
       // sample if in hold mode AND previous sampled value was joined
       holder(i,j) = i : ba.sAndH((b == 1) & (j == 1)) <: _,_;
     };
};

// A map from cc range [0,127] to an abitrary range [Min, Max]
ccToVal(Min, Max, cc) = cc * (( Max - Min) / 127) + Min;

// Trigger the synths envelope generators
// Note C3
trigger = button("Trigger [midi:key 60]");

// The potentiometer.
potentiometer = hslider("Cutoff [midi:ctrl 74]", 0, 0, 127, 1);

// Filter cutoff range
cutoff_min = 30;
cutoff_max = 3000;

// Select the voice you want to edit with this selector
// For midi control:
// Please use a two-state button sending 0 and 1 midi cc values
voice_selector = checkbox("Selected Voice (0 unchecked / 1 checked) [midi:ctr\
l 16]");

// Cutoff frequency value for voice 0
// Only listen to incoming cc values when selected voice == 0
cutoff_0 = (voice_selector == 0, potentiometer:releaseAfterJoin) :
 ccToVal(cutoff_min, cutoff_max) : hbargraph("Voice 0: Cutoff", cutoff_min, c\
utoff_max);

// Cutoff frequency value for voice 1
// Only listen to incoming cc values when selected voice == 1
cutoff_1 = (voice_selector == 1, potentiometer:releaseAfterJoin) :
 ccToVal(cutoff_min, cutoff_max) : hbargraph("Voice 1: Cutoff", cutoff_min, c\
utoff_max);

synth_1 = os.square(900)*en.smoothEnvelope(2, trigger):fi.lowpass(1, cutoff_0\
);
synth_2 = os.square(433)*en.smoothEnvelope(2, trigger):fi.lowpass(1, cutoff_1\
);

process = synth_1 + synth_2;


@bornej
Copy link
Author

bornej commented Sep 17, 2019

Also since we have midi channel now...

// One potentiometer controlling filter cutoff for two synth voices.
// CC values sent to channel 0 goes to synth voice 1.
// CC values sent to channel 1 goes to synth voice 2.

import("stdfaust.lib");

releaseAfterJoin(b, i) = joined_h(b, i) , i : select2((b == 1) & (_))
with {
     // Ensure we do not sample a new value if the old value hasn't
     // been joined.
     joined_h(b,i) = joined(b,i) ~ holder(i)
     with {
       joined(b,i,h,thru) =
       ((b == 1) & (i == h)) , (b == 0) : ba.on_and_off, thru;
       // sample if in hold mode AND previous sampled value was joined
       holder(i,j) = i : ba.sAndH((b == 1) & (j == 1)) <: _,_;
     };
};

// A map from cc range [0,127] to an abitrary range [Min, Max]
ccToVal(Min, Max, cc) = cc * (( Max - Min) / 127) + Min;

// Trigger the synths envelope generators
// Note C3
trigger = button("Trigger [midi:key 60]");

// Receiving CC values from channel 0 and 1.
chan_0 = hslider("Cutoff_chan_0 [midi:ctrl 74 0]", 0, 0, 127, 1);
chan_1 = hslider("Cutoff_chan_1 [midi:ctrl 74 1]", 0, 0, 127, 1);

// Filter cutoff range
cutoff_min = 30;
cutoff_max = 3000;

// Switch from voice 1 to voice 2 when values
// are received from chan 1 and chan 2 respectively.
voice_selector = ba.on_and_off(chan_1,chan_0):
hbargraph("Selected voice",0,1);

// Cutoff frequency value for voice 0
// Only listen to incoming cc values when selected voice == 0
cutoff_0 = ((voice_selector == 0), chan_0:releaseAfterJoin) :
 ccToVal(cutoff_min, cutoff_max) : hbargraph("Voice 0: Cutoff", cutoff_min, c\
utoff_max);


// Cutoff frequency value for voice 1
// Only listen to incoming cc values when selected voice == 1
cutoff_1 = ((voice_selector != 0), chan_1:releaseAfterJoin) :
 ccToVal(cutoff_min, cutoff_max) : hbargraph("Voice 1: Cutoff", cutoff_min, c\
utoff_max);

synth_1 = os.square(900)*en.smoothEnvelope(2, trigger):fi.lowpass(1, cutoff_0\
);
synth_2 = os.square(433)*en.smoothEnvelope(2, trigger):fi.lowpass(1, cutoff_1\
);

process = synth_1 + synth_2;

In thiis example we use a single physical potentiometer to control the cutoff of two synth voice filters. On our hardware controller we can change the midi channel the potentiometer sends its data to.

In faust,
CC values received from channel 0 goes to Voice 0 cutoff.
CC values reveived from channel 1 goes to Voice 1 cutoff.

This illustrate the midi join when switching from channel 0, to channel 1.

Rem: This example need some reworking, This does not work as smoothy
as wanted...
If you move the potentiometer too quick:

  • the voice_selector sometimes do not register the inputs correctly,
  • Sometime we jump around the held value and "struggle" to join....

@sletz
Copy link
Member

sletz commented Sep 17, 2019

@rmichon is going to Grenoble tomorrow. Are you going to be part of the meeting with Jerome and James ? This could be a way to show your demo ?

@bornej
Copy link
Author

bornej commented Sep 17, 2019

Yes, we meet tomorrow indeed. I definitely plan to demo the NordDrum + mi-faust!
I've not had that much fun and enthusiasm for an electronic device
in a very long time ^^.
By the way thank for your work again Faust is absolutely brilliant.

@sletz
Copy link
Member

sletz commented Sep 17, 2019

OK so I suggest that we go back on this precise releaseAfterJoin issue after the meeting. @rmichon will hopefully better understand the use-case, and see if the problem can be solved 1) with existing functions, or 2) by recoding the DSP in another way 3) possibly adding the releaseAfterJoin in the libraries if 1) and 2) are not possible

@bornej
Copy link
Author

bornej commented Sep 17, 2019

Yes, it's the most simplest thing and somehow It's hard to find a way to describe
this only textually.
I'll show it to @rmichon...
Also, a picture is worth a thousand words they say...
I though about uploading a video too.
Once you have seen it, it's absolutely evident.

Anyway the feedback you gave me was already a big help in my efforts to
make this thing clearer. We are not here yet, but we are close!

We will see tomorrow.

@rmichon
Copy link
Contributor

rmichon commented Sep 19, 2019

OK, I suggest that we have a look at this all together when @bornej visits GRAME on Tuesday.

@sletz
Copy link
Member

sletz commented Sep 19, 2019

Yes, the discussion would be then if this parameter adaptation code is needed at FAUST DSP level, or at the MIDI architecture level, since it seem to depends of the behavior of a specific device.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants