Pattern timing issue with OffsetOut

classic Classic list List threaded Threaded
15 messages Options
Reply | Threaded
Open this post in threaded view
|

Pattern timing issue with OffsetOut

James Opstad
This post was updated on .
Hi all,

I've been having some problems when using patterns for granulation. The code below highlights the issue. All the examples should recreate the original sound (either a sine wave or white noise) as each grain envelope starts midway through the previous one. With the first example you'll hear that the grains gradually get out of phase until there is a dip in the sound where phase cancellation occurs. With the other examples this phase shifting happens more rapidly. It seems things are getting out of sync. I have tried this on macOS and Linux with the same audio interface. What's strange is that they both exhibit the problem but in different ways (the dip in the first example is not evident on Linux). Any help would be much appreciated. Apologies if I've missed something obvious.

(
// groups
~g_source = Group.tail;
~g_grains = Group.tail;

// busses
~sin = Bus.audio(s, 1);
~noise = Bus.audio(s, 1);

// SynthDefs
SynthDef(\sin, { |freq, out|
        var sound = SinOsc.ar(freq);
        Out.ar(out, sound);
}).add;

SynthDef(\noise, { |out|
        var sound = WhiteNoise.ar(0.2);
        Out.ar(out, sound);
}).add;

SynthDef(\grain, { |in, sustain, amp|
        var input = In.ar(in);
        var env = EnvGen.ar(Env.sine(sustain, amp), doneAction: 2);
        OffsetOut.ar(0, Pan2.ar(input * env, 0));
}).add;

// Grains
~grains = Pbindef(\pattern,
        \instrument, \grain,
        \group, ~g_grains,
        \sustain, 1,
        \amp, 0.5,
        \dur, Pkey(\sustain) * 0.5
)
)

(
// Start
x = Synth(\sin, [\freq, 220, \out, ~sin], ~g_source);
y = Synth(\noise, [\out, ~noise], ~g_source);
z = ~grains.play;
)

// Examples (try one at a time)
Pbindef(\pattern, \in, ~sin, \sustain, 1);
Pbindef(\pattern, \in, ~sin, \sustain, 0.5);
Pbindef(\pattern, \in, ~sin, \sustain, 0.25);
Pbindef(\pattern, \in, ~noise, \sustain, 1);
Pbindef(\pattern, \in, ~noise, \sustain, 0.5);
Pbindef(\pattern, \in, ~noise, \sustain, 0.25);
Reply | Threaded
Open this post in threaded view
|

Re: Pattern timing issue with OffsetOut

ddw_music
James Opstad wrote
I've been having some problems when using patterns for granulation. The code below highlights the issue. All the examples should recreate the original sound (either a sine wave or white noise) as each grain envelope starts midway through the previous one. With the first example you'll hear that the grains gradually get out of phase until there is a dip in the sound where phase cancellation occurs. With the other examples this phase shifting happens more rapidly.
No time to test, but it looks like OffsetOut.ar compensates the output signal for the scheduled time stamp, while In.ar doesn't.

A Synth can begin and end on a control block boundary only -- no exceptions. If the /s_new message is scheduled by a timestamp that falls in the middle of a control block, the server has to start playing the synth at the preceding control block boundary. OffsetOut takes the synth's normal output signal, and delays it by the right number of samples so that you hear it at the right time.

OffsetOut does NOT delay the synth's entire calculation to begin on the timestamp. In.ar starts on the control block, not in the middle. So, when the synth starts, In.ar reads the value for control block boundary (cbb) + 0, but OffsetOut will output the corresponding sample at cbb + x. Each synth will delay the input signal by a different amount, and that would account for the cancellation you're experiencing.

I don't think it's possible for SC to fix this. A hypothetical OffsetIn UGen would have to read sample values from a future control, which obviously it couldn't do as it hasn't happened yet. So I think we're stuck with this situation: In.ar is not compatible with OffsetOut.ar, unless the slight delays are acceptable.

hjh
Reply | Threaded
Open this post in threaded view
|

Re: Pattern timing issue with OffsetOut

James Opstad

Thanks. That makes sense. So this would only be an issue when using live input rather than reading from a buffer. There may be a way round this by having the grain synth running constantly with the envelope triggered by a separate impulse synth. Here is a quick starting point:


(
// groups
~g_source = Group.tail;
~g_impulse = Group.tail;
~g_grains = Group.tail;

// busses
~sin = Bus.audio(s, 1);
~impulse = Array.fill(2, { Bus.audio(s, 1); });
~noise = Bus.audio(s, 1);

// SynthDefs
SynthDef(\sin, { |freq, out|
var sound = SinOsc.ar(freq);
Out.ar(out, sound);
}).add;

SynthDef(\impulse, { |out|
var impulse = Impulse.ar(0);
OffsetOut.ar(out, impulse);
}).add;

SynthDef(\grain, { |in, gate_in, sustain, amp|
var input = In.ar(in);
var gate = In.ar(gate_in);
var env = EnvGen.ar(Env.sine(sustain, amp), gate);
Out.ar(0, Pan2.ar(input * env, 0));
}).add;
)

(
// Start
Synth(\sin, [\freq, 220, \out, ~sin], ~g_source);
Array.fill(2, { |i| Synth(\grain, [\in, ~sin, \gate_in, ~impulse[i], \sustain, 1, \amp, 0.5], ~g_grains); });

Pbind(
\instrument, \impulse,
\group, ~g_impulse,
\out, Pseq([~impulse[0], ~impulse[1]], inf),
\dur, 0.5
).play;
)

I don't think there is any way to synchronise changing the envelope sustain with triggering the impulse though. Is that right? Not using patterns and moving it all onto the server may be the only solution.


Sorry to anyone who tried my original example and didn't get any output. There should be an initial sustain value inside ~grains. I will edit the post.




