BelaPlatform / supercollider

an environment and programming language for real time audio synthesis and algorithmic composition
GNU General Public License v3.0
14 stars 8 forks source link

numAudioBusChannels on Bela scsynth lower than remote sclang #67

Open brianfay opened 6 years ago

brianfay commented 6 years ago

Hi, I've been struggling to build a simple guitar looper using SuperCollider on Bela.

After a ton of debugging I realized that using sclang's bus allocator (I'm calling Bus.audio()) must be creating a bus with an index that's too high for Bela. If I tried providing my own bus index, say around 10, things were fine. But if I used Bus.audio(), with a bus index in the hundreds, I got silence.

Setting this parameter in the server startup with s.options.numAudioBusChannels = 1024; fixes the issue.

I'm running SuperCollider using the 12-SuperCollider: 7-remote-control script in the IDE, and then running some of the code here in a remote SC IDE. Everything's worked very smoothly apart from the bus issue. With the bus issue I never had any errors in the Bela IDE post window or the SC IDE post window, so it was really hard to figure out what was going on.

I would put in a PR to change the remote-control script to set s.options.numAudioBusChannels and s.options.numControlBusChannels, but I'm confused about what the number should be; is Bela intentionally using less than 1024 for performance reasons?

giuliomoro commented 6 years ago

Sorry I did not see this before. I am not sure. numInputBusChannels and numOutputBusChannels are used to decide how many audio channels should be used, and whether (and how many) the analog channels should be treated as audio or not. I am not sure if the above is relevant.

In regular supercollider, what is the default number of numAudioBusChannels? If this thing is just allocating memory, that should not be a performance concern, so I would not see a good reason why we changed the default value (assuming we did).

giuliomoro commented 5 years ago

now, in SCClassLibrary/Common/Control/Server.sc, these default values are given:

       // order of variables is important here. Only add new instance variables to the end.
        var <numAudioBusChannels=1024;
        var <>numControlBusChannels=16384;
        var <numInputBusChannels=2;
        var <numOutputBusChannels=2;
        var <>numBuffers=1026;

and these are the same in the Bela fork (soon to be updated and rebased!) and in the upstream SC 3.10 branch. The server comes with its own defaults (scsynth --help), and these are:

   -c <number-of-control-bus-channels> (default 16384)
   -a <number-of-audio-bus-channels>   (default 1024)
   -i <number-of-input-bus-channels>   (default 8)
   -o <number-of-output-bus-channels>  (default 8)
   -z <block-size>                     (default 64)
   -Z <hardware-buffer-size>           (default 0)
   -S <hardware-sample-rate>           (default 0)
   -b <number-of-sample-buffers>       (default 1024)
   -n <max-number-of-nodes>            (default 1024)
   -d <max-number-of-synth-defs>       (default 1024)
   -m <real-time-memory-size>          (default 8192)

