Sine wave oscillator

Posted on
  • I know the space for code is limited on the espruino, but would there be space to add a simple sine wave oscillator in the Espruino?

    The motivation for this is that I use a couple of espruino boards to drive some WS2812 LED installations (mostly goodnight light installation for my kids). These use slow sines waving colors in and out (from different directions and over time), which has proven to be very good to fall into sleep.

    However the pure Math.sin call is pretty expensive and often just avoidable given the fact that the distance from one sample to the next stays the same. Thus a simple (sine) oscillator would work wonders performance wise. Unfortunately, if I implement these in JS code it's both too heavy in terms of memory and processing speed.

    However, since the underlying math is dead simple and the implementation very small, I'd like to ask whether it is possible to implement this in the Espruino firmware.

    What I want is something like this:

    osc = new Oscillator(frequency, phase, amplitude); // phase defaults to 0, amplitude to 1
    

    After the installation, sequent calls to the following member should be enough:

    osc.getSineValueAndAdvance; // or a shorter name like getValue
    

    It should just spit out the current sine value and advance to the next sample

    The implementation is easy and just rely on 4 values stored in an array. The values are actually just two complex values. Let's call them 'position' and 'angle'.

    The 'position' will contain the current sine (real) and cosine (imaginary) value and 'angle' the angle advance on the unit circle.

    Now for every call to the function the current (real) position value should get returned. After that the next 'position' value is obtained by code like this (a simple butterfly algorithm):

    var temp = position.re * angle.im - position.re * angle.im;
    position.im = position.re * angle.re + position.im * angle.im;
    position.re = temp;
    

    The above code works for the normed unit circle (with the radius 1). However, since floating point calculations are a bit expensive on some boards, it also works if the values are fixed point. Often, even 16 bit internal values work sufficiently. This means position and angle are int16, where the unit circle radius of 1 becomes 2^31-1. With this, the integer multiplication must still be scaled (simple bit shift) to fit.

    If implemented in assembler, the code could be very tiny and thus worth the addition.

    What remains missing is the implementation. This is just slightly more complicated like:

    angle.re = Math.sin(phase);
    angle.im = Math.cos(phase);
    pos.re = Math.sin(frequency)*amplitude;
    pos.im = Math.cos(frequency)*amplitude;
    

    In the above code the amplitude is taken into account directly. However, in case of a fixed point implementation it makes sense to stay at the maxInt precision and just multiply the value per sample converting the integer into float again.

    I don't know how much work this will be or how big the footprint will be in the end, but at least for my projects it will be a big improvement. The alternative is to supply the above functions by hand, which is luckily possible with the inline assembler or nativeCalls

    PS: Don't take the pseudo code above for grant, it is written without testing just out of my head. For in-depth information you can look at this site: https://dspguru.com/dsp/howtos/how-to-create-oscillators-in-software/

  • Wed 2018.09.26

    Have you checked out the 'getPattern()' function and 'patterns' array

    http://www.espruino.com/Individually+Addressable+LEDs

    I think this is close to what you are after. Pretty impressive and doesn't seem to slow down using a Pico

    What frequency are you after and how many X-axis points to plot?

  • What you describe is more or less what I am doing today. If you look at the second pattern it calls heavily Math.sin (3 times per pixel). For a low number of pixels it is probably not an issue, but for about 250 pixels (=750 calls per refresh) it makes a difference.

    Also this so called quadrature oscillator can come handy for other uses as well.

  • This isn't something that I'm sure would be worth adding to Espruino core, as Math.sin itself should be reasonably fast compared to general JS execution speed. For example:

    function go() {
      var t = getTime();
      for (var i=0;i<1000;i++) Math.sin(i);
      print("sin ",getTime()-t);
      var t = getTime();
      for (var i=0;i<1000;i++) Math.abs(i);
      print("abs ",getTime()-t);
    }
    
    >go()
    sin  0.45587158203
    abs  0.41012573242
    

    So even if I could make it as fast as abs, it's only going to shave 10% off the time - if that.

    However nothing stops you from doing it yourself (or just finding ways to improve the speed of the loop). As an example:

    var a = new Uint8ClampedArray(250*3);
    
    function getColours(arr,r,g,b) {  
      for (var i=0;i<750;i+=3) {
        arr[i  ] = 128+128*Math.sin(r+i);
        arr[i+1] = 128+128*Math.sin(g+i*2);
        arr[i+2] = 128+128*Math.sin(b+i*3);
      }
    }
    
    function getColoursCompiled(arr,r,g,b) {
      "compiled";
      for (var i=0;i<750;i+=3) {
        arr[i  ] = 128+128*Math.sin(r+i);
        arr[i+1] = 128+128*Math.sin(g+i*2);
        arr[i+2] = 128+128*Math.sin(b+i*3);
      }
    }
    
    // Sum all items in the given array
    var c = E.compiledC(`
    // void getColours(int, int, int)
    void getColours(unsigned char *data, int ai, int ar){
      int pr = 65535;
      int pi = 0;
    
      for (int i=0;i<750;i++) {
       int temp = ((pr * ai) - (pi * ar)) >> 16;
       pi = ((pr * ar) + (pi * ai)) >> 16;
       pr = temp;
        *(data++) = pr>>8;
      }
    }
    `);
    
    function test() {
      var i,t;
      
      t = getTime();
      for (i=0;i<10;i++) getColours(a,0,0,0);
      print("normal ",(getTime()-t)/10);
      t = getTime();
      for (i=0;i<10;i++) getColoursCompiled(a,0,0,0);
      print("compiled ",(getTime()-t)/10);
      
      // Get the address
      var addr = E.getAddressOf(a,true);
      if (!addr) throw new Error("Not a Flat String");
    
      
      var ang = 0.1;
      t = getTime();
      for (i=0;i<10;i++) c.getColours(addr,65536*Math.cos(ang),65536*Math.sin(ang));
      print("inline C ",(getTime()-t)/10);
    }
    
    >test()
    normal  0.55126647949
    compiled  0.19281005859
    inline C  0.00126647949
    

    So a compiled version of exactly the same code is almost 3 times as fast. What's stopping it going much faster is actually all the double arithmetic.

    However inline C using integers and your oscillator example is brutally fast (400x more than normal JS).

    Thing is, if you just used inline C for your oscillator, those gains would get wiped out by the JS execution speed, so you really need to keep the whole thing in C to get the improvements.

    Hope that helps!

  • I wasn't aware of a compileC. Now that I know about it, I still doesn't find it in the documentation. If possible, a simple section about its existince would be nice.

    So far I have just tested your benchmark and it leaves me optimistic that I can speed up the processing without any additions to the Espruino firmware (like the proposed quadrature oscillator). This said, I think it would be a nice addition nonetheless as it doesn't cost too much and can be used for various purposes (at least I have used the code in some of my other projects heavily).

    Anyway, when trying to implement the whole thing, I ran into the problem that I needed to call sin() [cos isn't needed as it can be derrived from sin). However, it seems it isn't available by default and I don't know how to include a math library. I can still split the entire algorithm in a way that I could use Math.sin from JavaScript, but on the other hand I'm also interested in how it would work with compileC.

    Over the weekend I plan to complete the code and benchmark it against the existing code (also in term of memory usage). It could be interesting to see a real world benchmark.

  • Sat 2018.09.29

    re: 'I don't know how to include a math library'

    @CWBudde1, I ran into this same issue a week ago.

    From, at end of page:

    'There is no C preprocessor - so #define/etc won't work'

    http://www.espruino.com/InlineC

    which means no #include

  • Regarding the documentation: I have inserted a reference on the assembler page and created a pull request for my changes. I also added the compiledC to the json specification, but for some reasons GitHub was unable to create a pull request for it so far.

    It would be nice if both changes can make it into the main repo as it is very useful to know about this possibilities.

  • Thanks - yes, I'll add this. I'll do what I did for the assembler, like this: http://www.espruino.com/Reference#l_E_asm

    So you get docs and if the IDE doesn't convert the call for some reason, you still get a sensible warning.

    For functions like sin/etc I'm afraid life gets difficult as you only have access to a very specific set of exported functions from Espruino. It's not just the math functions that are missing, but even the ability to convert doubles to ints and so on. You can:

    • Add a setLUT function or similar, and call that from JS with the right values (probably easier)
    • Use your own version of sin - you could even copy Espruino's low memory version from https://github.com/espruino/Espruino/blob/master/libs/math/jswrap_math.c#L26 (note it's actually got a good explanation of why just pulling in the stdlib sin wouldn't be so great).
    • Use peek32(E.getAddressOf(Math.sin)) in JS to get the address of the JS Math.sin function and then pass that into your C code to call (but double conversion will probably still hit you).

    Nether solution is great, but I don't see there's much of a way around it given how it's all working.

  • At start up - why not create a 0-89 indexed float array and store the value of the sin at each index. Then when you want to use the value - it's just a simple lookup - no computation. You just need to do some manipulation of the index - so for 100deg look up the 80 degree value - and for > 180 flip the sign...

    The compares might be slower so you could store the whole array - or 0-179.

    You could even store fixed point values rather than floating point.

  • needs some slicing and dicing of the input argument ...but you can combine this even with proximation, which makes it possible as well without trigo... who does notice that it is not a exactly a sine? ...it's not steppers trying to plot a circle... :)

    http://lab.polygonal.de/2007/07/18/fast-and-accurate-sinecosine-approximation/ --- I recall this from the times when I had to use tables in books and logarithm calculation ruler... --- this were really cool times that gave you the feel for numbers... accuracy of 3 digits is plenty... (may be for very slow applications it may not be good enough... 30fps is kind of a minimum... so storing values could become a memory capacity issue... on the other hand, you could burn a serially addressable flash eprom to get there; their capacity is beyond what you need).

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Sine wave oscillator

Posted by Avatar for CWBudde1 @CWBudde1

Actions