From: ddw_music [via New SuperCollider Mailing Lists Forums (Use These!!!)] <ml-node+[hidden email]>
Sent: 09 February 2017 03:38
To: James Opstad
Subject: Re: Pattern timing issue with OffsetOut
 
James Opstad wrote
I've been having some problems when using patterns for granulation. The code below highlights the issue. All the examples should recreate the original sound (either a sine wave or white noise) as each grain envelope starts midway through the previous one. With the first example you'll hear that the grains gradually get out of phase until there is a dip in the sound where phase cancellation occurs. With the other examples this phase shifting happens more rapidly.
No time to test, but it looks like OffsetOut.ar compensates the output signal for the scheduled time stamp, while In.ar doesn't.

A Synth can begin and end on a control block boundary only -- no exceptions. If the /s_new message is scheduled by a timestamp that falls in the middle of a control block, the server has to start playing the synth at the preceding control block boundary. OffsetOut takes the synth's normal output signal, and delays it by the right number of samples so that you hear it at the right time.

OffsetOut does NOT delay the synth's entire calculation to begin on the timestamp. In.ar starts on the control block, not in the middle. So, when the synth starts, In.ar reads the value for control block boundary (cbb) + 0, but OffsetOut will output the corresponding sample at cbb + x. Each synth will delay the input signal by a different amount, and that would account for the cancellation you're experiencing.

I don't think it's possible for SC to fix this. A hypothetical OffsetIn UGen would have to read sample values from a future control, which obviously it couldn't do as it hasn't happened yet. So I think we're stuck with this situation: In.ar is not compatible with OffsetOut.ar, unless the slight delays are acceptable.

hjh


new-supercollider-mailing-lists-forums-use-these.2681727.n2.nabble.com
Pattern timing issue with OffsetOut. Hi all, I've been having some problems when using patterns for granulation. The code below highlights the issue. All the examples should recreate the original...

To unsubscribe from Pattern timing issue with OffsetOut, click here.
NAML
Reply | Threaded
Open this post in threaded view
|

Re: Pattern timing issue with OffsetOut

Scott Wilson-3
Thanks James (H)! We couldn't quite figure it out, but In was the missing piece.

Could you not solve this however by not using OffsetOut, but instead using SubSampleOffset to delay both the input and the Env? Away from my machine just now. Will test later!

