What's all this JIT stuff



  • I'm seeing this in the commits recently, is this related to C++ API or JS?


  • administrators

    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!


  • administrators

    Its about 95% faster than a Javascript loop so It's rather 11.5 🙂


  • administrators

    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):

    Performance

    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 😛 so what sort of things will this be useful for?


  • administrators

    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!


Log in to reply
 

3
Online

343
Users

1.1k
Topics

7.1k
Posts

Looks like your connection to Forum was lost, please wait while we try to reconnect.