SuperCollider on Linux

I am starting to use SuperCollider with Ubuntu and I noticed Jack xruns already happening with an scsynth load of less than 50% in the top command while nothing else is running on a quite performant machine (Intel Core i9-11900).

I use an USB audio interface and have Jack configured with 256 frames per period and 2 periods per buffer. The sampling rate is 44100 Hz. The CPU govenor is set to performance and Intel turbo boost is on.

Shouldn’t I be able to get more out of such a machine without xruns?

What could be the cause of the xruns?

Where can I find the latest recommendations to configure my OS and Jack for low-latency high-performance audio work?

Run this script, it checks some system settings which you can apply. Installing a realtime kernel (rt) or lowlatency helps too

See also:
https://manual.ardour.org/setting-up-your-system/the-right-computer-system-for-digital-audio/

This might help to get JACK working nicely together with Desktop sound:
https://ovenwerks.github.io/studio-controls/

Hey, I just drop in my personal Jackd starting configuration. My personal only goal was to be able to play my electric guitar with no audible latency:

jackd -P 95 -d alsa -d hw:1 -p 512

Also How do I configure my linux system to allow JACK to use realtime scheduling? | JACK Audio Connection Kit might help
Dunno if it helps, also am open for hints.

Thank you very much for all your helpful tips. I configured my machine now according to the recommendations from realtimeconfigquickscan but the situation has not changed.

This is why I wrote a simple Jack client producing two sine waves (based on simple_client.c which comes with Jack). I added an empty while loop to burn CPU cycles in each frame. With a command line argument I can set the loop counter to adjust the load. This way I can run the client without xruns at 80% CPU with a 64 frames/period and 2 periods/buffer.

In comparison, an scsynth loading the machine with only 10% CPU produces xruns with the same Jack configuration. This is what I play in SC:

200.do({ { SinOsc.ar([200, 202], 0, 0.001) }.play() })

Why does scsynth behave so differently as a Jack client compared to my C program?

FWIW, I could never figure out all the priority configurations in vanilla Ubuntu. I never had a fully functioning SC system until I switched to Ubuntu Studio.

hjh

Would you mind telling us which performance you can achieve with your setup? What is the lowest latency you can sustain without xruns under which scsynth load? I am still puzzled by the difference I get between scsynth and my test Jack client.

Gerhard

I compiled SC 3.11.2 on my machine with -DCMAKE_BUILD_TYPE=Release -DNATIVE=ON which allows me to run this line without xruns for a minute at 64 frames/period and 2 periods/buffer:

250.do({ { SinOsc.ar([200, 202], 0, 0.001) }.play() })

This is a bit better than with the packaged SC 3.11.1 I used before, but still far off what I would like to see.

Could it be that scsynth produces a very spiky load for some reason which makes it be late every now and then, causing these xruns?

Could you post the output of the realTimeConfigQuickScan script mentioned above ?

In my experience the biggest improvement happens when setting CPU governor to performance.

== GUI-enabled checks ==
Checking if you are root… no - good
Checking filesystem ‘noatime’ parameter… 5.4.0 kernel - good
(relatime is default since 2.6.30)
Checking CPU Governors… CPU 0: ‘performance’ CPU 1: ‘performance’ CPU 10: ‘performance’ CPU 11: ‘performance’ CPU 2: ‘performance’ CPU 3: ‘performance’ CPU 4: ‘performance’ CPU 5: ‘performance’ CPU 6: ‘performance’ CPU 7: ‘performance’ CPU 8: ‘performance’ CPU 9: ‘performance’ - good
Checking swappiness… 10 - good
Checking for resource-intensive background processes… none found - good
Checking checking sysctl inotify max_user_watches… >= 524288 - good
Checking whether you’re in the ‘audio’ group… yes - good
Checking for multiple ‘audio’ groups… no - good
Checking the ability to prioritize processes with chrt… yes - good
Checking kernel support for high resolution timers… found - good
Kernel with Real-Time Preemption… ‘threadirqs’ kernel parameter - good
Checking if kernel system timer is high-resolution… found - good
Checking kernel support for tickless timer… found - good
== Other checks ==
Checking filesystem types… ok.
** Set $SOUND_CARD_IRQ to the IRQ of your soundcard to enable more checks.
Find your sound card’s IRQ by looking at ‘/proc/interrupts’ and lspci.

Disable hyperthreading in BIOS?
https://manual.ardour.org/setting-up-your-system/the-right-computer-system-for-digital-audio/

Set periods/buffer in JACK to 3?

Install a low-latency kernel and boot it?
https://packages.ubuntu.com/search?keywords=linux-lowlatency

edit:
Try a other USB port?

I have tried all these, thank you, but no change.

I am now wondering what kind of communication is going on between the server and the language behind the scenes and if it could be responsible for the xruns.

The ServerStatusWatcher runs this aliveThread asking the server for its status, which then responds with /status.reply. I was assuming that this also updates the green numbers in the IDE, but there must be something else going on because if I stop the aliveThread, the incoming /status.reply messages stop, but the numbers in the IDE are still updating. How are they communicated from the server to the IDE?