> On 9 Feb 2017, at 11:21, James Opstad <[hidden email]> wrote:
>
> Thanks. That makes sense. So this would only be an issue when using live input rather than reading from a buffer. There may be a way round this by having the grain synth running constantly with the envelope triggered by a separate impulse synth. Here is a quick starting point:
>
>
> (
> // groups
> ~g_source = Group.tail;
> ~g_impulse = Group.tail;
> ~g_grains = Group.tail;
>
> // busses
> ~sin = Bus.audio(s, 1);
> ~impulse = Array.fill(2, { Bus.audio(s, 1); });
> ~noise = Bus.audio(s, 1);
>
> // SynthDefs
> SynthDef(\sin, { |freq, out|
> var sound = SinOsc.ar(freq);
> Out.ar(out, sound);
> }).add;
>
> SynthDef(\impulse, { |out|
> var impulse = Impulse.ar(0);
> OffsetOut.ar(out, impulse);
> }).add;
>
> SynthDef(\grain, { |in, gate_in, sustain, amp|
> var input = In.ar(in);
> var gate = In.ar(gate_in);
> var env = EnvGen.ar(Env.sine(sustain, amp), gate);
> Out.ar(0, Pan2.ar(input * env, 0));
> }).add;
> )
>
> (
> // Start
> Synth(\sin, [\freq, 220, \out, ~sin], ~g_source);
> Array.fill(2, { |i| Synth(\grain, [\in, ~sin, \gate_in, ~impulse[i], \sustain, 1, \amp, 0.5], ~g_grains); });
>
> Pbind(
> \instrument, \impulse,


_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Reply | Threaded
Open this post in threaded view
|

Re: Pattern timing issue with OffsetOut

Scott Wilson-3
Okay, sorry, talking rubbish. OffsetOut is sample accurate. SubsampleOffset just gives you the additional subsample offset.

So what we’d need in this case is a SampleOffset UGen, which would just return mSampleOffset at the time the synth was scheduled. This would be trivial to implement AFAICS and I can see the use case.

Though this raises the question of why not make a subsample accurate version of OffsetOut? (I guess the existing one isn’t for reasons of efficiency, since it requires no interpolation)

S.

> On 9 Feb 2017, at 12:32, Scott Wilson <[hidden email]> wrote:
>
> Thanks James (H)! We couldn't quite figure it out, but In was the missing piece.
>
> Could you not solve this however by not using OffsetOut, but instead using SubSampleOffset to delay both the input and the Env? Away from my machine just now. Will test later!
>
>> On 9 Feb 2017, at 11:21, James Opstad <[hidden email]> wrote:
>>
>> Thanks. That makes sense. So this would only be an issue when using live input rather than reading from a buffer. There may be a way round this by having the grain synth running constantly with the envelope triggered by a separate impulse synth. Here is a quick starting point:
>>
>>
>> (
>> // groups
>> ~g_source = Group.tail;
>> ~g_impulse = Group.tail;
>> ~g_grains = Group.tail;
>>
>> // busses
>> ~sin = Bus.audio(s, 1);
>> ~impulse = Array.fill(2, { Bus.audio(s, 1); });
>> ~noise = Bus.audio(s, 1);
>>
>> // SynthDefs
>> SynthDef(\sin, { |freq, out|
>> var sound = SinOsc.ar(freq);
>> Out.ar(out, sound);
>> }).add;
>>
>> SynthDef(\impulse, { |out|
>> var impulse = Impulse.ar(0);
>> OffsetOut.ar(out, impulse);
>> }).add;
>>
>> SynthDef(\grain, { |in, gate_in, sustain, amp|
>> var input = In.ar(in);
>> var gate = In.ar(gate_in);
>> var env = EnvGen.ar(Env.sine(sustain, amp), gate);
>> Out.ar(0, Pan2.ar(input * env, 0));
>> }).add;
>> )
>>
>> (
>> // Start
>> Synth(\sin, [\freq, 220, \out, ~sin], ~g_source);
>> Array.fill(2, { |i| Synth(\grain, [\in, ~sin, \gate_in, ~impulse[i], \sustain, 1, \amp, 0.5], ~g_grains); });
>>
>> Pbind(
>> \instrument, \impulse,
>
>
> _______________________________________________
> sc-users mailing list
>
> info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
> archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
> search: http://www.listarc.bham.ac.uk/lists/sc-users/search/


_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Reply | Threaded
Open this post in threaded view
|

Re: Pattern timing issue with OffsetOut

daniel-mayer
In reply to this post by James Opstad

Am 09.02.2017 um 12:21 schrieb James Opstad <[hidden email]>:

> I don't think there is any way to synchronise changing the envelope sustain with triggering the impulse though. Is that right? Not using patterns and moving it all onto the server may be the only solution.

Hi,

I've a bit lost track of what is your primary goal.
Do you want to granulate a live input with patterns that way (as accurate as possible)
and without any use of a buffer ?

Besides from the offset issue there's another related problem you might run into with EnvGen,
very short grains (below block length) are not possible therewith, citing myself:

http://new-supercollider-mailing-lists-forums-use-these.2681727.n2.nabble.com/Very-small-grains-cause-CPU-avalanche-td6330872.html


>A workaround that served my needs well then was defining the actual
>envelope in the synth by a wavetable oscillator and leaving the
>EnvGen envelopes above the critical length, having something like

>PlayBuf.ar(...) * Osc.ar(envBufnum, envOscFreq) * EnvGen.ar(...)

>Created a number of buffers before with Envs, .asWavetable and .sendCollection
>to be able to switch by Pbind.
>Envelope's shape can be influenced in the language by Env definition
>and scaled in the synth by envOscFreq.


Using that strategy you can keep using patterns.
I would definitely recommend to do so as trying something similar server-side
with triggered EnvGen or DemandEnvGen can be tricky.

Some time ago I had a very hard time and at the end didn't get satifying results for more than one reason ...

http://new-supercollider-mailing-lists-forums-use-these.2681727.n2.nabble.com/triggering-envelopes-with-demand-rate-ugens-td7606487.html


miSCellaneous lib's buffer granulation tutorial contains different granulation strategies,
thus might give some ideas, though, as it name says, concentrates on buffers.

But indeed your approach of live granulation using patterns is interesting.
For live input granulation I rather used GrainIn yet, but as I find pattern granulation more flexible in general
I should have a closer look at this, so thanks for the nudge.

Regards

Daniel

----------------------------------------------------
http://daniel-mayer.at/software_en.htm
----------------------------------------------------















_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Reply | Threaded
Open this post in threaded view
|

Re: Pattern timing issue with OffsetOut

James Opstad

Hi,


Yes, that's right, I was trying to granulate a live input accurately using patterns.


>PlayBuf.ar(...) * Osc.ar(envBufnum, envOscFreq) * EnvGen.ar(...) 


In this example, what would go in the EnvGen? I'm not quite clear on the relationship between this and the envelope stored in a buffer.


I've just installed miSCellaneous lib and will have a look.


Thanks,

James




From: Daniel Mayer [via New SuperCollider Mailing Lists Forums (Use These!!!)] <ml-node+[hidden email]>
Sent: 09 February 2017 23:00
To: James Opstad
Subject: Re: Pattern timing issue with OffsetOut
 

Am 09.02.2017 um 12:21 schrieb James Opstad <[hidden email]>:

> I don't think there is any way to synchronise changing the envelope sustain with triggering the impulse though. Is that right? Not using patterns and moving it all onto the server may be the only solution.

Hi,

I've a bit lost track of what is your primary goal.
Do you want to granulate a live input with patterns that way (as accurate as possible)
and without any use of a buffer ?

Besides from the offset issue there's another related problem you might run into with EnvGen,
very short grains (below block length) are not possible therewith, citing myself:

http://new-supercollider-mailing-lists-forums-use-these.2681727.n2.nabble.com/Very-small-grains-cause-CPU-avalanche-td6330872.html


>A workaround that served my needs well then was defining the actual
>envelope in the synth by a wavetable oscillator and leaving the
>EnvGen envelopes above the critical length, having something like

>PlayBuf.ar(...) * Osc.ar(envBufnum, envOscFreq) * EnvGen.ar(...)

>Created a number of buffers before with Envs, .asWavetable and .sendCollection
>to be able to switch by Pbind.
>Envelope's shape can be influenced in the language by Env definition
>and scaled in the synth by envOscFreq.


Using that strategy you can keep using patterns.
I would definitely recommend to do so as trying something similar server-side
with triggered EnvGen or DemandEnvGen can be tricky.

Some time ago I had a very hard time and at the end didn't get satifying results for more than one reason ...

http://new-supercollider-mailing-lists-forums-use-these.2681727.n2.nabble.com/triggering-envelopes-with-demand-rate-ugens-td7606487.html


miSCellaneous lib's buffer granulation tutorial contains different granulation strategies,
thus might give some ideas, though, as it name says, concentrates on buffers.

But indeed your approach of live granulation using patterns is interesting.
For live input granulation I rather used GrainIn yet, but as I find pattern granulation more flexible in general
I should have a closer look at this, so thanks for the nudge.

Regards

Daniel

----------------------------------------------------
http://daniel-mayer.at/software_en.htm
----------------------------------------------------















_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/



To unsubscribe from Pattern timing issue with OffsetOut, click here.
NAML
Reply | Threaded
Open this post in threaded view
|

Re: Pattern timing issue with OffsetOut

julian.rohrhuber
In reply to this post by Scott Wilson-3

> On 09.02.2017, at 14:21, Scott Wilson <[hidden email]> wrote:
>
> Okay, sorry, talking rubbish. OffsetOut is sample accurate. SubsampleOffset just gives you the additional subsample offset.
>
> So what we’d need in this case is a SampleOffset UGen, which would just return mSampleOffset at the time the synth was scheduled. This would be trivial to implement AFAICS and I can see the use case.
>
> Though this raises the question of why not make a subsample accurate version of OffsetOut? (I guess the existing one isn’t for reasons of efficiency, since it requires no interpolation)

When I wrote SubsampleOffset, I decided to leave the type of interpolation to the user. You just wrap your output in an appropriate DelayL or DelayC, I thought. But of course if this is interesting, one could combine this into one.
_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Reply | Threaded
Open this post in threaded view
|

Re: Pattern timing issue with OffsetOut

daniel-mayer
In reply to this post by James Opstad

Am 14.02.2017 um 13:35 schrieb James Opstad <[hidden email]>:

> Hi,
>
>
> Yes, that's right, I was trying to granulate a live input accurately using patterns.
>
>
>> PlayBuf.ar(...) * Osc.ar(envBufnum, envOscFreq) * EnvGen.ar(...)
>
>
> In this example, what would go in the EnvGen? I'm not quite clear on the relationship between this and the envelope stored in a buffer.



This was used here just for ending the synth with doneAction, I should have mentioned that. Now, with rewriting an example, it came to my mind that it's probably easier to use BufRd instead of Osc, as with Osc you need the Wavetable format and BufRd can simply move through the buffer, which is more straight. But ... BufRd seems to have an issue with loop = 0 and very short durations (I should bring that up in a separate thread ...), so I added an additional gate with Duty, which can also take a doneAction arg.


// create a sine buffer for envelope (you could also fill with hanning of course,
// but I take a sine wave here just for this "env-as-audio" example
// to avoid unintended DC with bad values)

(
u = Signal.sineFill(1000, [1]);

v = Buffer.loadCollection(s, u);
)


// a synthdef for checking with envelope as audio waveform first
(
SynthDef(\live_gran_1, { |out, grainBuf, grainDur, pan = 0, amp = 1, interpolation = 4|
        var env, numFrames = BufFrames.kr(grainBuf), gate;
        env = BufRd.ar(
                1,
                grainBuf,
                Phasor.ar(
                        0,
                        numFrames / (grainDur * s.sampleRate),
                        0,
                        numFrames - 1
                ),
                loop: 0,
                interpolation: interpolation
        );
        gate = Duty.ar(
                grainDur,
                0,
                Dseq([1, 0]),
                doneAction: 2
        );
        OffsetOut.ar(out, Pan2.ar(env * gate, pan, amp))
}).add
)


This example shows the limitations of accuracy with pattern sequencing in realtime, this is independant from the relation between In.ar and OffsetOut.
Whereas the waveform taken as envelope should be sufficient for most practical granulation purposes (here 5 ms grain duration), taken as audio, as here, we hear irregularities. Besides soft overtones (probably coming from sine wave interpolation in BufRd) there are soft plops every 10 seconds or so. This comes from a timing calibration, which causes pattern sequencing not to be sample accurate in realtime, for details and an example see

http://new-supercollider-mailing-lists-forums-use-these.2681727.n2.nabble.com/sample-accuracy-with-lang-timing-was-Various-timing-issues-td7615278.html

It can be avoided with NRT sequencing, but the latter is probably not what you want. I don't know what you intend to granulate, though I think for most practical applications this accuracy should do. If not, it might really be worth rethinking the whole process server-side.


// envelope as audio
(
p = Pbind(
        \instrument, \live_gran_1,
        \amp, 0.2,
        \dur, 0.005,
        \grainBuf, v,
        \grainDur, 0.005,
// \grainDur, 0.002,
        \addAction, \addToTail,
        \interpolation, 4,
        \out, 0
)
)

x = p.play;

s.scope;

s.freqscope;

// for recording and analysing audio
s.makeGui;


x.stop;

////////


Here an example for live granulation with reverb on single grains,
use headphones to avoid feedback


(
h = Signal.hanningWindow(1000);

w = Buffer.loadCollection(s, h);

a = Bus.audio(s, 1);

b = Bus.audio(s, 2);
)



(
s.meter;

SynthDef(\live_gran_2, { |out, in, grainBuf, grainDur, pan = 0, amp = 1, interpolation = 4|
        var inSig, env, numFrames = BufFrames.kr(grainBuf), gate;
        inSig = In.ar(in, 1);
        env = BufRd.ar(
                1,
                grainBuf,
                Phasor.ar(
                        0,
                        numFrames / (grainDur * s.sampleRate),
                        0,
                        numFrames - 1
                ),
                loop: 0
        );
        gate = Duty.ar(
                grainDur,
                0,
                Dseq([1, 0]),
                doneAction: 2
        );
        OffsetOut.ar(out, Pan2.ar(inSig * env * gate, pan, amp))
}).add
)




(
// make a group for ordering, granulation synths must be between input and effect
// start input and reverb effect
// use headphones to avoid feedback

g = Group.new;


x = { Out.ar(a, SoundIn.ar()) }.play(g, addAction: \addToHead);

{
        GVerb.ar(
                In.ar(b),
                roomsize: 10,
                revtime: 2,
                damping: 0.4,
                inputbw: 0.2,
                drylevel: -3,
                earlyreflevel: -10,
                taillevel: -12
        )
}.play(g, addAction: \addAfter);
)


// start live granulation
(
p = Pbind(
        \instrument, \live_gran_2,
        \amp, 0.2,
        \in, a,
        \dur, 0.01,
        \grainBuf, w.bufnum,
        \grainDur, 0.005,
        \addAction, \addToTail,
        \group, g,
        \interpolation, 4,
        // some grains to out and some to reverb
        \out, Pwrand([0, b], [2, 1].normalizeSum, inf)
);

q = p.play;
)

q.stop


Also consider running several granulation streams with fxs (e.g. delay) in parallel. With delay this could come close to variants where you store the input in a buffer. You can do similar and with more fx options also with miSCellanous' PbindFx.

For live granulation with single synthdef I'd have to rethink my older tries which lead to troubles, but I suppose there must be a viable server-side solution also for short grains as you suggested. The In / OffsetOut discrepancy is ignored in above examples, you'd have to check if in your context this is feasible or not.


Regards

Daniel

-----------------------------
www.daniel-mayer.at
-----------------------------















_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Reply | Threaded
Open this post in threaded view
|

Re: Pattern timing issue with OffsetOut

daniel-mayer
In reply to this post by ddw_music

Am 09.02.2017 um 04:38 schrieb ddw_music <[hidden email]>:

> So I think we're stuck with this
> situation: In.ar is not compatible with OffsetOut.ar, unless the slight
> delays are acceptable.


There is a way, at the moment I encountered a few limitations, but they are probably irrelevant practically, delays can be shrinked from block length to 0 or 1 sample.

We can measure the offset time, which an OffsetOut delays the synth, this can be done via an impulse sent to a dedicated bus. Then there are basically two strategies to use this measured offset: (1) use it with Out and a delayed signal using In, (2) use the measured delay with a gate for In.ar, probably the prefered way.

Here's a proof of concept with an impulse as test signal, it might be that implementation is not ideal. At that point I'm seeing accuracy deviations of a single sample from time to time on my machine, also there seems to be an issue with passing the impulse bus, it must be hard-wired to the SynthDef. The impulse bus gets a negative and a positive sample per synth. This should work for scheduling with one impulse bus as long as delta between synths isn't below the critical block length. But even in this case the procedure will work if we take two or more synthdefs with different buses and alternate them in scheduling.


(
// impulse bus

i = Bus.audio(s, 1);

SynthDef(\offset_test, { |out = 0, balance = 1|
        var imp = Impulse.ar(0.001), offset, in,
                defaultOffset = ControlRate.ir.reciprocal;
        // determine delay of OffsetOut
        OffsetOut.ar(i, imp * 2);
        Out.ar(i, imp.neg);
        in = In.ar(i, 1);
        offset = Timer.ar(in);
        offset = Select.ar(offset.sign, [
                DC.ar(defaultOffset),
                offset - (SampleDur.ir * 0.9999)
        ]);

        // just for finishing the synth
        Line.ar(dur: 0.1, doneAction: 2);

        // with balance = 1 impulses should cancel out
        OffsetOut.ar(0, imp.neg);
        Out.ar(0, DelayN.ar(imp * balance, 0.01, offset));
}).add
)


(
// with balance = 1 impulses should cancel out
~balance = 1;

p = Pbind(
        \instrument, \offset_test,
        \dur, 0.1,
        \impulseBus, i,
        \balance, Pfunc { ~balance }
).play
)

// for record and analyse
s.makeGui

// impulses become audible
~balance = 1.2


p.stop



For more practical working we can define two pseudo ugens:

OffsetOutDelay returns the current delay of this synth with OffsetOut,
DelayOut uses it to delay an input signal accordingly


// DelayOut {
// *ar { |outBus, channelsArray, impulseBus|
// var impulseIn, offset, defaultOffset = ControlRate.ir.reciprocal,
// impulse, thr = 1e-6;
// impulse = Impulse.ar(thr);
// OffsetOut.ar(impulseBus, impulse * 2);
// Out.ar(impulseBus, impulse.neg);
// impulseIn = In.ar(impulseBus);
// offset = Timer.ar(impulseIn);
// offset = Select.ar(offset.sign, [
// DC.ar(defaultOffset),
// offset - (SampleDur.ir * 0.9999)
// ]);
// ^Out.ar(outBus, DelayN.ar(channelsArray, defaultOffset * 1.0001, offset))
// }
// }


OffsetOutDelay {
        *ar { |impulseBus|
                var impulseIn, offset, defaultOffset = ControlRate.ir.reciprocal,
                        impulse, thr = 1e-4;
                impulse = Impulse.ar(0);
                OffsetOut.ar(impulseBus, impulse * 2);
                Out.ar(impulseBus, impulse.neg);
                impulseIn = In.ar(impulseBus);
                offset = Timer.ar(impulseIn);
                ^Select.ar(offset.sign, [
                        DC.ar(defaultOffset),
                        offset - (SampleDur.ir * (1 - thr))
                ])
        }
}



DelayOut {
        *ar { |outBus, channelsArray, impulseBus|
                var offset, defaultOffset = ControlRate.ir.reciprocal, thr = 1e-4;
                offset = OffsetOutDelay.ar(impulseBus);
                ^Out.ar(outBus, DelayN.ar(channelsArray, defaultOffset * (1 + thr), offset))
        }
}


///////////////////////////////


Recompile ...


Live granulation example for strategy (1), input is delayed and
DelayOut enters as OffsetOut would, probably not desirable.

(
a = Bus.audio(s, 1);
b = Bus.audio(s, 1);
)

(
SynthDef(\grain_delay, { |rel = 0.1, inBus|
        var sig, inSig, env = EnvGen.ar(Env([1, 0], [rel]), doneAction: 2);
        inSig = In.ar(inBus, 1);
        sig = inSig * env;
        Out.ar(0, inSig);
        DelayOut.ar(1, sig, b);
}).add
)


// start input signal
x = { Out.ar(a, Impulse.ar(100, 0, 0.1)) }.play;


// input L and granulation R
(
p = Pbind(
        \instrument, \grain_delay,
        \inBus, a,
        \dur, 0.1,
        \addAction, \addToTail
).play
)

// record and see difference between in signal and delayed granulation

s.makeGui


(
p.stop;
x.free;
)

///////////////////////////////


Live granulation example for strategy (2), input is gated,
probably rather desirable.
It has to be rethought from case to case if further processings within
the SynthDef also need to be delayed by measured offset or not.
Maybe better to use separate fx synths in this case, e.g. with PbindFx.


(
a = Bus.audio(s, 1);
b = Bus.audio(s, 1);

SynthDef(\grain_no_delay, { |rel = 0.1, inBus|
        var sig, inSig, offset, env;
        inSig = In.ar(inBus, 1);
        offset = OffsetOutDelay.ar(b);
        env = EnvGen.ar(Env([0, 1, 0], [offset, rel]), doneAction: 2);
        sig = inSig * env;
        Out.ar(0, inSig);
        Out.ar(1, sig);
}).add
)

// start input signal
x = { Out.ar(a, Impulse.ar(100, 0, 0.1)) }.play;

// input L and granulation R
// note that overall level of grains might differ between several runs
// as pulse and event stream entry are not synced (envelope starts in the middle of impulses)
(
p = Pbind(
        \instrument, \grain_no_delay,
        \inBus, a,
        \dur, 0.1,
        \addAction, \addToTail
).play
)


// record and see, no time difference between in signal and granulation,
// though grains are delayed (from control block boundaries) as if they would be with OffsetOut

s.makeGui


(
p.stop;
x.free;
)


Regards

Daniel

-----------------------------
www.daniel-mayer.at
-----------------------------





_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Reply | Threaded
Open this post in threaded view
|

Re: Pattern timing issue with OffsetOut

daniel-mayer

Am 16.02.2017 um 01:43 schrieb Daniel Mayer <[hidden email]>:

> Live granulation example for strategy (2), input is gated,
> probably rather desirable.


The gating cannot be written as in \grain_no_delay as 'offset' in the envelope changes.
I have to rethink, but it should work as it does with pulses, will come back to this later.

Meanwhile a sketch for a server-side live granulation variant:
there are also interesting problems arising, connected to the
question how to write envelopes properly.
As Envs itself are problematic with short durations
I came up with a BufRd + Sweep solution for playing through an
envelope buffer. Sweep is better suited than
Phasor here as we only need one going through,
we are regarding only overlaps <= 1.

Another point here is that the envelope itself shouldn't be accelerated
or slowed down while playback of one grain, so we have to latch the trigrate.


(
SynthDef(\live_gran_2, { |out, in, envBuf, trigRate = 50, overlap = 0.999, panMax = 0.5, maxDelax = 0.2,
        delay, panType = 0, amp = 1, minGrainDur = 0.001, interpolation = 4|
        var inSig, env, numFrames = BufFrames.kr(envBuf), startTrig, grainDur,
                latchedTrigRate, latchedStartTrig, latchedOverlap, pan;

        inSig = In.ar(in, 1);
        startTrig = Impulse.ar(trigRate);
        // why this ? - shape of envelope shouldn't be changed while application
        latchedTrigRate = Latch.ar(K2A.ar(trigRate), startTrig);
        latchedStartTrig = Impulse.ar(latchedTrigRate);
        latchedOverlap = Latch.ar(K2A.ar(overlap), startTrig);

        latchedOverlap = max(latchedOverlap, latchedTrigRate * minGrainDur);
        grainDur = (latchedOverlap / latchedTrigRate);

        env = BufRd.ar(
                1,
                envBuf,
                Sweep.ar(
                        latchedStartTrig,
                        latchedOverlap.reciprocal * latchedTrigRate * numFrames,
                ).clip(0, numFrames - 1),
                interpolation: interpolation
        );

        pan = Demand.ar(
                latchedStartTrig,
                0,
                Dswitch1([
                        Dseq([1, -1], inf),
                        Dwhite(-1, 1)
                ], panType)
        ) * panMax * 0.999;

        Out.ar(out, DelayC.ar(Pan2.ar(inSig * env * amp, pan), 0.2, delay) * EnvGate.new);
}).add
)


(
s.meter;

a = Bus.audio(s, 1);

p = Platform.resourceDir +/+ "sounds/a11wlk01.wav";
b = Buffer.read(s, p);


h = Signal.hanningWindow(1000);
e = Buffer.loadCollection(s, h);
)


s.makeGui


// start fx first

y = Synth(\live_gran_2, [envBuf: e, in: a, amp: 0.1])


// start source ( can be sound in )

x = { Out.ar(a, PlayBuf.ar(1, b, loop: 1)) }.play

// use with headphones
// x = { Out.ar(a, SoundIn.ar()) }.play



y.set(\overlap, 0.1)

y.set(\trigRate, 120)

y.set(\trigRate, 500)

y.set(\overlap, 0.8)


(
x.release;
y.release;
)


All in all it seems that, if you want to avoid a buffer, live granulation with short grains is posing subtle questions in language and server.
I'll think about summing up and adding a live granulation tutorial to miSCellaneous lib at some point.


Regards

Daniel

-----------------------------
www.daniel-mayer.at
-----------------------------







_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Reply | Threaded
Open this post in threaded view
|

Re: Pattern timing issue with OffsetOut

daniel-mayer

Am 17.02.2017 um 13:38 schrieb Daniel Mayer <[hidden email]>:

>
> Am 16.02.2017 um 01:43 schrieb Daniel Mayer <[hidden email]>:
>
>> Live granulation example for strategy (2), input is gated,
>> probably rather desirable.
>
>
> The gating cannot be written as in \grain_no_delay as 'offset' in the envelope changes.
> I have to rethink, but it should work as it does with pulses, will come back to this later.


I think I've come close to an accurate solution in the case of language-based live granulation and the In.ar + OffsetOut issue:
As said, OffsetOut can be used to send an impulse to a bus when the delayed output starts. We don't need to measure the delay time, just to read the impulse from the bus and use it as trigger with BufRd + Sweep.ar. Another point then: it seems that OffsetOut fires twice (with and without delay), I added an additional gate with SetResetFF, maybe there's a clearer solution.

Here an example which compares granulation from In.ar: Left channel gets the inaccurate sequencing with Out.ar, right channel is sequenced with accurate delays triggered by OffsetOut. But as it finally uses Out instead of OffsetOut for audio output, the input isn't delayed, it's just the envelope which starts with delay.


////////

(
a = Bus.audio(s, 1);
b = Bus.audio(s, 1);

// pass envelopes with buffer
h = Signal.hanningWindow(1000);
e = Buffer.loadCollection(s, h);

SynthDef(\live_gran_1_check, { |inBus, envBuf, grainDelta,
        overlap = 1, amp = 1, minGrainDur = 0.001, interpolation = 4|

        var inSig, offset, env, numFrames = BufFrames.ir(envBuf), gate,
                defaultOffset = ControlRate.ir.reciprocal;
        inSig = In.ar(inBus, 1);

        // low overlap bound given by minimal grain duration
        overlap = max(overlap, minGrainDur / grainDelta);

        // impulse for offset check
        OffsetOut.ar(b, Impulse.ar(0));

        // additional gate to start with 0 in delayed case
        gate = SetResetFF.ar(In.ar(b));

        // two envelopes ...
        env = BufRd.ar(
                1,
                envBuf,
                Sweep.ar(
                        // ... first triggered at synth start, second with offset
                        [Impulse.ar(0), In.ar(b)],
                        (overlap * grainDelta).reciprocal * numFrames,
                ).clip(0, numFrames - 1),
                interpolation: interpolation
        );
        // to finish synth
        Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);

        // normal Out with accuracy limit by control block size
        Out.ar(0, env[0] * inSig);

        // shifted accurate output
        Out.ar(1, env[1] * gate * inSig);
}).add
)



// start input signal
x = { Out.ar(a, SinOsc.ar(1000, 0, 0.1)) }.play;

s.makeGui;

// record and check, left channel is inaccurate, right is ok
(
p = Pbind(
        \instrument, \live_gran_1_check,
        \inBus, a,
        \envBuf, e,
        \dur, 0.005,
        \grainDelta, Pkey(\dur),
        \overlap, 0.5,
        \addAction, \addToTail
).play
)

(
x.free;
p.stop;
)

(
a.free;
b.free;
)


////////

Example with accurate live granulation only, panning added

(
a = Bus.audio(s, 1);
b = Bus.audio(s, 1);

// pass envelopes with buffer
h = Signal.hanningWindow(1000);
e = Buffer.loadCollection(s, h);

SynthDef(\live_gran_1, { |inBus, envBuf, grainDelta, pan = 0,
        overlap = 1, amp = 1, minGrainDur = 0.001, interpolation = 4|

        var inSig, offset, env, numFrames = BufFrames.ir(envBuf), gate,
                defaultOffset = ControlRate.ir.reciprocal;
        inSig = In.ar(inBus, 1);

        // low overlap bound given by minimal grain duration
        overlap = max(overlap, minGrainDur / grainDelta);

        // impulse for offset check
        OffsetOut.ar(b, Impulse.ar(0));

        // additional gate to start with 0 in delayed case
        gate = SetResetFF.ar(In.ar(b));

        env = BufRd.ar(
                1,
                envBuf,
                Sweep.ar(
                        In.ar(b),
                        (overlap * grainDelta).reciprocal * numFrames,
                ).clip(0, numFrames - 1),
                interpolation: interpolation
        );
        // to finish synth
        Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);

        // shifted accurate output
        Out.ar(0, Pan2.ar(env * gate * inSig, pan));
}).add
)


(
p = Platform.resourceDir +/+ "sounds/a11wlk01.wav";
c = Buffer.read(s, p);
)


// start source ( can be sound in )

x = { Out.ar(a, PlayBuf.ar(1, c, loop: 1)) }.play

// use with headphones
// x = { Out.ar(a, SoundIn.ar()) }.play


(
~dur = 0.005;
~overlap = 0.5;

p = Pbind(
        \instrument, \live_gran_1,
        \inBus, a,
        \envBuf, e,
        \dur, Pfunc { ~dur },
        \grainDelta, Pkey(\dur),
        \overlap, Pfunc { ~overlap },
        \pan, Pwhite(-0.7, 0.7),
        \addAction, \addToTail
).play
)

s.makeGui;


// replace while running

~dur = 0.05;

~overlap = 0.1;

~dur = 0.01;

~overlap = 0.7;

(
x.free;
p.stop;
)

(
a.free;
b.free;
)


In comparison to server-based sequencing with SynthDef \live_gran_1 above, this seems more flexible to me, especially because you have better options for differentiated processing and overlapping grains, I excluded the latter case in \live_gran_2 as it's tricky (if you are interested, miSCellaneous' Buffer Granulation tutorial Ex. 1e does something similar with TGrains, using a tip of Julian).


Greetings

Daniel

----------------------------------------------------
http://daniel-mayer.at/software_en.htm
----------------------------------------------------






_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Reply | Threaded
Open this post in threaded view
|

SubsampleOffset (was Re: [sc-users] Pattern timing issue with OffsetOut)

Wouter Snoei
In reply to this post by julian.rohrhuber
Hi guys,

just to chime in on this sidenote about Subsampleoffset; I’m not sure if it is of any use as OffsetOut itself is actually not sample accurate but only NTP accurate (1/5000s). It shows jitter below that. It can easily be tested by creating two synths with sine waves in counter phase exactly one second from each other. With bundled timing in less then half of the cases they will actually cancel out. So to me Subsampleoffset seems a bit pointless in general, would only make sense if SC would actually become sample accurate (which I would very much like it to be, but that discussion has been going on for a bit longer ;-) ).

