[{"data":1,"prerenderedAt":36},["ShallowReactive",2],{"note-instruments-on-a-pi-5-en":3},{"slug":4,"order":5,"title":6,"summary":7,"measured":8,"topic":9,"toc":10,"html":35},"instruments-on-a-pi-5",90,"\"Instruments on a Pi 5: Surge XT voices across the cores\"","An instrument that computes every voice on one core sets the polyphony a Pi 5 can play, however idle the other cores are. Surge XT does not split its voices into tasks, so the cores it can use come from running each instrument in a worker of its own, spread over the audio cores.","Raspberry Pi 5 (4 GB) with ZynthianOS Vangelis, 48 kHz, 256 frames, October 2026; Surge XT 1.4.0, plugin-hostd 0.13.1 with omx-clap-host 0.23.2, and jalv.","zynthian",[11,14,17,20,23,26,29,32],{"id":12,"title":13},"why-voices-need-cores","Why voices need cores",{"id":15,"title":16},"what-a-clap-host-can-do-about-it","What a CLAP host can do about it",{"id":18,"title":19},"what-surge-xt-does-with-it","What Surge XT does with it",{"id":21,"title":22},"what-was-proven","What was proven",{"id":24,"title":25},"the-numbers","The numbers",{"id":27,"title":28},"other-instruments","Other instruments",{"id":30,"title":31},"what-was-missing-at-the-time","What was missing at the time",{"id":33,"title":34},"what-was-not-shown","What was not shown","\u003Ch2 id=\"why-voices-need-cores\">Why voices need cores\u003C\u002Fh2>\n\u003Cp>A Zynthian is played. When a pad patch holds 16 notes, each with its own oscillators, filters and envelopes, every voice is\ncomputed in every audio block, and the block has a deadline: 5.3 ms at 48 kHz and 256 frames. A Pi 5 has four cores. If one\ninstrument computes all its voices on one core, that core sets the polyphony the Pi can play before the first xrun,\nhowever idle the other three are.\u003C\u002Fp>\n\u003Ch2 id=\"what-a-clap-host-can-do-about-it\">What a CLAP host can do about it\u003C\u002Fh2>\n\u003Cp>CLAP's thread-pool extension (\u003Ccode>clap.thread-pool\u003C\u002Fcode>) lets a plugin split its block into tasks (one per voice, or per group of\nvoices) and ask the host to run them (\u003Ccode>request_exec\u003C\u002Fcode>). The host decides where they run. omx-clap-core spreads them over\nreal-time helper threads, one per core the worker may use, each pinned to its core and parked between cycles; the tasks are\nhanded out by one atomic counter, the audio thread takes its share, and the call returns when the last task is done, with\nno allocation, lock or system call on that path (see the \u003Ca href=\"\u002Fprojects\u002Fomx-clap-core\u002Fthread-pool\">thread pool reference\u003C\u002Fa>).\u003C\u002Fp>\n\u003Cp>The pool is not a global switch. It is given per plugin, by its qualification: a plugin that is real-time safe and asks for\nthe pool gets helper threads; a plugin with real-time violations is hosted isolated, in a worker of its own, on the serial\npath. A task that allocates or locks on a helper thread would stall the other cores' deadline too, so the measurement that\nadmits a plugin to a shared worker is the same one that admits it to the pool.\u003C\u002Fp>\n\u003Ch2 id=\"what-surge-xt-does-with-it\">What Surge XT does with it\u003C\u002Fh2>\n\u003Cp>Nothing. Surge XT 1.4.0 does not offer \u003Ccode>clap.thread-pool\u003C\u002Fcode> and its source never calls \u003Ccode>request_exec\u003C\u002Fcode>; none of the 489\nplugin descriptors on the device names the thread pool either. So on the Pi, Surge XT's voices run on one core whatever the\nhost does: one host thread from 3 % of a core at one voice to 39 % at 64 voices.\u003C\u002Fp>\n\u003Cp>What does use the cores for instruments is separate workers. plugin-hostd runs each instrument (or each chain) in a worker\nprocess of its own, and JACK runs independent clients in parallel. Four Surge XT instances in four workers, each holding 16\nnotes, ran on the four cores at 14 to 15 % each, 64 voices in all, at a JACK DSP load of 17.6 %; one instance playing 64\nvoices loaded JACK at 39.7 %. The \u003Ccode>placement\u003C\u002Fcode> setting makes the spreading the host's decision: workers across the audio\ncores, the daemon kept off them.\u003C\u002Fp>\n\u003Cp>The cost of placement is one process per instrument; what it buys is the other three cores for three more instruments.\u003C\u002Fp>\n\u003Ch2 id=\"what-was-proven\">What was proven\u003C\u002Fh2>\n\u003Cp>Surge XT as CLAP, played by plugin-hostd, and as LV2 by jalv, the way Zynthian's stock path plays it. An instrument probe\nsends a scripted MIDI scenario into the instrument's note input and measures its outputs window by window: peak and RMS\nlevel, pitch, brightness and JACK xruns. No window of any run had an xrun.\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>proof\u003C\u002Fth>\n\u003Cth>plugin-hostd, Surge XT CLAP\u003C\u002Fth>\n\u003Cth>jalv, Surge XT LV2\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>it plays\u003C\u002Ftd>\n\u003Ctd>C4 at 261.4 Hz, -13.5 dBFS peak\u003C\u002Ftd>\n\u003Ctd>the same\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>velocity\u003C\u002Ftd>\n\u003Ctd>with velocity sensitivity on, velocity 20, 64 and 127 give -53.9, -37.3 and -13.5 dBFS\u003C\u002Ftd>\n\u003Ctd>not set (jalv cannot set the parameter at start)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>polyphony\u003C\u002Ftd>\n\u003Ctd>1, 8, 16, 32, 48 and 64 notes held, all sounding (limit raised to 64)\u003C\u002Ftd>\n\u003Ctd>1 to 32 at the default limit\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>voice stealing\u003C\u002Ftd>\n\u003Ctd>default limit 16: 32 notes cost what 16 do\u003C\u002Ftd>\n\u003Ctd>the same\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>pitch bend\u003C\u002Ftd>\n\u003Ctd>A4 440.0 Hz, full up 494.0 Hz, full down 392.0 Hz, back to 440.0 Hz\u003C\u002Ftd>\n\u003Ctd>440.0, 494.0, 392.0, 441.0 Hz\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>sustain (CC 64)\u003C\u002Ftd>\n\u003Ctd>released: silent; released under the pedal: -13.5 dBFS; pedal up: silent\u003C\u002Ftd>\n\u003Ctd>the same\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>all notes off\u003C\u002Ftd>\n\u003Ctd>8 notes, CC 123: silent 1.5 s later; the \u003Ccode>all_notes_off\u003C\u002Fcode> verb: silent\u003C\u002Ftd>\n\u003Ctd>CC 123: silent\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>mod wheel (CC 1)\u003C\u002Ftd>\n\u003Ctd>a patch routing the wheel to the filter: brightness 0.029 at 0, 0.300 at 127, 0.025 back at 0\u003C\u002Ftd>\n\u003Ctd>0.033, 0.300, 0.025\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>MPE\u003C\u002Ftd>\n\u003Ctd>zone set by the MPE configuration message; a note on member channel 2 at 440 Hz; a full bend on another channel leaves it; a quarter-range bend on its own channel: 880 Hz (the 48-semitone range)\u003C\u002Ftd>\n\u003Ctd>440, 440, 880 Hz\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>native window\u003C\u002Ftd>\n\u003Ctd>Surge XT's own window maps on the Pi's display in 664 ms on request\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch2 id=\"the-numbers\">The numbers\u003C\u002Fh2>\n\u003Cp>CPU of the host thread that runs Surge XT (percent of one core over a 4 s window, ±1 to 2 %), JACK's DSP load, notes held:\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>notes\u003C\u002Fth>\n\u003Cth>plugin-hostd, limit 16 (default)\u003C\u002Fth>\n\u003Cth>plugin-hostd, limit 64\u003C\u002Fth>\n\u003Cth>jalv, limit 16 (default)\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>1\u003C\u002Ftd>\n\u003Ctd>3 %, DSP 3.7\u003C\u002Ftd>\n\u003Ctd>2 %, DSP 3.5\u003C\u002Ftd>\n\u003Ctd>3 %, DSP 3.6\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>8\u003C\u002Ftd>\n\u003Ctd>6 %, DSP 7.0\u003C\u002Ftd>\n\u003Ctd>6 %, DSP 7.0\u003C\u002Ftd>\n\u003Ctd>6 %, DSP 6.7\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>16\u003C\u002Ftd>\n\u003Ctd>10 %, DSP 11.3\u003C\u002Ftd>\n\u003Ctd>10 %, DSP 11.3\u003C\u002Ftd>\n\u003Ctd>9 %, DSP 10.4\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>32\u003C\u002Ftd>\n\u003Ctd>10 %, DSP 11.3\u003C\u002Ftd>\n\u003Ctd>19 %, DSP 20.2\u003C\u002Ftd>\n\u003Ctd>9 %, DSP 10.5\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>48\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003Ctd>29 %, DSP 29.7\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>64\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003Ctd>39 %, DSP 39.7\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>4 instances × 16\u003C\u002Ftd>\n\u003Ctd>14 to 15 % on each of the four cores, DSP 17.6\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003Ctd>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>The same instrument costs the same in both hosts, within a point at every note count. From the request to the instrument's\nports in JACK, plugin-hostd answered \u003Ccode>add\u003C\u002Fcode> in 580 to 585 ms over five runs; jalv's ports appeared 1.85 and 1.98 s after it\nwas started.\u003C\u002Fp>\n\u003Cp>Qualification rated Surge XT isolated for both host profiles, on real-time safety: 410 966 allocations and 115 032 locks on\nthe audio thread over the parameter sweep, 18 884 and 98 304 under notes. Isolated is a way of hosting, not a refusal:\nSurge XT runs, in a worker of its own, and is never grouped with real-time safe plugins. Its cost at 512 frames is a median\nof about 2.1 µs per sample at every rate (about 10 % of a core at 48 kHz), with a 95th percentile of 27 to 37 µs per sample\nin the note-on blocks.\u003C\u002Fp>\n\u003Ch2 id=\"other-instruments\">Other instruments\u003C\u002Fh2>\n\u003Cp>At 48 kHz and 256 frames, read digitally at JACK ports. FluidSynth and Pianoteq run on their own, with their own threads:\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>instrument\u003C\u002Fth>\n\u003Cth>busy threads\u003C\u002Fth>\n\u003Cth>JACK DSP %\u003C\u002Fth>\n\u003Cth>xruns per window\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Surge XT, one instance, 64 voices, through plugin-hostd\u003C\u002Ftd>\n\u003Ctd>1\u003C\u002Ftd>\n\u003Ctd>27.5\u003C\u002Ftd>\n\u003Ctd>0\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Surge XT, four instances, 16 voices each, through plugin-hostd\u003C\u002Ftd>\n\u003Ctd>4, one per worker\u003C\u002Ftd>\n\u003Ctd>18.8\u003C\u002Ftd>\n\u003Ctd>0\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>FluidSynth, 1200 voices, \u003Ccode>cpu-cores=1\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>1\u003C\u002Ftd>\n\u003Ctd>68\u003C\u002Ftd>\n\u003Ctd>0 to 1\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>FluidSynth, 1200 voices, \u003Ccode>cpu-cores=2\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>2\u003C\u002Ftd>\n\u003Ctd>36\u003C\u002Ftd>\n\u003Ctd>0\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>FluidSynth, 1200 voices, \u003Ccode>cpu-cores=4\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>4\u003C\u002Ftd>\n\u003Ctd>19\u003C\u002Ftd>\n\u003Ctd>0\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Pianoteq 9.2.1 trial, 64 notes, \u003Ccode>multicore=4\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>4\u003C\u002Ftd>\n\u003Ctd>44 to 49\u003C\u002Ftd>\n\u003Ctd>0 to 1\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch2 id=\"what-was-missing-at-the-time\">What was missing at the time\u003C\u002Fh2>\n\u003Cp>Measured with plugin-hostd 0.13.1 and omx-clap-host 0.23.2:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Surge XT's own file names hold a space (\u003Ccode>Surge XT.clap\u003C\u002Fcode>, and a percent-encoded LV2 binary), which the host did not\naccept: every proof above used a link without the space.\u003C\u002Fli>\n\u003Cli>Presets: the CLAP path listed no factory presets, and a preset file whose path holds a space was not loaded.\u003C\u002Fli>\n\u003Cli>The engine paged through all 775 parameters; Surge's five remote-control pages were read by the host and not shown.\u003C\u002Fli>\n\u003Cli>No CLAP extension carries Surge's modulation matrix; its own window does.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"what-was-not-shown\">What was not shown\u003C\u002Fh2>\n\u003Cp>The first xrun (no run reached it: 64 voices on one instance took 39 % of a core); the effect of the thread pool, which Surge\nXT does not use; parameter modulation events, tuning and state context; anything at another rate or block size than 48 kHz\nand 256 frames.\u003C\u002Fp>\n",1793954995865]