s.boot(startAliveThread: false);

Doesn’t update the numbers. I don’t know what is happening with s.stopAliveThread; and the IDE, it stops sending status requests from the language. But I don’t think that’s a problem.

Repeated { ... }.play; may cause a more pronounced spike in the language and the server because it creates, sends and instantiates a new synthdef/synth. With a sound card buffer of 256 or less, all other audio applications run fine? [Edit: under some kind of stress, processing, allocation, etc.]

With SC 3.11.2 the following does update the server info on the bottom right of the IDE for me (green numbers):

(

Server.default.boot(startAliveThread: false);
Server.default.doWhenBooted({
    { SinOsc.ar() * 0.1 }.play(Server.default);
});

)

The IDE itself sends status messages to whatever is the default server.

But status messages are 1/ extremely lightweight for processing and 2/ not handled in the audio thread so they should have no effect on audio thread timing.

I haven’t benchmarked so I don’t have any numbers. I can say that audio performance with the built-in soundcard is quite a bit worse since upgrading to Ubuntu Studio 20.04, but with a USB soundcard, I can run SuperCollider + the much CPU-hungrier VCV Rack without glitches.

I’m afraid I don’t know why you’re getting so many xruns… In Ubuntu Studio, I simply never did.

Also note though that top’s CPU reading is likely to be irrelevant for audio performance. Audio processing occurs in short bursts and system-level CPU meters do not measure this well (and, in a multicore system, you may be seeing the average over all cores while scsynth has only one audio thread). “Jack xruns already happening with an scsynth load of less than 50% in the top command” is actually not surprising.

hjh

for me it doesn’t produce any sound… I don’t have the latest checkout, something may have changed.

I would advise against running the server with startAliveThread: false.

The alive-thread is how the language knows that the server is up and running.

If you don’t start the alive thread, then the language has no way to know that the server booted. So then, of course, doWhenBooted actions won’t fire and you get no sound.

But this entire line of investigation was based on the assumption that /status messages are processed in the audio loop. That’s not true.

/status is handled here:

“Sequenced commands” are for asynchronous server messages, also including buffer allocation and sound file reading – commands that are expected to take an unknown amount of time and that should never run in the audio loop.

If any “sequenced commands” are interrupting audio processing, that would mean that the audio thread’s priority is too low. This shouldn’t happen if your user is a member of the audio group – “Checking whether you’re in the ‘audio’ group… yes - good” – so that shouldn’t be it.

Because the premise for turning off the alive thread was not correct, then it isn’t useful to continue with that investigation.

~~

CPU percentages… as an informal test, I just firebombed scsynth with about 30 simultaneous waveguide synths (these are expensive).

  • scsynth’s CPU reading hovered generally around 60%, occasionally spiking to 70 or 80% (with xruns at that point).
  • ‘top’ showed about 68%.
  • XFCE task manager showed 18%.

I did get xruns with this. (Audio processing is about being on time, not about raw throughput. scsynth is not a database. You will hit practical limits far below 90-100% CPU.)

EDIT:

250.do({ { SinOsc.ar([200, 202], 0, 0.001) }.play() })

At 64 samples x 2 periods, I see:

  • scsynth: about 25%.
  • ‘top’: 4-6% (!). (Takeaway: ‘top’ may not be trustworthy at small buffer sizes.)
  • Tons of xruns.

But I never tried to tune my system for ultra-low latency… so I’m afraid I don’t have other ideas. Just confirming that it’s normal to see xruns well below high CPU percentages, especially with small buffer sizes.

hjh

For the sake of accuracy/completeness:

If you don’t start the alive thread, then the language has no way to know that the server booted. So then, of course, doWhenBooted actions won’t fire and you get no sound.

I was just looking at the part before replying to another thread here and it does not seem to be the case - doWhenBooted, IIUC, does not require the “alive thread” to run. I’m guessing that the reason doWhenBooted might not fire in certain situations (and why turning on “alive thread” helps) is related to timing…

Please correct me if I’m wrong!

I also don’t see a practical need to disable alive thread - I’ve never seen it disrupting anything. (Updating the gui node tree, OTOH, can indeed mess with timing, especially when triggering many synths in short succession).

Marcin

As far as I know: doWhenBooted requires the variable serverRunning to be set to true.

In the normal boot process, it’s the alive thread that sets serverRunning.

So, unless you set serverRunning manually, no alive thread = serverRunning never gets updated = no boot actions.

I could be missing something. But with the given example, the action consistently never fires.

hjh

Yes, doWhenBooted routine waits while server.serverRunning.not, so there might be a routine instance on the loose. The server status was never (in history) an issue, it was a question in this thread. There is another mechanism which informs back the status of each node when registered, that might need some bandwidth if loaded, but is not the case either. Also, without startAliveThread Function.play, SynthDef.add/send, and other things will not work, I guess anything that checks if the server is running.

And I forgot to mention that s.boot(false); seems to consistently hang the IDE when quitting (?).