Another reason btw. not to use Subsample offset in OffsetOut is because interpolation will always cause some loss in sound quality, so the choice should be up to the user.

cheers,
Wouter

> Op 14 feb. 2017, om 22:00 heeft Julian Rohrhuber <[hidden email]> het volgende geschreven:
>
>
>> On 09.02.2017, at 14:21, Scott Wilson <[hidden email]> wrote:
>>
>> Okay, sorry, talking rubbish. OffsetOut is sample accurate. SubsampleOffset just gives you the additional subsample offset.
>>
>> So what we’d need in this case is a SampleOffset UGen, which would just return mSampleOffset at the time the synth was scheduled. This would be trivial to implement AFAICS and I can see the use case.
>>
>> Though this raises the question of why not make a subsample accurate version of OffsetOut? (I guess the existing one isn’t for reasons of efficiency, since it requires no interpolation)
>
> When I wrote SubsampleOffset, I decided to leave the type of interpolation to the user. You just wrap your output in an appropriate DelayL or DelayC, I thought. But of course if this is interesting, one could combine this into one.
> _______________________________________________
> sc-users mailing list
>
> info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
> archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
> search: http://www.listarc.bham.ac.uk/lists/sc-users/search/


_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Reply | Threaded
Open this post in threaded view
|

Re: SubsampleOffset (was Re: [sc-users] Pattern timing issue with OffsetOut)

