What's all this JIT stuff
-
I'm seeing this in the commits recently, is this related to C++ API or JS?
-
It's a big new feature that I am working on right now. Basically its a embedded C compiler that generates terribly fast machine code that can be used to write new DSP algorithms with almost native C++ speed. You can then call it from Javascript. This is the last missing piece to make the scripting environment really awesome :)
-
Oh :) I see what you're doing. You're turning it up to 11!
-
Its about 95% faster than a Javascript loop so It's rather 11.5 :)
-
Alright, I started to migrate this into HISE and it is real fun. I made some tests using a additive synthesizer with 6 harmonics and 6 low pass filters that smooth out the amplitudes.
Check out the CPU usage of the three different engines (native C++, NativeJIT and Javascript):
As you can see it's 17% JS vs 2,2% NativeJIT vs 0.9% C++. While it's not 100% there, it definitely allows writing DSP algorithms without compiling!
The Javascript Code
/** NativeJIT Example Script * * Drop this into a ScriptFX Processor and compare the performance between the different engines. * * The example algorithm is a additive sine wave generator with 6 sinewaves and simple LP smoothing * of the amplitudes to reduce clicking. * * You can use the SliderPack to draw the harmonic structure and switch between the different engines * (they should sound the same) */ // Loads the "Example.cpp" file in the script folder and creates a NativeJIT compiler called "Example" loadJITModule("Example.cpp"); // Compile and create an object from the given source code // The globals in the C code become local to this instance and you can access them via "c.global" const var c = Example.createModule(); // Loads a compiled DSP module with the same functionality const var cppMod = Libraries.load("core").createModule("additive_synth"); // The variables for the Javascript version reg b = Buffer.create(6); reg lastValues = Buffer.create(6); reg uptime = 0.0; reg uptimeDelta = 0.03; reg a = 0.999; reg invA = 1.0 - a; // GUI Stuff... const var SliderPack = Content.addSliderPack("SliderPack", 10, 10); // [JSON SliderPack] Content.setPropertiesFromJSON("SliderPack", { "width": 240, "sliderAmount": 6 }); // [/JSON SliderPack] const var Engine = Content.addComboBox("Engine", 271, 9); // [JSON Engine] Content.setPropertiesFromJSON("Engine", { "items": "Off\nJavascript\nNativeJIT\nCompiled C++" }); // [/JSON Engine] Content.setHeight(120); function prepareToPlay(sampleRate, blockSize) { // Needed for DSP Modules... cppMod.prepareToPlay(sampleRate, blockSize); } function processBlock(channels) { local e = Engine.getValue(); if(e == 3) { // Calling the c.processBlock method iterates the buffer // and calls the defined process(float input) function for // each sample c.processBlock(channels[0]); channels[0] >> channels[1]; } else if (e == 2) { // The same code in slow Javascript local a0, a1, a2, a3, a4, a5; local v0, v1, v2, v3, v4, v5; for(s in channels[0]) { a0 = (lastValues[0]*a + b[0]*invA); a1 = (lastValues[1]*a + b[1]*invA); a2 = (lastValues[2]*a + b[2]*invA); a3 = (lastValues[3]*a + b[3]*invA); a4 = (lastValues[4]*a + b[4]*invA); a5 = (lastValues[5]*a + b[5]*i); v0 = a0 * Math.sin(uptime); v1 = a1 * Math.sin(2.0*uptime); v2 = a2 * Math.sin(3.0*uptime); v3 = a3 * Math.sin(4.0*uptime); v4 = a4 * Math.sin(5.0*uptime); v5 = a5 * Math.sin(6.0*uptime); lastValues[0] = a0; lastValues[1] = a1; lastValues[2] = a2; lastValues[3] = a3; lastValues[4] = a4; lastValues[5] = a5; uptime += uptimeDelta; s = v0+v1+v2+v3+v4+v5; } channels[0] >> channels[1]; } else if (e == 4) { // give the native C++ module the buffer to calculate cppMod.processBlock(channels); } } function onControl(number, value) { if(number == SliderPack) { // Set global values of NativeJIT modules directly // as if they were a Javascript Object! c.b[value] = SliderPack.getSliderValueAt(value); // Use the parameter API to set the overtones cppMod.setParameter(value, SliderPack.getSliderValueAt(value)); // set the buffer value for the given overtone for JS b[value] = SliderPack.getSliderValueAt(value); } }
The NativeJIT code
This is how the NativeJIT code looks like. It's plain old C with the addition of the Buffer type.
/** The NativeJIT code for the additive synthesiser. */ double uptime = 0.0; double uptimeDelta = 0.03; // Buffer is a custom type which correlates to the Buffer type in Javascript // Treat them like a float array (there is a buffer overrun protection) Buffer b(6); Buffer lastValues(6); float a = 0.999f; float invA = 0.001f; float process(float input) { const float uptimeFloat = (float)uptime; const float a0 = (lastValues[0]*a + b[0]*invA); const float a1 = (lastValues[1]*a + b[1]*invA); const float a2 = (lastValues[2]*a + b[2]*invA); const float a3 = (lastValues[3]*a + b[3]*invA); const float a4 = (lastValues[4]*a + b[4]*invA); const float a5 = (lastValues[5]*a + b[5]*invA); const float v0 = a0 * sinf(uptimeFloat); const float v1 = a1 * sinf(2.0f*uptimeFloat); const float v2 = a2 * sinf(3.0f*uptimeFloat); const float v3 = a3 * sinf(4.0f*uptimeFloat); const float v4 = a4 * sinf(5.0f*uptimeFloat); const float v5 = a5 * sinf(6.0f*uptimeFloat); lastValues[0] = a0; lastValues[1] = a1; lastValues[2] = a2; lastValues[3] = a3; lastValues[4] = a4; lastValues[5] = a5; uptime += uptimeDelta; return v0+v1+v2+v3+v4+v5; };
Note: This feature is not yet committed so you won't be able to check this out (I remove this hint when it's in the codebase).
-
Looks exciting, I'm going to have to relearn C :p so what sort of things will this be useful for?
-
You can use this to script custom modulators, effects or synths. It's a bit like Reapers JSFX but embedded into HISE (I have to make performance tests between those two).
Especially the effect section in HISE is a bit underpopulated so this might come in handy there.
I will be using it to prototype DSP modules which will then be hardcoded into HISE. There are yet some drawbacks (only x64 support, no "real" branching, no iOS support)
-
Aha now I get it!