grame-cncm / faust

Functional programming language for signal processing and sound synthesis
http://faust.grame.fr
Other
2.59k stars 325 forks source link

Proof of concept for discussion: add taps (throw-catch primitives) #995

Closed nuchi closed 9 months ago

nuchi commented 10 months ago

Builds the functionality from https://github.com/nuchi/faust-tap-library/ into the faust language. There was some discussion of this recently on the discord, and I had a clear vision for what I personally thought it should look like, so I just went and tried it. I don't expect that just because I went and built it, that means anyone else will like it! But I think it's easier to have a discussion about something that already exists than trying to imagine what form it might have.

It adds a tap definition and retrieval syntax, #foo and $foo, which have inputs/outputs (1, 0) and (0, 1) respectively.

I implemented it mostly by modifying the propagate function. The boxes are passed through unchanged to the signal phase. I modified the propagate function so that in addition to passing slotenvs inward, it also passes tapenvs outward (i.e. returns a tapenv in addition to a list of signals).

The second halves of par/seq/merge/split/rec each have access to the tapenv of the first half.

~Known issue: The implementation of propagate for rec is broken right now, I didn't know the right way to deal with the tap envs because I don't understand how the recursive tree things work. Right now if I try to pull a tap out of the recursive part, I'll get an assertion in recursive-tree.cpp at this line: cerr << "ASSERT : one Bruijn reference found\n";.~

UPDATE: I added functionality for rec.

An example:

foo.dsp:

import("stdfaust.lib");
A(k) = *(k);

complicatedExpression = _,_,_,_ : (
        (A(1) <: _,#A),
         A(2),
         A(3),
        (A(4) <: _,#B),
         A(5)
    ) ~ (_,_,_,_,_ :> _)
      : si.block(5)
      ;
process = complicatedExpression , $B;
% ~/faust/build/bin/faust -I ~/.faust/share/faust/  -d -norm -svg foo.dsp 
process = (_,_,_,_ : ((_,1 : *<:_,#A),(_,2 : *),(_,3 : *),(_,4 : *<:_,#B),(_,5 : *))~(_,_,_,_,_:>_) : !,!,!,!,!),$B;
process has 4 inputs, and 1 output
output signals are : 
{IN2*4}
(IN[2]*4)

(4.0f*IN[2])
Dump normal form finished...

foo-svg/process.svg:

Click to expand ![process](https://github.com/grame-cncm/faust/assets/16771734/c8502683-668c-4283-b342-23543752a04b)
magnetophon commented 10 months ago

Super cool, thank you so much!

oleg-nesterov commented 10 months ago

COOOOOOOOOOOOOOOL ;)

I'll try apply this patch and play with faust later (g++ on my laptop is too old to compile faust), let me ask a couple of questions right now.

So, iiuc

    +    } else if (isBoxTap(box)) {
    +        *inum = 0;
    +        *onum = 1;
    +    } else if (isBoxTapDef(box)) {
    +        *inum = 1;
    +        *onum = 0;

means that #X has no outputs, great! so I guess

    process = #X : $X;

should act as

    process = _;

right?

But, what will

    process = #X,#X,#X : $X;

do? Won't it "silently" drop 2 inputs of 3?

and I guess something like

    x1 = 1 : #X : $X;
    x2 = 2 : #X : $X;

    process = x1, x2;

won't work "as expected" ? it will output (1,1) or (2,2) but not (1,2) ?

and can't resist... I don't really like the syntax because it conflicts with fpp which already (ab)uses the $name syntax... but don't worry, nobody uses fpp, it can use ${name} instead.

Thanks!

nuchi commented 10 months ago

so I guess

    process = #X : $X;

should act as

    process = _;

right?

Correct!

% ~/faust/build/bin/faust -d -norm <(echo 'process = #X : $X;')                     
process = #X : $X;
process has 1 input, and 1 output
output signals are : 
{IN0}
(IN[0])

(IN[0])
Dump normal form finished...

But, what will

    process = #X,#X,#X : $X;

do? Won't it "silently" drop 2 inputs of 3?

Also correct. Right now for the proof-of-concept I didn't implement any checking for duplicate definitions, and it will grab the first one it finds.

% ~/faust/build/bin/faust -d -norm <(echo 'process = #X,#X,#X : $X;')                      
process = #X,#X,#X : $X;
process has 3 inputs, and 1 output
output signals are : 
{IN0}
(IN[0])

(IN[0])

and I guess something like

    x1 = 1 : #X : $X;
    x2 = 2 : #X : $X;

    process = x1, x2;

won't work "as expected" ? it will output (1,1) or (2,2) but not (1,2) ?

As I've implemented it right now, correct:

process = (1 : #X : $X),(2 : #X : $X);
process has 0 inputs, and 2 outputs
output signals are : 
{1,1}
(1, 1)

(1, 1)

But it depends on the order in which I pass the slot and tap envs around. I just played around with it locally so that x1 and x2 look "closer" before they look "farther" and then I can get it to turn into (1, 2). But really to avoid such ambiguities the programmer should avoid duplicate names. Though I can see a case for doing stuff with paths so that one can have abstractions with taps in them, instead of just relying on the variable name.

and can't resist... I don't really like the syntax because it conflicts with fpp which already (ab)uses the $name syntax... but don't worry, nobody uses fpp, it can use ${name} instead.

I selected tokens that were not currently used by the language, but was unaware of fpp :)

oleg-nesterov commented 10 months ago

FYI, your patch doesn't apply to master-dev, it conflicts with the recent changes in compiler/parser.

I think you shouldn't include the changes in parser/faustparser.{hpp,cpp}, Stephane/Yann can do 'make -C compiler/parser' themselves.

to avoid such ambiguities the programmer should avoid duplicate names

This looks like a serious limitation to me... But better than nothing.

I selected tokens that were not currently used by the language, but was unaware of fpp :)

Yes, yes, I understand, please forget.

nuchi commented 10 months ago

FYI, your patch doesn't apply to master-dev, it conflicts with the recent changes in compiler/parser.

Right. Easy enough to change later if, in the end, the patch is accepted.

This looks like a serious limitation to me... But better than nothing.

It's not a fundamental limitation and could be changed to allow groups, I think — I wrote it this way in the quickest and most straightforward way I could figure out. I wouldn't mind redoing it later if there's interest.

sletz commented 9 months ago

This kind of patch will not be accepted. Our current idea is to start from the Widget Modulation model, and see how it could be adapted for bargraph, and add them as additional audio outputs.