daniel-mayer

Am 22.02.2017 um 15:57 schrieb Wouter Snoei <[hidden email]>:

> Hi guys,
>
> just to chime in on this sidenote about Subsampleoffset; I’m not sure if it is of any use as OffsetOut itself is actually not sample accurate but only NTP accurate (1/5000s). It shows jitter below that. It can easily be tested by creating two synths with sine waves in counter phase exactly one second from each other. With bundled timing in less then half of the cases they will actually cancel out. So to me Subsampleoffset seems a bit pointless in general, would only make sense if SC would actually become sample accurate (which I would very much like it to be, but that discussion has been going on for a bit longer ;-) ).

Did you perform the sine wave tests in NRT ?

As far as I can say from my tests with OffsetOut it is sample accurate in NRT but not in RT.
I haven't checked SubsampleOffset in this regard but it might be the same

http://new-supercollider-mailing-lists-forums-use-these.2681727.n2.nabble.com/sample-accuracy-with-lang-timing-was-Various-timing-issues-td7615278.html

The problem is related to hardware control and might never be solved for principal reasons,
as it was discussed in some older dev threads.

Regards

Daniel

-----------------------------
www.daniel-mayer.at
-----------------------------



_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
Reply | Threaded
Open this post in threaded view
|

Re: SubsampleOffset (was Re: [sc-users] Pattern timing issue with OffsetOut)

