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

Patching a signal to multiple inputs #30

Closed
vitreo12 opened this issue Feb 13, 2023 · 7 comments
Closed

Patching a signal to multiple inputs #30

vitreo12 opened this issue Feb 13, 2023 · 7 comments

Comments

@vitreo12
Copy link

Hi there!

First of all: great work on this library! It's a real joy to use and it definitely pushes DSP development in Rust forward.

I'm sorry to open an issue for this, but I couldn't find an answer in the examples. How could I patch one signal to different inputs of a node (and, if possible, of different nodes)?

let modulator = (sine_hz(1.0) + 1.0) * 0.5;
let synth = (saw_hz(100.0) | modulator * 3000.0 | modulator * 0.2) >> moog();

Of course Rust comes in by not allowing to use a moved value (modulator) on the second occurrence. I wonder: how would I express this using a more fundsp syntax?

@SamiPerttu
Copy link
Owner

SamiPerttu commented Feb 14, 2023

Hi!

You can express it by using a branch:

let modulator = (sine_hz(1.0) + 1.0) * 0.5;
let synth = (saw_hz(100.0) | modulator >> (mul(3000.0) ^ mul(0.2))) >> moog();

In a generative spirit, a simple way to accomplish the same is a closure. However, in this case the deterministic pseudorandom phase system will assign different (random) initial phases for the two channels.

// Do not use this unless it is okay that frequency and Q have different phases!
let modulator = || (sine_hz(1.0) + 1.0) * 0.5;
let synth = (saw_hz(100.0) | modulator() * 3000.0 | modulator() * 0.2) >> moog();

Envelopes are not influenced by the pseudorandom phase system. A multichannel envelope is also fairly convenient:

let modulator = lfo(|t| {
    let m = (sin_hz(1.0, t) + 1.0) * 0.5;
    (m * 3000.0, m * 0.2)
});
let synth = (saw_hz(100.0) | modulator) >> moog();

As the modulator is consumed when used in an expression, it is not so simple to send it to different nodes altogether. I think duplicating the work by using a closure, maybe one that generates an envelope, is an okay way to get around this restriction:

let modulator = || lfo(|t| (sin_hz(1.0, t) + 1.0) * 0.5);
let synth = (saw_hz(100.0) | modulator() * 3000.0 | modulator() * 0.2) >> moog();

@vitreo12
Copy link
Author

vitreo12 commented Feb 14, 2023

Thanks for your answer!

I guess my main problem with the closure approaches is that we would actually be computing the modulator() sample twice, when only one sine would be sufficient. So, for this case, I would probably use your first example.

However, another question then pops to mind: would it be possible to "patch" the modulator to the inputs of two unrelated nodes?

@SamiPerttu
Copy link
Owner

For two unrelated nodes, it's not possible - some mechanism is needed to coordinate the work done by the modulator in computing samples.

@vitreo12
Copy link
Author

I could probably solve it like this:

fn saw_moog(freq: f64, cutoff: f64, q: f64) -> An<impl AudioNode<Sample = f64, Inputs = U1, Outputs = U1>> {
    (saw_hz(freq) | pass() >> (mul(cutoff) ^ mul(q))) >> moog()
}

let modulator = (sine_hz(1.0) + 1.0) * 0.5;
let node = modulator >> (saw_moog(100.0, 3000.0, 0.4 ) ^ saw_moog(200.0, 1000.0, 0.2)) >> join::<U2>();

which I think is actually a very elegant way of expressing a DSP graph

@SamiPerttu
Copy link
Owner

That's a nice way of expressing it! The join in the last line averages together the channels. If it's okay to sum instead, then it can be simplified a bit by using the bus operator:

let node = modulator >> (saw_moog(100.0, 3000.0, 0.4) & saw_moog(200.0, 1000.0, 0.2));

@vitreo12
Copy link
Author

vitreo12 commented Feb 15, 2023

This is even more elegant, thanks!

I have one more question: is there a way to achieve single sample feedback circuits? I see that there are the feedback and tick operators, but how would I feed the output of the feedback back to, for example, the frequency of the oscillator?

sine(440) ---> feedback ---> * 100
  ^                              |
  |                              |
   ------------------------------

@vitreo12
Copy link
Author

vitreo12 commented Feb 15, 2023

Something like this works (is it doing single sample feedback or block feedback?):

dc(440.0) >> feedback(sine() * 100.0) * 0.01;

Is there a way to get the output of the sine() instead of the feedback? This way I wouldn't have to scale the amplitude down.

EDIT:

I should read the docs a little better before posting, here's the solution:

dc(440.0) >> feedback2(sine(), mul(100.0));

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

No branches or pull requests

2 participants