moonman
moonman

Reputation: 67

Javascript looping behaviour

The alltones array contains all the possible notes in music scale.

var alltones = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" ];

I want to take the user's input of a tone, and then construct a list that contains certain elements of the array.

Say I start at alltones[5] or "F", and want to take every second element of the array from that point and put it into my list, until I get back around to "F". The array is more like a circle than straight list. I'm a little unsure of how the array operates once the loop reaches the last element.

Do I approach the problem by generating a new array, based on the users input. Or is there a way that JavaScript knows to loop back to the start of an array once it reaches the end such that it treats the array like a circle?

An example: User input = F
The output that I am seeking is to count (every two items) from F until we get back around the array to F
so the desired output would be => F G A B C# D#

Upvotes: 4

Views: 1139

Answers (5)

Cecchi
Cecchi

Reputation: 1535

How about this:

function getNotes(index) {
  var list = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" ];
  return list.splice(index).concat(list).filter(function(note, i) {
    return i % 2 == 0;
  });
}
getNotes(3); // ["D#", "F", "G", "A", "B", "C#"]

What this does is reorganize the array first so that the selected index is shifted to the beginning and everything before that is moved to the end, and then filters out every other note. If you'd rather have the getNotes function consume a string, you can use the indexOf method. indexOf and filter (used above) are part of ECMAScript 5 so you may need polyfills depending on what browsers you plan on supporting.

Here's a Fiddle with some examples.

Upvotes: 1

Rain Diao
Rain Diao

Reputation: 926

Here is the way to loop back to the start of an array once it reaches the end:

arr[index%arr.length], if index > arr.length, it will "loop" to the start.

this tech is efficient, and works for almost every language.

var alltones = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" ];

function generateMusic(startIndex) {
    var i = 0, music = [],
        tonesCount = alltones.length

    do { 
        music.push(alltones[(startIndex+i)%tonesCount]); 
    } while ((i+=2) < tonesCount);

    return music;
}

Result:

console.log(generateMusic(3)); //["D#", "F", "G", "A", "B", "C#"]
console.log(generateMusic(5)); //["F", "G", "A", "B", "C#", "D#"]

Upvotes: 1

palerdot
palerdot

Reputation: 7642

doing it in two loops one for array members starting after the initial point and another from the beginning is a better way; the key here is to start from the beginning depending where we left off at the end of the array.

you can create a generic function like

  var alltones = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" ];

  function build_tones(arr, start, jump_by){

  var new_list = [];

           //finish first iteration and get the last index so that we can offset accordingly from the beginning
             var get_last = (function(){
               var last;
              for(i = start; i <= arr.length; i+=jump_by){
                new_list.push(arr[i]);
                last = i;
                }
              return last;
              })();

     //do the second iteration by picking up from the index left and continue from first

    for(i = 0 + arr.length%get_last; i < start; i+=jump_by){
    new_list.push(arr[i]);
    }

    return new_list;

  }

 var result = build_tones(alltones, 5, 2);
 console.log(result);

Upvotes: 0

rampion
rampion

Reputation: 89113

There isn't an existing Array method to do what you want, but one is easily defined using Array.prototype.slice and Array.prototype.concat:

// move i elements from the start of the array to the end of the array
Array.prototype.lcycle = function(i) {
  var xs = this.slice(0,i);
  var ys = this.slice(i);
  return ys.concat(xs)
};
// move i elements from the end of the array to the start of the array
Array.prototype.rcycle = function(i) {
  var xs = this.slice(0,-i);
  var ys = this.slice(-i);
  return ys.concat(xs);
};

And then you can use lcycle and rcycle as normal array methods:

>>> alltones.lcycle(3)
[ "D#" , "E" , "F" , "F#" , "G" , "G#" , "A" , "A#" , "B" , "C" , "C#" , "D" ]
>>> alltones.rcycle(4)
[ "G#" , "A" , "A#" , "B" , "C" , "C#" , "D" , "D#" , "E" , "F" , "F#" , "G" ]

Note that both of these methods return a new array. If you wanted to mutate the original array, you could define similar methods using Array.prototype.splice.

// move i elements from the start of the array to the end of the array, mutating the original array
Array.prototype.lcycle_m = function(i) {
  var args = this.splice(0,i);
  args.unshift(0);
  args.unshift(this.length);
  this.splice.apply(this, args);
};
// move i elements from the end of the array to the start of the array, mutating the original array
Array.prototype.rcycle_m = function(i) {
  var args = this.splice(-i);
  args.unshift(0);
  args.unshift(0);
  this.splice.apply(this, args);
};

And again, you can can use lcycle_m and rcycle_m as normal array methods:

>>> alltones.lcycle_m(3)
>>> alltones
[ "D#" , "E" , "F" , "F#" , "G" , "G#" , "A" , "A#" , "B" , "C" , "C#" , "D" ]
>>> alltones.rcycle_m(3)
>>> alltones
[ "C" , "C#" , "D" , "D#" , "E" , "F" , "F#" , "G" , "G#" , "A" , "A#" , "B" ]

Upvotes: 2

kms
kms

Reputation: 389

You can do it in two loops, as JS has no built in support for circles.

for (var i = users_input; i < alltones.length; i += 1) {
  ... use alltones[i] for calc
}
for (var i = 0; i < users_input; i += 1) {
  ... use alltones[i] for calc
}

or something along that line. There really is no need for creating a new array.

Upvotes: 1

Related Questions