As exciting as Extempore may sound on paper, or in demos, itâs not a terribly well maintained project. Hardly any updates in a year. Combined with the complex tooling and LLVM frozen to a 2016 version, itâs not really useable by outsiders. If their private website were to go down, it would also become unbuildable, as the stuff on github is not self-contained; the (custom patched?) LLVM source automatically gets downloaded during build from the extempore private website. Also, compilation is like 5-6 longer than for SC. And Iâm not sure if itâs just the size of their libraries or ancient LLVM but they also load much more slowly than what Kronos manages to pull off, roughly based on the same backend.
Kinda sad because simple audio-rate function compilation works decently fast in Extempore, perhaps even faster than Faust, e.g. example from their (online-only) doc that does still work
(bind-func dsp
(lambda (in:SAMPLE time:i64 chan:i64 data:SAMPLE*)
(* .1 (sin (/ (* 2.0 3.1415 ;; 2pi(ish)
440.0 ;; frequency (Hz)
(i64tof (% time 44100))) ;; time mod samplerate
44100.0)))))
(dsp:set! dsp)
;; functions are hot-swappable, to cut it off, do e.g.
(bind-func dsp:DSP
(lambda (in time chan dat) 0.0)) ;; cuts snd
If anyone is curious, it doesnât work to rebind the dsp
itself; if you do (dsp:set! dsp)
again you get this message:
You can only set the DSP callback once, but you
can re-define that function as often as you like
The explicit typing is a bit annoying, but by and large not strictly necessary, as you can see from the 2nd example. One thing thatâs not so cool is that there are single and multi-channel versions of the same things, e.g. oscillators, an seemingly these need to be chosen explicitly e.g. osc_c
vs. osc_mc_c
. (The final _c
is for closure.) This is probably due to types not being polymorphic like in some other languages in this space. You also need to cast numbers explicitly e.g. from int to float, as this this basic frequency sweeper, lifted as-is from the documentation:
(bind-func dsp:DSP
(let ((oscil (osc_mc_c 0.0))
(duration (* SRf 1.0)) ;; samplerate * 1.0 seconds
(range (/ 440.0 duration))) ;; rise up to 440.0 hz
(lambda (in time chan dat)
;; explicit conversion required to coerce time (i64) into a float
(oscil chan 0.3 (* range (% (i64tof time) duration))))))
And I could be wrong on this, but basing a live coding thing on a language (Scheme) that apparently doesnât support optional parameters except in a left-to-right fashion as closures such as
(bind-func osc_c
(lambda (phase)
(lambda (amp freq)
(let ((inc:SAMPLE (* STWOPI (/ freq SR))))
(set! phase (+ phase inc))
(if (> phase SPI) (set! phase (- phase STWOPI)))
(* amp (_sin phase))))))
seem like a pretty bad idea. I mean even XLisp used by Nyquist has optional & named parameters.
Actually, there is some work behind the scenes to make the guts of their JIT compiler less dependent on a given version of LLVM, so upgradable. But this is being done by an outside contributor, so itâs in the eternal PR review process that unfortunately the git flow model âfacilitatesâ.
Iâd have to check what JIT Kronos uses, but one of the obscure thing about LLVM is that it has multiple incompatilble JITs, or at least JIT APIs. Extempore uses the older MCJIT one, not the latest, modular ORC. And MCJIT was actually the 2nd gen LLVM JIT. There was an even older one in LLVM <3.5, which didnât even get a real name.
One other thing I could not yet figure out how to do in Extempore is how to get an equivalent of Ndef \fadeTime, i.e. at least custom fade times if not also cross-fade custom envelopes (that Ndef also supports) when replacing a source with bind-func
.