Array
The array is the default container type in HiseScript for holding multiple elements that can be accessed by their index. There are a few other container types which are better suited for particular workflows (see the section about Alternatives below), but for general data processing this is one of the most important concepts to know about.
Basic usage
const var a = []; // Declare an array
a[0] = 12; // Set the element at position 0 to 12
a[1] = "Hello"; // Set the element at position 1 to "Hello"
Console.print(a.length); // Print the length (in this case 2)
a.clear(); // Deletes all items from the array
If you have used another programming language before, this might look pretty familiar. Be aware that you can add / delete items from the array despite having it declared as const var
. The "constness" just refers to the assignment, so you can't reassign this variable to another value.
Iterating over an array
Basically there are two ways of iterating (= going over each element in the array):
Range-based Loop | Index-based Loop |
for(element in array)
|
for(i = 0; i < array.length; i++)
|
This is the preferred method, because it has a clearer syntax (and is faster). As long as you don't need to know the index of the element, it's recommended to use this method. | If you need the index of the current item in the loop (eg. because you iterate over multiple arrays at once), use this loop. |
The index based loop construct is the only place where you can define anonymous variables (so that for(i = 0...
doesn't throw a compile error).
Ownership & lifetime
An array is reference counted, which means that if you assign an array to another variable, it will use the same array.
const var a = [1, 5, 6];
const var b = a;
a[0] = 10; // set the first element of a
Console.print(b[0]); // the first element of b will also be 10
Alternatives
The Array is a very dynamic container type and can be resized at any time to make room for more elements. For UI and data processing this is an incredibly useful feature. However this flexibility makes it a very poor choice for whenever you want to do some MIDI processing logic which usually runs in the audio callback where allocating new memory is a no go.
In order to mitigate this problem, there are a bunch of other container types similar to the stock Array but with an emphasis on certain tasks within the realtime callback:
- Buffer is a densely packed, floating point array that represents audio signals
- MidiList is a object that holds 128 integer numbers and is particularly useful for holding MIDI information (eg. note numbers)
- FixObjectArray is a preallocated list of elements with a predefined memory structure and can be the most efficient solution for many tasks
- Unorderedstack is a stack that offers fast insertion / removal by ignoring the order of elements and is particularly useful for storing the information about currently played notes (where the order doesn't matter).
If you don't want to use those containers, you can of course use the Array in the MIDI processing context as long as you don't resize the container (which is why the Array.reserve() function is so importanta).
Class methods
clear
Clears the array.
Array.clear()
This is a quick operation (the allocated storage space will not be freed), so you can use it in a realtime callback.
const var arr = []; // Declare an array
// preallocate 10 elements, do this if you
// know how many elements you are about to insert
arr.reserve(10);
for(i = 0; i < 10; i++)
{
// Add an element to the end of the array
arr.push(Math.randInt(0, 1000);
}
Console.print(trace(arr)); // [ 523, 5, 76, 345, 765, 45, 977, 223, 44, 54]
arr.clear();
Console.print(trace(arr)); // []
clone
Creates a deep copy of the array.
Array.clone()
If you assign an array object
reference to another constant or variable, you're only setting the reference. Making any changes to the array B by referencing it will also make changes to array A. If you need a separate set of data to work on, you need to clone it.
const arr1 = [0, 1];
var arr2 = arr1;
// Changing any element in arr2 will also change it in arr1
arr2[0] = 22;
Console.print(trace(arr1)); // [22, 1]
// Reset the element 0 back to 0
arr1[0] = 0;
// Cloning the array creates a new dataset in memory, separate from the original array
arr2 = arr1.clone();
Console.print(trace(arr1)); [0, 1]
arr2[0] = 22;
Console.print(trace(arr2)); [22, 1]
Because arrays in HISE are effectively objects (which is hinted at by them having a bunch of class methods we're looking at here), this method will also work with any other object, including JSON objects, component references etc.
concat
Concatenates (joins) two or more arrays
Array.concat(var argumentList)
This method combines two or more arrays. You can pass in any number of arrays which will be put at the end of the array. It ignores non-array argument elements.
const var arr1 = [0, 1, [2, 3, 4]];
// note how the array in the array is counted as a single element
Console.print(arr1.length); // 3
const var arr2 = [5, 6, 7];
const var arr3 = [8, 9, 10];
arr1.concat(arr2);
Console.print(trace(arr1)); // [0, 1, [2, 3, 4], 5, 6, 7]
arr1.concat(arr3);
// the arr1 already contains arr2
Console.print(trace(arr1)); // [0, 1, [2, 3, 4], 5, 6, 7, 8, 9, 10]
// set type to array
const var arr4 = [];
arr4.concat(arr2, arr3, 8726, [11, 12, 13]);
// non-array arguments get ignored // arguments can be arrays themselves
Console.print(trace(arr4)); // [5, 6, 7, 8, 9, 10, 11, 12, 13]
contains
Searches for the element in the array.
Array.contains(var elementToLookFor)
The Array.contains
method checks if an array includes a certain element. If the array contains the specified element, it returns true
,
otherwise it returns false
.elementToLookFor
is the element to search for within the array.
Example
const var fruits = ["apple", "banana", "mango", "orange"];
Console.print(fruits.contains("banana")); // true
Console.print(fruits.contains("grape")); // false
every
Checks if all array elements pass a function test. Edit on GitHub
Array.every(var testFunction, var optionalThisObject)
filter
Creates a new array filled with elements that pass the function test. Edit on GitHub
Array.filter(var testFunction, var optionalThisObject)
find
Returns the value of the first element that passes the function test.
Array.find(var testFunction, var optionalThisObject)
The test function you pass in can have up to 3 parameters:
- the first parameter will be the element you need to perform the check on
- the second parameter will be the index
- the third parameter will be the array itself
const var list = [ "Hello", "world", "HISE", "rules" ];
Console.print(list.find(function(element){ return element.contains("H");})); // Hello
Console.print(list.find(function(element){ return element.contains("HI");})); // HISE
Using this function can vastly decrease the amount of code you need to write. This is the same logic of the first call with a loop and a custom function to achieve the same thing.
const var list = [ "Hello", "world", "HISE", "rules" ];
function findH()
{
for(element in list)
{
if(element.contains("H"))
return element;
}
return undefined;
}
Console.print(findH()); // Hello
findIndex
Returns the index of the first element that passes the function test. Edit on GitHub
Array.findIndex(var testFunction, var optionalThisObject)
forEach
Calls a function for each element. Edit on GitHub
Array.forEach(var testFunction, var optionalThisObject)
indexOf
Searches the array and returns the first index.
Array.indexOf(var elementToLookFor, int startOffset, int typeStrictness)
Return the index of the first occurence or -1
if the item can't be found.
const var a = [1, 5, 5];
a.indexOf(5); // will return 1
a.indexOf("5"); // will also return 1, the search is not type-strict.
insert
Inserts the given arguments at the firstIndex.
Array.insert(int firstIndex, var argumentList)
The Array.insert
method allows you to add an element at a specified firstIndex
in the array. It modifies the original array and shifts the elements after the specified index to the right allowing for greater control over the array's structure than using Array.push
or setting the element using the []
-operator.
Note that if you pass in an Array as new element, it will add the element as an array and not the individual elements, so you'll end up with an Array within an Array.
Example
const var numbers = [1, 2, 3, 4, 5];
numbers.insert(2, 10);
Console.print(trace(numbers)); // [1, 2, 10, 3, 4, 5]
numbers.insert(0, 20);
Console.print(trace(numbers)); // [20, 1, 2, 10, 3, 4, 5]
numbers.insert(numbers.length, 30);
Console.print(trace(numbers)); // [20, 1, 2, 10, 3, 4, 5, 30]
numbers.insert(3, [101, 102, 103]);
Console.print(trace(numbers));
isArray
Checks if the given variable is an array.
Array.isArray(var variableToTest)
A simple bool check whether the argument variable is an Array. Note that this function is not a method that you call on the object itself (because calling it on a non-array would not find the function).
Instead you'll call it with the generic syntax Array.isArray()
const var trustMeIAmAnArray = 0;
const var notAnArrayTooButNiceTryString = "[1, 2, 3, 4, 5]";
const var list = [1, 2, 3, 4];
Console.print(Array.isArray(notAnArrayTooButNiceTryString)); // false;
Console.print(Array.isArray(trustMeIAmAnArray)); // false
Console.print(Array.isArray(list)); // true (finally)
join
Joins the array into a string with the given separator.
Array.join(var separatorString)
This method is useful when you want to change the item list of some UI controls, for example a Combobox
.
const var list = ["item1", "item2", "item3"]; // Creates a list of all available samplemaps
const var box = Content.addComboBox("box", 0, 0); // Creates a combobox
box.set("items", list.join("\n")); // sets the list as items
The opposite of this method is String.split()
map
Creates a new array from calling a function for every array element.
Array.map(var testFunction, var optionalThisObject)
This is useful if you want to perform an operation for every single element of an array by passing in a premade function. An alternative would be to use a for(x in array)
loop, but the map method allows for cleaner multidimensional processing.
You need to pass in a function to be executed on each element of the array and an (optional) object that will be used as this
in the function call.
The function can have up to three parameters:
- the element
- the index of the element
- the full array
and is supposed to return a value that will be added to the newly created array that is returned by the function:
function(element, index, array)
{
return someValue;
}
But you can omit the second and third parameter if you don't need it.
const arr1 = ["hello", 2, 3];
arr1.map(function(element, index)
{
Console.print(index + ": " + element);
});
// Output:
// Interface: 0: hello
// Interface: 1: 2
// Interface: 2: 3
The method returns an array of individual function returns. If no return exists, the element will be undefined/null.
const arr1 = [0, 1];
const arr2 = arr1.map(function(element)
{
return element + 10;
});
Console.print(trace(arr2)); // [10, 11]
In order to supply a object that you can reference through this
in the function call, use the second argument:
const test = 10;
const arr1 = [0, 1];
arr1.map(function(element)
{
Console.print(test); // 10
}, test);
pop
Removes and returns the last element.
Array.pop()
This is useful for managing sequential input that you're keeping track of: history of played notes, velocities, custom undo history etc.
You might want to use it in conjunction with Array.push() in order to implement a stack logic with the array.
Note that there's a special container type if you need a stack that doesn't care about the order of elements.
const arr1 = [1, 2, 3];
arr1[4] = 5;
Console.print(arr1.pop()); // 5
// we didn't set the 4th element (index 3) so it'll be undefined
Console.print(arr1.pop()); // error: API call with undefined parameter
arr1[3] = 22;
Console.print(trace(arr1)); // [1, 2, 3, 22]
// we can check ourselves for errors in our logic in this case
if (isDefined(arr1.pop()
{
// do stuff
}
push
Adds the given element at the end and returns the size.
Array.push(var elementToInsert)
If you know that you are going to add multiple elements, you can call Array.reserve()
to preallocate the amount of elements and improve performance:
const var a = [];
// Uncomment this to see the performance improvement:
//a.reserve(100);
Console.start();
for(i = 0; i < 100; i++)
{
a.push(5);
}
Console.stop()
pushIfNotAlreadyThere
Adds the given element at the end and returns the size.
Array.pushIfNotAlreadyThere(var elementToInsert)
The method will not add an element to an array if a matching element already exists inside the array.
If the argument is an element that already exists, the return will still be the first index beyond the end of an array (not an index of a first/any matching element).
It is basically a short version of typing:
if(myArray.indexOf(someElement) == -1)
myArray.push(someElement);
const arr1 = [0, 1];
arr1.pushIfNotAlreadyThere(2);
Console.print(trace(arr1)); // [0, 1, 2]
// It won't add an element if it already exists in the array
arr1.pushIfNotAlreadyThere(2);
Console.print(trace(arr1)); // [0, 1, 2]
arr1.pushIfNotAlreadyThere(1);
Console.print(trace(arr1)); // [0, 1, 2]
Console.print(arr1.pushIfNotAlreadyThere(1)); // 3
remove
Removes all instances of the given element.
Array.remove(var elementToRemove)
Note that this will search and remove the value you pass in as argument - if you want to remove an element at a certain index, use Array.removeElement()
instead.
const var arr1 = [1, 2, 3, 4, 2, 5,];
arr1.remove(2);
Console.print(trace(arr1)); // [1, 3, 4, 5]
removeElement
Removes the element at the given position.
Array.removeElement(int index)
Note that the argument you pass into this function is supposed to be the (zero-based) index in the array, in order to remove a(ll) element(s) by value, use Array.remove()
instead
const var arr1 = [1, 5, 3];
Console.print(arr1[1]); // 5
arr1.removeElement(1);
Console.print(arr1[1]); // 3
reserve
Reserves the space needed for the given amount of elements.
Array.reserve(int numElements)
If you are going to populate this array in a realtime callback, you need to make sure that there is enough storage allocated in order to avoid reallocation. This method can be used to preallocate slots that can be filled later.
Be aware that this method will not change the Array.length
property:
const var array = []; // create a new array
array.reserve(128); // allocate 128 items
Console.print(array.length) // will output 0;
array[64] = 190; // this will not allocate
Console.print(array.length) // will print 65
This method will allocate enough memory to hold primitive values, but if you are going to store complex objects (arrays or objects), calling Array.reserve()
will not prevent reallocation.
reverse
Reverses the order of the elements in the array.
Array.reverse()
javascriptconst var arr1 = [1, 2, 3];arr1.reverse();Console.print(trace(arr1)); // [3, 2, 1]
some
Checks if any array elements pass a function test. Edit on GitHub
Array.some(var testFunction, var optionalThisObject)
sort
Sorts the array.
Array.sort()
This will sort the array using a sensible sort algorithm:
- Numbers will be sorted naturally,
- Strings will be sorted alphabetically
- Objects and arrays will not be sorted.
const var a = [1, 6, 4, 2, 1];
a.sort();
for(i in a)
Console.print(i);
// Result: 1, 1, 2, 4, 6
You can also customize the sorting by supplying a custom sort function with Engine.sortWithFunction()
.
sortNatural
Sorts array of numbers, objects, or strings with "number in string" priority. Can also sort a combination of all types
Array.sortNatural()
It puts arrays first in index order (doesn't sort them), followed by a mix of int, double and string variables. If a string starts with a number, it'll get thrown in the mix.
JSON objects go last.
const var arr1 = [5.2, 3, "1", "17",
[4, 2], [1, 12],
"word", "with2", "3LittlePigs",
{"prop1": 12, "prop2": 55}];
arr1.sortNatural();
// [[4, 2], [1, 12], "1", 3,
// "3LittlePigs", 5.2, "17",
// {"prop1": 12, "prop2": 55} ]
Console.print(trace(arr1));
You can also customize the sorting by supplying a custom sort function with Engine.sortWithFunction()
.