Hi Miller,

this sounds great! First-class multi-channel support would be a real game changer.

Actually, after Winfried Ritsch told me about the "pd_snake" project, I came up with a couple of ideas on my own. You can find them here: https://git.iem.at/pd/pdsnake/-/blob/master/docu/discussion.txt. Don't know if this aligns with what you are envisioning, but it might give you some inspiration either way :-)

In particular, I would like to point out https://git.iem.at/pd/pdsnake/-/blob/master/docu/discussion.txt#L33-41. This would allow us to create patches where the channel count can be changed dynamically with a single message!

Also, multi-channel signals would give us a chance to vectorize DSP algorithms that are otherwise hard or impossible to optimize. For example, with modern AVX instructions you can compute 8 oscillators or IIR filters for the price of 1. (With proper manual loop unrolling, just like in the "*_perform8" methods, some compilers are able to vectorize it automatically.)

(one question about this... I _could_ take a sightly bigger risk and put the
last 3 fields ahead of s_refcount, etc, which I don't think anyone should
be using... this would make things look cleaner).
I think this should be fine.

typedef struct _signal
{
    int s_n;            /* *TOTAL* number of points in the array */
    t_sample *s_vec;    /* the array */
    t_float s_sr;       /* *TOTAL* samples per second */
    int s_refcount;     /* number of times used */
    int s_isborrowed;   /* whether we're going to borrow our array */
    struct _signal *s_borrowedfrom;     /* signal to borrow it from */
    struct _signal *s_nextfree;         /* next in freelist */
    struct _signal *s_nextused;         /* next in used list */
    int s_vecsize;      /* allocated size of array in points */
    	/* *** NEW STUFF *** */
    t_float s_rate;     /* sample rate */
    int s_length;       /* number of points in each channel */
    int s_nchans;       /* number of channels */
    int s_overlap;      /* number of times each sample will appear */
}
Personally, I would keep s_n as the number of samples per channel. The total number of samples is simply s_n * s_nchans. Existing externals - that do not know about s_nchans - would effectively operate on the first channel and ignore the rest. Newer multi-channel-aware externals, on the other hand, may use all the channels.

I also think that DSP objects would need a new API method to create multi-channel outputs. The general idea is that the input channel counts are taken from upstream, but the output channel counts are specified by the object and passed downstream. (There might be objects where input and output channel count differs; any kind of merger/splitter/mixer objects comes to my mind.)

I think I have some more ideas/notes in one of my notebooks. I can look them up and see if there's something useful.

Anyway, I am quite excited about this!

Cheers,

Christof

On 01.09.2022 21:58, Miller Puckette via Pd-dev wrote:
Hi Pd dev -

I'm preparing to rework the DSP network to give tilde objects more control over
their inputs and outputs, for instance allowing for multi-channel signals and to
allow objects to decide for themselves whether to promote float inputs to
signals (so that you don't have to say "+~ 0 to get the faster version, and so
that I can make the hip/lop/bp/vcf frequency and Q inputs available as signals
or as floats).

Of course I mean to make this compatible with existin DSP objects, although for
simplicity I'm going to propose one slightly risky move, changing the size of
the t_signal structure -- as iohannes mentioned a few years ago, this seems
very unlikely to break anyone's tilde objects.  The new structure would now
look as follows:

typedef struct _signal
{
    int s_n;            /* *TOTAL* number of points in the array */
    t_sample *s_vec;    /* the array */
    t_float s_sr;       /* *TOTAL* samples per second */
    int s_refcount;     /* number of times used */
    int s_isborrowed;   /* whether we're going to borrow our array */
    struct _signal *s_borrowedfrom;     /* signal to borrow it from */
    struct _signal *s_nextfree;         /* next in freelist */
    struct _signal *s_nextused;         /* next in used list */
    int s_vecsize;      /* allocated size of array in points */
    	/* *** NEW STUFF *** */
    t_float s_rate;     /* sample rate */
    int s_length;       /* number of points in each channel */
    int s_nchans;       /* number of channels */
    int s_overlap;      /* number of times each sample will appear */
}

(one question about this... I _could_ take a sightly bigger risk and put the
last 3 fields ahead of s_refcount, etc, which I don't think anyone should
be using... this would make things look cleaner).

For example, the FFT object's outputs should really have a sample rate of 1/N
times the input sample rate, a vector length of 1, and a channel count of N. For
compatibility, I'd take the "s_n" field to just be N, although in the future one
could optionally use s_length as N and run as many DFTs as there are channels. 
(This would be incompatible with current practice in wierd situations in which
one ran an fft~ objects into another fft~ objects as input - a real bad idea but
perhaps the only way in vanilla to time-reverse a signal block by block, so I
bet someone is depending on being able to do that :)

Meanwhile, before the DSP routine is called, all signal inputs are populated
with vectors by promoting float inputs to signals, all inputs are guaranteed to
have the same s_n field, and all outputs are automatically generated to match
all the inputs.  I want that to be the default option but to allow the object to
access non-matching signals, not-filled-in signals (so that it can schedule
scalar versions, as in "+~", and to take care of generating its own output
signals (which may thus have different sizes from the input signals).

I could then design a "trunk~" object that combines or splits one-channel
signals into multichannel ones, and I could extend +~, etc., to know how to add
one-channel signals to multichannel ones.  Also, clone~ could (optionally)
unpack multichannel signals to distribute among copies.

it might also be useful to have the option to ask for the output signals,
if auto-generated, never to reuse the same vector as the input; I guess that
can be provided if there's a demand for it.

I'm thinking this is a big enough and dangerous enough change that I should do
it on a separate branch first.  I've got some travel coming up but hope to start
coding soonish.

cheers
Miller



_______________________________________________
Pd-dev mailing list
Pd-dev@lists.iem.at
https://lists.puredata.info/listinfo/pd-dev