julian.rohrhuber
In reply to this post by Wouter Snoei
Please correct me if I am wrong, I write this from memory.

OffsetOut won’t remove the asynchronicity between the clock in your sound card and the NTP clock. Sclang schedules using the NTP clock, so that we get a bit of jitter between this scheduled time (which is as precise as you happen to set the NTP clock) and the current sound hardware (which runs as it likes).

A new synth is always started at the next block boundary from the exact NTP time point. I think minus the sound card’s buffer, so that the NTP time in the OSC message should specify as well as possible when exactly the signal hits the wire. But because (for efficiency reasons) the synth is scheduled at a block boundary, it may be off that much. OffsetOut corrects this offset in discrete sample steps (it doesn’t interpolate).

I wrote the SubsampleOffset UGen to give you a way to read the time difference between the scheduled time and the time the signal actually is calculated (that is within the discrete sample steps). You could then, that was what I thought, use a delay of your interpolation choice to do this micro-adjustment if you want. I never found a use for it though.

Now of course this does not remove the jitter between the clocks, but it should shift the sound to a correct position relative to the logical time of sclang.

If you ran sclang’s scheduler on the sound hardware, things would look different, because there would be no jitter.


> On 22.02.2017, at 15:57, Wouter Snoei <[hidden email]> wrote:
>
> Hi guys,
>
> just to chime in on this sidenote about Subsampleoffset; I’m not sure if it is of any use as OffsetOut itself is actually not sample accurate but only NTP accurate (1/5000s). It shows jitter below that. It can easily be tested by creating two synths with sine waves in counter phase exactly one second from each other. With bundled timing in less then half of the cases they will actually cancel out. So to me Subsampleoffset seems a bit pointless in general, would only make sense if SC would actually become sample accurate (which I would very much like it to be, but that discussion has been going on for a bit longer ;-) ).
>
> Another reason btw. not to use Subsample offset in OffsetOut is because interpolation will always cause some loss in sound quality, so the choice should be up to the user.
>
> cheers,
> Wouter
>
>> Op 14 feb. 2017, om 22:00 heeft Julian Rohrhuber <[hidden email]> het volgende geschreven:
>>
>>
>>> On 09.02.2017, at 14:21, Scott Wilson <[hidden email]> wrote:
>>>
>>> Okay, sorry, talking rubbish. OffsetOut is sample accurate. SubsampleOffset just gives you the additional subsample offset.
>>>
>>> So what we’d need in this case is a SampleOffset UGen, which would just return mSampleOffset at the time the synth was scheduled. This would be trivial to implement AFAICS and I can see the use case.
>>>
>>> Though this raises the question of why not make a subsample accurate version of OffsetOut? (I guess the existing one isn’t for reasons of efficiency, since it requires no interpolation)
>>
>> When I wrote SubsampleOffset, I decided to leave the type of interpolation to the user. You just wrap your output in an appropriate DelayL or DelayC, I thought. But of course if this is interesting, one could combine this into one.
>> _______________________________________________
>> sc-users mailing list
>>
>> info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
>> archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
>> search: http://www.listarc.bham.ac.uk/lists/sc-users/search/
>
>
> _______________________________________________
> sc-users mailing list
>
> info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
> archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
> search: http://www.listarc.bham.ac.uk/lists/sc-users/search/


_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
archive: http://www.listarc.bham.ac.uk/marchives/sc-users/
search: http://www.listarc.bham.ac.uk/lists/sc-users/search/