Faster Array looping

Posted on
Page
of 2
/ 2
Next
  • Hello,
    I've got an 8x16 LED matrix and I'm using the Graphics module to draw a simple game onto it. I'm using an Array to hold some background pixels with I then loop and use Graphics.setPixel() to output. Everything works fine but looping my array for active pixels seems very slow. So my question is, is there a fast/better way to be doing the following?

      // Create empty array
      var board = Array();
      for (var r=0; r < 16; r++) {
          board[r] = Array();
          for (var c=0; c < 8; c++) {
              board[r][c] = 0;
          }
      }
    
      // (un)set pixels in array code here
    
      // Loop and output pixels
      for(var r=0; r < 16; r++) {
          for(var c=0; c < 8; c++) {
              if(board[r][c] == 1) {
                  martix.setPixel(c,r);
              }
          }
      }
    
  • I assume you've implemented some method that you call to send the graphics to the screen?

    In that case, why the triple buffer?

    Can you post all your code?

  • Yep... is all slow... so think about a different arrangement, like getting things in parallel out... set/get pixel is a slow thing, see LCD Graphics. Some users resorted to inline assembler...

    As @DrAzzy mentioned, you would need to provide a little bit more details... about HW involved and SW you wrote...

    Choosing typed arrays and array buffers makes it already faster because you deal just w/ bytes and not with integers. Plain arrays are resource monsters.

    Furthermore, try to use Array.forEach... and pass a function name, then variable resolution happens for certain things to much lesser degree... Since Espruino runs of the source code - not like browsers who do some JIT and run from internalized/'compiled'/tokenized code - fast
    algorithms reduce the number of variables used/referenced...

    So you do not just have to be frugal w/ code cycles, but also with memory:

    Since you crate your array, did you think of just having one bit for a byte in a UInt8Array? JavaScript allows you to shift, and that saves you tons of memory (which you will use later for many other things...). First though you can start with a UInt8Array where you put 0x00 and 0x01. Then you loop through it. For your setPixel() I assume it's monochrome what you do...
    Instead of an if - which does a lot of referencing, you just setPixel with color 0 and 1... You have the zeros at hand anyway...

    What is the driver/module you use to connect to your matrix display?

    If your graphics can be drawn with lines, it may be done faster too. I tried some stuff where the SPI is the lock down... But it can made to work.

    @Gordon mentioned also the way to push out a bit image/sprite... and 16 by 8 is small... I though assume you have larger sprites you plan to push out.

  • I'm coding Tetris. The code below is unfinished and still very rough around the edges with lots of improvements to be made. Here is what I've done so far.

    1. Added all block types and their rotations
    2. Block fall and can be moved/rotated (Use blockOp.left() blockOp.right() blockOp.rotate() blockOp.down() )
    3. Wall and floor collision detection
    4. All blocks are the same colour but want to add full RGB later

    The next part I'm working on is storing the fallen blocks in an Array so I can render them on each frame and then detect when other falling blocks need to settle on top of them.
    Here is a link to see the full code. The output is a 16x8 RGB-123 LED Matrix (the code should run fine without connecting anything) Line 191 logs the stored fallen blocks array when I then want to loop for rendering and collision detection but it's is very slow.
    http://jsfiddle.net/t0ucb1yt/1/

  • Nice!

    So each pix is a square of a tetris shape, correct?

    So think of an undraw and redraw... then, at worst, it is 8 pix you set... vs 144... so you get ~93% faster or have to draw only ~6.6%... and if you have transition functions for each shape - average 3 for rotation and 3 for falling down you get it 98% faster, that's down to about 4.2%.

    Since drawing can be seen as line px set, a line drawing, a line drawing and a px set, and two line drawings at max, you may get down to 4 interactions between source execution state and internal execution state...

    When designing the pac man, I simplified the man with two lines in an wider and a narrower angle... which means: 2 line un-draws - drawing the current state/lines in background (black) color - and 2 line draws - drawing the new state in foreground (yellow) color.

    In other words, you still need the board for collision detect - and may be for an occasional redraw after a game break and display of something else...

    I did not look much into the collision detection algorithm, but any location > 0 (1... are colors) means a free spot... and so you would also treat the border this way, then you do not need extra logic for border detection... in other words, your border is just an other 'occupying color', one you consider stealth / not to be seen/drawn... this increases of course your memory footprint for the board from 144 to 180 - 25%, but you reduce amount of execution logic and complexity considerably...

    I have not published yet the drawing of the pac man board... but I had to resort to all kinds of 'short cuts' to fit in memory... But I implemented and published the 4x4 - 16 (or more) pieces puzzle - which are all time and memory critical.

    have fun.

  • Just to add, in Espruino, 'normal' arrays are quite slow to index, but Uint8Array/etc are pretty fast. It's not such an issue for small arrays though. Have you seen the performance page?

    One thing that can really help is to use forEach, so you can change:

          for(var i=0; i < pixels.length; i++) {
            // Draw pixel
            m.setPixel(pixels[i][0], pixels[i][1]);
          }
    

    to

          pixels.forEach(function(p) {m.setPixel(p[0], p[1]); }); // Draw pixels
    

    But as @allObjects suggests, I think drawing only the bits that have changed each frame would be a great help. You could actually have two instances of Graphics. One that contains the current set of squares, and another that is what you're drawing (containing the current squares and the shape that's dropping).

    So then instead of m.clear() you can do m.buffer.set(currentSquares.buffer) to copy the current squares into your 'display' buffer.

    Just an idea that might tidy up your code, but what about changing drawPixels to take the offset as an argument, so that instead of:

      switch(block.type) {
        case 'L':
          switch(block.rot) {
            case 1:
              this.drawPixels([[block.x, block.y],
                [block.x-1, block.y],
                [block.x-1, block.y+1],
                [block.x+1, block.y]]);
              break;
    ...
    

    You do:

      switch(block.type) {
        case 'L':
          switch(block.rot) {
            case 1:
              this.drawPixels(block.x, block.y, [[0,0],[-1, 0], [-1,1], [1,0]]);
              break;
    

    In fact you can tidy it up even more by putting all the blocks into a global array:

    var myBlocks = {
     "L": [0,
                 [[0,0],[-1, 0], [-1,1], [1,0]], // 1
                 [...], // 2
                 [...], // 3
                 [...]], // 4
       "J" : // ...
    // ...
    this.drawPixels(block.x, block.y,myBlocks[block.type][block.rot])­;
    

    That should be a load faster, and it'll use a lot less memory too.

  • #game #tetris

    Had the chance to look at the code.... I guess I understand a bit what you do...

    Did you consider to make each block a class? If you do so, you do not need switching...

    Think about each block being a thing that understands the following 'calls' or actions:

    1. f1() - fall one step (+1 along y-axis)
    2. fA() - fall all the way through
    3. m(d) - move in +/-1 d(irection) along x-axis
    4. r(d) - rotate in -/+1 d(irection) = counter/clock-wise 90 degrees
    5. dr(c) - draw with color c

    Until now, we defined all the so called behavior - the actions a block can do when asked by itself or by 'others'. There will be more of those, but for now let it be with the the above 5.

    Next we need to define what each block needs to know about itself (and others can ask the block about) - the so called state of the block:

    1. bc - block color
    2. sc - score points for block
    3. x - x coord on the board ((extended) board top left corner is x=0,y=0, top right is 10,0)
    4. y - y coord on the board
    5. ps - points (squares) - array of [x,y] arrays of points relative to x and y the block occupies
    6. rx - rotation index in the set of possible rotations
    7. rs - rotations, an array of ps representing all rotations

    So let me give you an example for the simples block - the 'square-1':

    // used to temporarily hold on to prototype
    var p; 
    
    // square-1 block class definition 
    //
    // The class definition begins with defining a function, the so 
    // called constructor that will be invoked with new and sets 
    // everything that an instance will have unique (state). Since 
    // certain values - like the color,  score, etc. is all the same 
    // for a particular block, they are not included in the 
    // constructor, rather defined only on the prototype like all
    // the actions of the block. The constructor for blocks where 
    // the rotational position matters, an initial random rotation 
    // index rx is passed to pick and set the initial points and 
    // as well the current rotation index for the block. Since those 
    // do not matter for the SQ1, they are not mentioned here... For 
    // the generic block creation algorithm in the board though, the 
    // possible rotations - exactly one - have (has?) to be defined.
    // For example, see the code in the attached html, which 
    // includes the B2 block - bar with 2 points - where rotation
    // matters.
    var SQ1 = function() {
      this.x  = 4; 
      this.y  = 1;
      this.ps = [[0,0]];
    }
    
    // shortening the prototype assignments 
    p = sq1.prototype;
    
    // shared block color, score, and all the possible rotations 
    // are defined once now including all the behavior (methods).
    // color is either a single number or an rgb-compound.
    p.bc = 1;
    p.sc = 1;
    p.rs = [[[0,0]]];
    
    // draw method of  sq1:
    // general block drawing is going through the points like 
    // this:
    //   this.ps.forEach(function(p){ 
    //     g.setPixel(this.x+p[0],this.y+p[1],this.­c);
    //   });
    // but for SQ1 this can be simplified as done below.
    // g is 'global' var for graphics you connect/talk to
    p.dr = function(c) { g.setPixel(this.x,this.y,c); };
    
    // fall 1 down (triggered by the timer thought the board... 
    // see board code. When it cannot fall down anymore, the 
    // block becomes frozen and a next block is created... all 
    // done through the board... see board code. To make that 
    // happen, the block checks new position and if free, 
    // returns true - and of course, 'moves' to the new position 
    // by un-draw at current position with background color and 
    // re-draw in new color. If new position is not free, nothing 
    // needs to be done, because by default a function returns 
    // undefined and that is interpreted by the board as false 
    // (could not 'fall down'), and the block becomes frozen.
    p.f1 = function() {
      if (brd.f(this.x,this.y,0,1,this.ps)) {
        this.dr(0);
        this.y++;
        this.dr(this.bc);
        return true;
      }
    };
    
    // for SQ1 rotate does not matter, therefore, it is an empty 
    // function (it is way 'cheaper' to have this empty function
    // to have a generic invocation in the board on rotation
    // request that figuring out for which blocks it matters
    // and have such a function and then invoke it...
    p.r = function(d){};
    
    // move left and right do something... first check with board
    // if new place free. If fee, the block moves there by first 
    // 'un-drawing' with background color bg on current position 
    // and then re-drawing with block color bc on new position.
    // Background color is some 0-ish value.
    p.m = function(d) {
      if (brd.f(this.x,this.y,d,0,this.ps))  {
        this.dr(0);
        this.x += d;
        this.dr(this.bc);
     }
    };
    
    // as last thing we register the block class with the board.
    // This will all the board to pick randomly the next block
    // block from the list of possible blocks and also set the
    // initial rotation position randomly... (which of course 
    // for the SQ1 does not matter). 
    // see later in code. 
    
    brd.rBC(SQ1);
    

    As I remember from playing, you have only one piece active - moving - at a time; and the pieces that have settled do not matter anymore, because when they settle they occupy a position of the board (beside the adding scoring points) - correct?

    With these assumptions, I'm now moving on with defining the board in similar style. Since the board exists only once, there is no need to create class and then an instance of it, like we do for blocks. Therefore, we create a so called singleton instance right away - with state and behavior:

    // brd - Board (or Game) singleton:
    // c: colums
    // r: rows - for testing, start w/ 4 only for square-1 block... ;-)
    // tm: time of interval in milliseconds
    // it: intervall timer that triggers next step in game
    // ls: locations - c x r = x * y = (10x18) array of trues and falses:
    //      occupied = true, not occupied (free) = false
    // sc: scoring (just counting squares, not yet taking time into account)
    // bCs: registered block classes SQ1, BR2,...
    // bs: blocks so far in the game
    // b:  current active block
    // f(xO,yO,ft) - check 'free' for current block b and xO(ffset) and 
    //      yO(ffset) and return true, otherwise false, or, if 3r parm
    //      ft = fatal is true, game over...
    // start() - starts the game / starts a new game
    // nB() - put new block on board (pick nbc(lass) and initial r(otation) randomly)
    // f1() - fall currently active block by +1 along y-axis
    // mB(d) - move current block in d = +/-1 direction along x-axis
    // rB(d) - rotate current block in -/+1 counter/clockwise direction
    // fz() - freeze/settle block in final position (will occupy board locations)
    // gO() - game over - handle interval and invoke draw of score / game over
    // drGO() - draw score and 'game over' to player
    // rBC(c) - register block class
    // init() - initialize... everything (strictly 'speaking: top border
    //      is not really needed, but I keep it for simplicity reasons
    var brd =
    { c: 8, r: 6, tm: 1000
    , it: null
    , ls: []
    , sc: 0
    , bCs: []
    , bs: []
    , b : null
    , f: function(x,y,xO,yO,ps,ft) {
        var i = ps.length, ls = this.ls;
        while (i > 0) { i--;
          if (ls[y + ps[i][1] + yO][x + ps[i][0] + xO]) {
            if (ft) { this.gO(); }
            return false;
        } }
        return true;
      }
    , start: function() {
        if (this.it) { clearInterval(this.it); this.it = null; }
        this.init();
        this.dr();
        this.nB();
        // this.it = setInterval("brd.f1()",this.tm);
        var _this = this;
        this.it = setInterval(function(){ _this.f1(); },this.tm);
      }
    , nB: function() {
        var bc = this.bCs[Math.floor(Math.random() * this.bCs.length)], r , b;
        rx = Math.floor(Math.random() * bc.prototype.rs.length);
        this.bs.push(this.b = (b = new bc(rx)));
        if (this.f(b.x,b.y,0,0,b.ps,false)) {
          this.b.dr();
        } else {
          this.gO();
        } 
      }
    , f1: function() { if (!this.b.f1()) { this.fz(); } }
    , mB: function(d) { this.b.m(d); }
    , rB: function(d) { this.b.r(d); }
    , fz: function() {
        var b = this.b, ps = b.ps, x = b.x, y = b.y, i = ps.length, ls = this.ls;
        while (i > 0) { i--; ls[y + ps[i][1]][x + ps[i][0]] = true; }
        this.sc += b.sc;
        this.nB();
      }
    , gO: function() {
        if (this.it) { clearInterval(this.it); this.it = null; }
        this.drGO();
      }
    , drGO: function() {
        // show scoring and that the game is over...
      }
    , dr: function() {
        this.ls.forEach(function(rls,y){ if ((y > 0) && (y < this.r)) { 
          rls.forEach(function(c,x){ if ((x > 0) && (x < this.c)) { 
            this.drl(x,y,c);
          } },this);
        } },this);
      }
    , drl: function(x,y,c) { setPixel(x,x,c); }
    , rBC: function(bC) { this.bCs.push(bC); }
    , init: function() {
        var rls, y = this.r + 2, x;
        this.ls = [];
        while (y > 0) { y--; 
          this.ls.push(rls = []); x = this.c + 2;
          while (x > 0) { x--;
            rls.push( (x === 0) || (x > this.c) || (y === 0) || ( y > this.r) );
          }
        }
        this.bs = [];
        this.sc = 0;
      }
    };
    

    This code needs for sure more things... for example, when a block is taller than two points the free-check get's out of bound... To make the game more interesting, speed can be varied/increased/interval shortened over time... The 'fall all the way' is missing, and of course all the other blocks...

    For development, I created a simple html page with embedded javascript. Since I do not have the hardware as you have, in the html test I overwrite the drawing methods... to suite the browser environment and canvas drawing. For input, I invoke the board methods from the UI directly. Your input capturing code has to do that in a similar way.

    I attached the html - just click on it and it will run. And as you can see from the screen shot, it also implements the bar with two squares... To look at the code, just view source in the browser.

    Thanks for posting... was a great inspiration --- and of course an example with a much higher success potential than may still unfinished pac man...


    2 Attachments

  • ...forgot to mention, that in the code some methods are exactly the same for all blocks... this allows to put them 'back into the board logic' rather then making 'super classes' and de-oo the code a bit - for a higher common good. Initially, I expected more 'short cuts' for the individual block types, but it turned out to be pretty uniform...

  • Thanks for the advice guys I've been rewriting all the code and it's much faster now. For the 'fallen' blocks that need to get rendered and checked for collisions I think I can copy and use the Graphics.buffer Array. I can renderer it using Graphics.drawImage() and easily check pixels for collisions by looking in the array (if it's colour is zero, it's empty).
    This method seems to work perfect, except one little issue. I have a 16x8 matrix which I've turned sideways and used Graphics.setRotation(1); . Sadly this breaks Graphics.drawImage(myCopyGraphicsBuffer)­;
    Is there a way to fix this?

    // Example code
    var m = Graphics.createArrayBuffer(16,8,24,{zigz­ag:true});
    m.flip = function(){ SPI2.send4bit(m.buffer, 0b0001, 0b0011); };
    m.setRotation(1);
    
    m.setColor(1,1,1);
    m.setPixel(0,0); // Top-left
    m.flip();
    
    // Copy buffer and clear matrix
    var a = new Uint8Array(m.buffer);
    var myBuffer = new Uint8Array(a);
    m.clear();
    
    var boardImg = {
      width: 16,
      height: 8,
      bpp: 24,
      buffer: myBuffer
    };
    m.drawImage(boardImg, 0, 0);
    m.flip();
    

    If you run this code the top-left pixel will flash white very quickly and disapear. If you comment out line 4 (m.setRotation(1);) the code work but obviously the bottom-left pixel lights up.

  • I'm not quite sure I understand what you want to do - you want to just copy the 'old' buffer back into m?

    I think the issue is that the image buffer itself has a really strange pixel order (because of the zigzag:true needed when sending to the LEDs, and also the setRotation(1)). You're then using that as data for drawImage which expects the data to be in a simple scanline format, and not rotated.

    If you do just want to restore the old data, try:

    var m = Graphics.createArrayBuffer(16,8,24,{zigz­ag:true});
    m.flip = function(){ SPI2.send4bit(m.buffer, 0b0001, 0b0011); };
    var mCopy = Graphics.createArrayBuffer(16,8,24,{zigz­ag:true});
    
    // save data away
    mCopy.buffer.set(m.buffer);
    // copy it back
    m.buffer.set(mCopy.buffer);
    

    That should work, and would be way faster as it just copies the raw data rather than trying to encode/decode it into pixels.

  • easily check pixels for collisions by looking in the array (if it's colour is zero, it's empty).

    The 'easily' may be challenged by the really strange pixel order as mentioned by @Gordon. Separating logic (model) from display (view) is good and longstanding practice. Anytime later, logic and display can be optimized and enhanced independently... especially the graphics you would like to enhance over time. Furthermore, it will allow you to pick any display... such as a 320x240 262K color display (with touch screen) - ... where shoving around of full screen buffers is just not an option... after all it's 153600 bytes, 1228800 bits, and this over SPI is ... (call it what your imagination likes...)... and it is only with 16 bit color depth... If you plan to go 24 bit - 1.6M colors - and with some LED displays you need an extra intensity byte - it becomes 32 bits per color!

    Yes, you can take advantage of a combination of Espruino memory Graphics Buffer and Display memory buffer and the knowledge about their inner workings... On the other hand - when you go that into the intrinsics - you can use then graphics controller that sits on the display to do such operations like shuffling around areas... This is much faster: since your block is already displayed, you just give commands to move graphics areas around - with tetris, it is two (2) rectangles at worst (the higher the resolution the more optimized is the amount of command bytes vs the amount of shoved around bits / bytes.

    I wondered the (rare?) redraw reason... and therefore just drew an empty board - which is in the end just a single rectangle, and then drawing the blocks that have settled and the active one... It may not beat the redraw from a buffer, but since it is rare and not in time critical phase of a game - for example on resume after a break with something else displayed, redrawing time does not matter that much.

    On the collision detection: when I was thinking about super fast collision detection a week-end ago - I was considering bit operation & instead of any loops with zero/null-checks. All blocks cover only a 4x4 bits area - 16 bits - and this information fits in an UInt16. So every location - 16*8 has a 16 bit value that represents the (combined) occupation information about the z/y location itself and the neighbors... Whit this setup, a collision detection boils down to a single & operation wether a particular x/y location is busy. The only draw back with this is a bit more code when a block settles to aggregate the UInt16s - and of course some memory consumption. Rotate would also be very simple, because it is just replacing one 16 bit value by another one... This will speed up your logic to the point for very very fast playing.

    Does not speed increase with level?

  • Ok, so using the output buffer array for collision detection is a bad idea.
    I'll try going back to an array of the fallen blocks to check for collisions.

    @Gordon I get an error when I try to use m.buffer.set()

    Uncaught Error: Field or method does not already exist, and can't create it on ArrayBuffer
     at line 1 col 13
    mCopy.buffer.set(m.buffer);
                 ^
     at line 1 col 9
    m.buffer.set(mCopy.buffer);
             ^
    
  • You need a buffer... but may be not a display/output buffer... but something has to represent the 'board' - 16x8 matrix - which is actually better than fallen/settled blocks. There is no need to check against all fallen blocks. When a block is fallen, the board is updated. Collision detection uses the board and the block's 'next' position - a maximum of four comparisons. Next positions are the next fall position, 90 degree left/right rotated block on same position, or move left/right position. If the next position would be a fall position, the block settles. If it cannot rotate or move left/right because of detected collision, nothing is done. The fallen blocks are kept for an eventual redraw - or - when you do not score when fallen/settled but at the end when game is over.

  • I still get an error when trying to copy the buffer even when there is output data in it? Here is the full test code.

    // Set up matrix
    SPI2.setup({baud:3200000, mosi:B15});
    var m = Graphics.createArrayBuffer(16,8,24,{zigz­ag:true});
    m.flip = function(){ SPI2.send4bit(m.buffer, 0b0001, 0b0011); };
    m.setRotation(1); // Rotate board 90degs
    var mCopy = Graphics.createArrayBuffer(16,8,24,{zigz­ag:true});
    
    m.setColor(0.3, 0.5, 0.7);
    m.setPixel(0,0);
    m.setPixel(4,4);
    m.flip();
    // Save copy
    mCopy.buffer.set(m.buffer);
    // Clear matrix
    m.clear();
    m.flip();
    // copy it back
    m.buffer.set(mCopy.buffer);
    m.flip();
    
    Uncaught Error: Field or method does not already exist, and can't create it on ArrayBuffer
     at line 1 col 13
    mCopy.buffer.set(m.buffer);
                 ^
     at line 1 col 9
    m.buffer.set(mCopy.buffer);
    
  • The 'easily' may be challenged by the really strange pixel order

    It's not a problem at all - just use getPixel and everything is handled behind the scenes for you - it only becomes an issue if you try and access the buffer itself directly.

    Errors

    Sorry, I should have checked my code. It's because Graphics.buffer is a plain (untyped) ArrayBuffer, not a Typed Array. The following should work:

    // save
    new Uint8Array(mCopy.buffer).set(m.buffer);
    // restore
    new Uint8Array(m.buffer).set(mCopy.buffer);
    

    One more thing - you'll probably want to do m.setRotation(1) and mCopy.setRotation(1), or when (if) you do operations on mCopy everything will be 90 degrees out.

  • Thank Gordon that works perfectly and using getPixel() for collision detection works really well.
    It's starting to come together now, see the short video below.
    Next I will add some hardware controls, line clearing and difficulty settings.
    https://www.youtube.com/watch?v=obvVTwzZ­a-o

  • Looks great!

  • Awesome stuff!
    You should consider adding a mirrored mode, its crazy difficult!

    (You see the the piece mirrored by horizontal/x axis. I did this for a clone i did as part of 1st year of my education and let me tell you - it takes a while to get used to it! When you get used to it, it's fun to swap back as well :D [I actually have a bachelors in games programming of all things...])

  • Wow, it looks awesome!

    Are you going to try and package it up into a complete system? It'd look great with a diffuser over the top (I'm pretty surprised RGB-123 don't sell them actually).

  • He's gotta get music out of it too... the original was just one tone at a time though wasn't it? So you could just use one PWM output and just change the frequency....

    It looks AWESOME!

  • He's gotta get music out of it too...

    :) If you use: http://www.espruino.com/Making+Music

    and then try

    var tune = "E   B C D   C B A   A C E   D C B   C D   E   C   A A   D F A   G F E   C E D   C B B   C D   E   C   A A";
    

    I just copied the notes off google, but I've messed up the rhythm a bit. If you change the positions of the spaces you should be able to get it to sound ok though :)

    Also: Simple hack, but if you connect a speaker between two PWM pins then you can get two tones at once :)

  • Looks like it's time for me to find an old buzzer speaker!
    Hopefully I will get some time tonight to finished the controls then I'll post the full source code here.

  • Looking forward to walk through the complete code - including display of current score! For example first at the top in the 'unused' space, and later at the bottom in the 'used' space of the settled blocks. Using the Graphics, it should be 'easy' to do...

    Regarding the music: For getting the rhythm, interpret the last blank of a sequence of separating blanks differently then the other ones. The last marks the separation and should be very short... the other ones should last as long as the note themselves (plus the length of the separation blank). This way you can get the rhythm right, but the holding of the notes is still missing. To help with that and not ending up with an overly long and cumbersome string, a more sophisticated notation is needed... The same is true for covering the range of an octave and beyond. Btw, the upper/lower case choice is great for getting the sharps and flats - because every flat can be represented by a flat (just don't try to tell that to a real musician...).

  • As a bit of a hack, you can actually lengthen the note using the character "-"

  • I don't really agree with your suggestion about how to write and handle the music :)

    I think the better way to think about it is that the piece is written in 16/16 (yes yes I know it's technically still 4/4 with 8th notes but I'm trying to get at the point that each note is actually both a note and a pause to get the staccato feel of the original).

    By counting it in my head as 16/16 I think this would be correct:

    var tune = "E   B C D   C B A   A C E   D C B     C D   E   C   A   A         D   F A   G F E     C E   D C B   B C D   E   C   A   A       ";
    

    Which is a string of 128 characters for the 8 bar song (with 8 notes and 8 pauses in each bar = 8 * (8+8) == 128) I've not tested it but I think it should be close :)

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

Faster Array looping

Posted by Avatar for Owen @Owen

Actions