so there seems to be different defaults, some built into the server and some used by the language. However, the language does not apply its internal defaults. It only uses them to check whether the user has edited them, otherwise it omits the command line option (and hence uses the server's default). Either way, the defaults for numAudioBusChannels should be 1024 and for numControlBusChannels should be 16384.

Therefore

@brianfay must be creating a bus with an index that's too high for Bela.

Can you tell me a bit more about how you debugged this and how I can reproduce it? Some step-by-step guide (including code) to reproduce the failure would greatly help. I really don't know how to use Sc!

brianfay commented 5 years ago

Hey @giuliomoro, sorry for the huge delay; I was on vacation when you replied and just moved to a new house a few weeks ago. I sort of forgot about this. Coincidentally I actually booted my Bela last night for the first time in months.

I will set aside some time this weekend to reproduce this and see if I can figure out where the issue is coming from.

Super excited to hear that SuperCollider fork is going to be updated!

giuliomoro commented 5 years ago

No worries. In fact I only looked at this again last night as I realized that Sc 3.10 is about to be released and I thought it'd be good to have a painless way of updating and building sc-bela after every upstrean release. Current plan is to squash all our commits so far into a single one. There are only a small number of files being modified, so rebasing these changes on top if any new release should be easy enough.

brianfay commented 5 years ago

Okay, tested again I still get this behavior (although I haven't updated Bela since June, so I should probably do that).

Here are my steps:

  1. On my laptop, I type the Bela's IP address into my browser to open the IDE.

  2. Under Code Examples, I choose the 12-SuperCollider project, 7-remote-control

  3. Without modifying anything, I run the example - it starts sclang on the Bela, which in turn boots an scsynth server.

  4. On my laptop, I open the SuperCollider IDE, and run this code to point my laptop's sclang to Bela's scsynth:

    ( // connect to the already-running remote belaserver
    Server.default = s = Server("belaServer", NetAddr("192.168.0.106", 57110));
    s.options.maxLogins = 4; // should match the settings on the Bela
    s.initTree;
    s.startAliveThread;
    CmdPeriod.add({s.freeAll});
    );

    (that IP was set by my router, which my Bela is plugged into)

  5. I run this code in the SuperCollider IDE:

    
    (
    a = Bus.audio();
    SynthDef.new(\sine, {|outBus=0|
    Out.ar(outBus, SinOsc.ar(1000,mul: 0.3))
    }).add;

SynthDef.new(\reRouteBus, {|inBus=0, outBus=0| Out.ar(outBus, In.ar(inBus)) }).add;

b = Synth.head(s.defaultGroup, \sine, [outBus: a]); c = Synth.after(b, \reRouteBus, [inBus: a, outBus: 0]); )

In theory, the audio from synth `b` should write to bus `a`, then `c` should read from that bus and write to bus 0, which I'll hear in the left channel.

However, I don't hear anything, unless if I start over and add
```s.options.numOutputBusChannels = 1024;```
to the `7-remote-control` script. If I do that, everything is fine.

Interestingly, if I try

"numOutputBusChannels: ".postln; s.options.numOutputBusChannels.postln;


in that script, I get `numOutputBusChannels: 116`
That's totally bizarre - I don't know where it's coming up with that number.
But it makes sense that it would cause the issue; when I allocated a bus with `a = Bus.audio();`, its index was over 200.
giuliomoro commented 5 years ago

Ok what I notice is that:

If you run that block again, it will sound. My limited understanding of Sc makes me think that it is normal that it fails, because there is not enough time to "create" the synthdef on the server before the instantiation message comes in.

However, if I run one bit at a time:

a = Bus.audio();
SynthDef.new(\sine, {|outBus=0|
    Out.ar(outBus, SinOsc.ar(1000,mul: 0.3))
}).add;
SynthDef.new(\reRouteBus, {|inBus=0, outBus=0|
    Out.ar(outBus, In.ar(inBus))
}).add;
b = Synth.head(s.defaultGroup, \sine, [outBus: a]);
c = Synth.after(b, \reRouteBus, [inBus: a, outBus: 0]);

it works fine first time. Also if I put

"numOutputBusChannels: ".post;
s.options.numOutputBusChannels.postln;

in the script that runs on the board, I get numOutputBusChannels: 2

Does the same happen to you?

brianfay commented 5 years ago

Hmm, running the block of code in one go actually works fine for me, in the sense that the synths get created with no error message (but I don't get sound). I can also verify that the synths are loaded by looking at s.plotTree

But you're definitely right; if the server tries to create a synth before its definition has been loaded, it will fail. Not sure why we're getting a difference there.

Whoops, I meant to type numAudioBusChannels. numOutputBusChannels is 2 and that's fine, since it corresponds to the two hardware outputs. I added this to the 7-remote-control SuperCollider script:

"numAudioBusChannels: ".post;
s.options.numAudioBusChannels.postln;

This is printing numAudioBusChannels: 116

If it helps, this is what I'm getting for the versions of scsynth/sclang:

root@bela:~# scsynth -v
SC FFT global init: cosTable initialised.
scsynth 3.9dev (Built from branch 'bela/releases' [a3856472f])
root@bela:~# sclang -v
SC FFT global init: cosTable initialised.
sclang 3.9dev (Built from branch 'bela/releases' [a3856472f])
giuliomoro commented 5 years ago

Hmm, running the block of code in one go actually works fine for me, in the sense that the synths get created with no error message (but I don't get sound). I can also verify that the synths are loaded by looking at s.plotTree

If the Sc IDE is open, running the wohle block of code multiple times (after restarting the server on Bela) will work fine: only the first time I run the block after starting the SC IDE do I get that error.

added this to the 7-remote-control SuperCollider script: "numAudioBusChannels: ".post; s.options.numAudioBusChannels.postln;

I think I tried this as well and I get 1024. I will check again tomorrow.

If it helps, this is what I'm getting for the versions of scsynth/sclang:

I think that is exactly the same I have (and I get the same result with 3.10beta, too). I don't think if that makes any difference, but could you try to connect the Bela board directly to your computer (without the router?). Also what version of the Sc IDE are you running ?

nuss commented 5 years ago

Writing a SynthDef in SuperCollider is an asynchronous process. I.e. you can't rely on it being finished if you instantiate a Synth in the same execution block immediately afterwards. I think that's what happens here. It fails during the first execution because the SynthDef hasn't finished compiling. In the next run the SynthDef exists, hence the Synth can be instantiated. You could wrap the executed block in a Routine which allows you to wait until compilation is finished:

// everything inside the Routine will happen synchronously
// every subsequent code after the Routine will not wait until the Routine has finished
fork {
    SynthDef(\mySynth, {
        // ... SynthDef code
    }).add;

    // wait until compilation has finished
    s.sync;

    // instantiate your Synth
    a = Synth(\mySynth);
}

Regarding the numOutputBusChannels: s.options.numOutputBusChannels should be the number of physical outputs - by default in my SuperCollider installation (on my computer) it's 2. What you get when you instantiate your first audio bus will be the first private bus (if you have 2 physical outputs assigned through s.options.numOutputBusChannels it should be at index 2). I honestly have no idea why your first private bus is at index 116 (the default is set to 2 just like in the original SuperCollder implementation - see here. You must have overridden that default somewhere...

brianfay commented 5 years ago

Thanks @nuss, I agree that adding the new synth must wait until the synthdefs are defined, and that is a problem with my example. However, I verified that the synths were actually running and there was no error in the sclang output.

The issue I was seeing was in numAudioBusChannels, not numOutputBusChannels, sorry for the typo.

I just had a look at the file you linked and I think I see the problem now.

When we call s.boot, it actually builds a string of command line options to pass scsynth. one of which is -a, <number-of-audio-bus-channels>

The ServerOptions.asOptionsString method builds a command line string for running scsynth - the -a argument is built this way:

o = o ++ " -a " ++ (numPrivateAudioBusChannels + numInputBusChannels + numOutputBusChannels) ;

In vanilla SC, numPrivateAudioBusChannels is 1020, numOutputChannels and numInputChannels are both 2.

In the Bela fork, numPrivateAudioBusChannels is set to 112. Add the four for numOutputChannels and numInputChannels, and you get the 116 I was talking about.

brianfay commented 5 years ago

It's interesting - diffing the two versions of that file, I see that the implementation of newBusAllocators has also changed, which may mean that this is fine when running the sclang code directly on the Bela.

The Bela version has some logic for dividing the number of buses for the allocator by maxNumClients, which is also a new concept to the Bela implementation.

This seems like something that would be useful for a laptop orchestra; it looks like multiple clients can connect, and each gets their own isolated Group and set of Buses to work with.

So I'm speculating the numPrivateAudioBusChannels was probably reduced from 1024 to 112 because it would allow multiple users to get their own buses, without increasing overall memory usage on the server.

I think this is cool, but maybe shouldn't be the default.

sensestage commented 5 years ago

The division of private audio buses over maxNumClients is not unique to Bela. It is just one of the cases where it's use becomes clear. It was originally meant for exactly that use: more than one client using one server.

numPrivateAudioBusChannels = 112 is the default amount set in ServerOptions (look at the class implementation) and will be what is set for the Bela server (calling Server.new sets a fresh instance of ServerOptions as options).

The issue is that the server is not explicitly set in Bus.audio().

Looking at the implementation of Bus.audio(), you can see that the server argument is assigned the default server if it is not given a value. So instead of allocating a bus from the Bela server, it allocates a bus from your default server.

*audio { arg server, numChannels=1; var alloc; server = server ? Server.default; alloc = server.audioBusAllocator.alloc(numChannels); if(alloc.isNil) { error("Meta_Bus:audio: failed to get an audio bus allocated."

So I don't think this is a Bela specific bug (none of the involved code was modified for Bela), but a user mistake in the call to Bus.audio.

On 09/09/18 04:01, Brian Fay wrote:

It's interesting - diffing the two versions of that file, I see that the implementation of |newBusAllocators| has also changed, which may mean that this is fine when running the sclang code directly on the Bela.

The Bela version has some logic for dividing the number of buses for the allocator by |maxNumClients|, which is also a new concept to the Bela implementation.

This seems like something that would be useful for a laptop orchestra; it looks like multiple clients can connect, and each gets their own isolated Group and set of Buses to work with.

So I'm speculating the numPrivateAudioBusChannels was probably reduced from |1024| to |112| because it would allow multiple users to get their own buses, without increasing overall memory usage on the server.

I think this is cool, but maybe shouldn't be the default.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/BelaPlatform/supercollider/issues/67#issuecomment-419685185, or mute the thread https://github.com/notifications/unsubscribe-auth/AAMS3rj3ecRExaen-oSGioSXOrn9BzMIks5uZHZqgaJpZM4Ufdph.

-- Sense/Stage Webpage: https://www.sensestage.eu Forum: https://forum.sensestage.eu Facebook page: https://www.facebook.com/EUSenseStage/

sensestage commented 5 years ago

I'll test though...

sensestage commented 5 years ago

Ok, looking at the code again, I see that you set the default Server to the bela server, so that should be ok then.

Trying to retrace your steps:

On my laptop, I type the Bela's IP address into my browser to open the IDE.

Under Code Examples, I choose the 12-SuperCollider project, 7-remote-control Without modifying anything, I run the example - it starts sclang on the Bela, which in turn boots an scsynth server.

First that code is not meant to run on bela, as it states at the top of the file ("run this in SC IDE" - perhaps we need to add something here? 'on your laptop'?). On the Bela you should run:

s = Server.default;

// Set up options for the Bela
s.options.numAnalogInChannels = 2;
s.options.numAnalogOutChannels = 2;
s.options.numDigitalChannels = 0;
s.options.maxLogins = 4;
s.options.speakerMuted = 1;    // mute the onboard audio amps
s.options.numMultiplexChannels = 0; // do not enable multiplexer channels

s.options.blockSize = 16;
s.options.numInputBusChannels = 2;
s.options.numOutputBusChannels = 2;

s.boot;

Then I think it may be good to modify the code in SCIDE a little bit, to ensure there were no issues with overlapping clients:

( // connect to the already-running remote belaserver
~soptions = ServerOptions.new;
~soptions.options.maxLogins = 4; // should match the settings on the Bela
Server.default = s = Server("belaServer", NetAddr("192.168.7.2", 57110), ~soptions, clientID: 1);

s.initTree;
s.startAliveThread;
CmdPeriod.add({s.freeAll});
);

Then I would be interested in what:

a = Bus.audio(); yields...

I find myself a bit at a disadvantage right now, as I am still on an older version of SC on my laptop, and with a show coming up next week, I don't want to update to a new build. With the version I have I am running into a bug when setting s.clientID = 1; which was probably fixed by the changes you saw in ContiguousBlockAllocator and in the Server::newBusAllocators.

scsynth running on Bela will have two clients: the sclang running on Bela, and the sclang running on your laptop.

Starting scsynt from Bela's commandline with: scsynth -u 57110 -a 116 -i 2 -o 2 -b 1026 -z 16 -R 0 -C 0 -l 4 -J 2 -K 2 -G 0 -s 0 -g 0 would solve that issue.

Using that I can run your example without issues.

So perhaps something changes with the new allocators.

giuliomoro commented 5 years ago

Just a quick note:

@sensestage First that code is not meant to run on bela, as it states at the top of the file ("run this in SC IDE" - perhaps we need to add something here? 'on your laptop'?).

When running that program, the .scd file that runs is actually this one: https://github.com/BelaPlatform/Bela/blob/master/examples/12-SuperCollider/7-remote-control/_main.scd

sensestage commented 5 years ago

ah, ok. Also my Bela version was a bit older...

Perhaps I should refrain from posting until I actually have time to set up up-to-date systems again and test with the latest version....

brianfay commented 5 years ago

Thanks @sensestage, I tried running with:

( // connect to the already-running remote belaserver
~soptions = ServerOptions.new;
~soptions.maxLogins = 4; // should match the settings on the Bela
Server.default = s = Server("belaServer", NetAddr("192.168.0.106", 57110), ~soptions, clientID: 1);

s.initTree;
s.startAliveThread;
CmdPeriod.add({s.freeAll});
);

as you suggested. I get the same behavior as earlier. When I call Bus.audio(), I get Bus(audio, 259, 1, belaServer)

I'm using SuperCollider 3.9 on my laptop, which does have different code in Server::newBusAllocators, so I guess it's not too surprising.

I am still really confused about numPrivateAudioBusChannels.

In the upstream it's initially set to 1020, as a result of this commit but there's still a method called ServerOptions::numPrivateAudioBusChannels_ that will reset it to 112. So that's an odd inconsistency, maybe not intentional.

The Bela fork has the older code that still initializes it to 112.

Maybe the preferred logic is to have 1020 buses, and split them evenly between the clients? Or maybe it should be to have 112 buses per each client? I'm really not sure. But at least I have the workaround of manually setting the numPrivateAudioBusChannels to something higher.

sensestage commented 5 years ago

That commit fixes the inconsistency between 1024 for numAudioBusChannels and 122 numPrivateAudioBusChannels. I just made a comment that then the default value in the setter should have the same fix.

There is some recalculation happening when you set numAudioBusChannels:

numAudioBusChannels_ { |numChannels=1024|
        numAudioBusChannels = numChannels;
        numPrivateAudioBusChannels = numAudioBusChannels - numInputBusChannels - numOutputBusChannels;
}

and viceversa:

numPrivateAudioBusChannels_ { |numChannels = 112|
        numPrivateAudioBusChannels = numChannels;
        this.recalcChannels;
}

recalcChannels {
        numAudioBusChannels = numPrivateAudioBusChannels + numInputBusChannels + numOutputBusChannels;
}

I have a hunch though that the issue might be in line 74 of Server.sc:

o = o ++ " -a " ++ (numPrivateAudioBusChannels + numInputBusChannels + numOutputBusChannels) ;

and that that should be:

o = o ++ " -a " ++ (numAudioBusChannels);

Could you try that? It should be fixed on the Server.sc on the Bela, I think.

And if this is the fix, then it should be fixed upstream as (numPrivateAudioBusChannels + numInputBusChannels + numOutputBusChannels) is only correct whenmaxLogins = 1 for the server you are starting.

sensestage commented 5 years ago

You could check on the Bela after you fix by adding the line:

s.asOptionsString.postln;

to your server startup script.

brianfay commented 5 years ago

Hey @sensestage, thanks for the suggestion, I think you're definitely right.

If I update /usr/local/share/SuperCollider/SCClassLibrary/Common/Control/Server.sc to set the -a flag to numAudioBusChannels, like this:

o = o ++ " -a " ++ numAudioBusChannels;

I still get -a 116 in the s.options.asOptionsString.postln; output.

However, if I also no-op the recalcChannels method, like this:

recalcChannels {
                //numAudioBusChannels = numPrivateAudioBusChannels + numInputBusChannels + numOutputBusChannels;
}

then I get -a 1024, and my test code works.

So I'm still confused about the intent of numPrivateAudioBusChannels vs numAudioBusChannels. I guess when I've heard references to "private audio buses", it has generally meant any audio bus that isn't reserved for the hardware. If that's how it's defined, then I would expect there to be 1020 private audio buses by default. Add the default 2 input channels 2 output channels, and you get the 1024 total audio bus channels

But it seems like we're also trying to use the term to mean the number of audio buses reserved to each client. Maybe that warrants adding a separate variable, like numClientAudioBusChannels? Then we could keep the logic for the total number of private (non-hardware) audio bus channels separate from the logic for the number of channels each client owns.

But I may be missing something, and I guess even if that does need to be changed, it should happen in the upstream branch.

sensestage commented 5 years ago

I definately think something is wrong in the upstream about this, confusing the buses per client and the desired/needed server arguments.

giuliomoro commented 5 years ago

Given how you seem to have some insights on this, could you create an issue upstream?

giuliomoro commented 3 years ago

is this still current and/or related to https://github.com/BelaPlatform/supercollider/issues/74 ?