data-fns
Version:
This library provides utility functions for working with array data that are useful in many contexts, including creative coding. It offers generic functions that perform common operations such as offsetting an array, generating an array based on a callbac
442 lines (367 loc) • 14.3 kB
JavaScript
/**
* Calls a callback function a specified number of times and returns the results in an array.
* @param iterations The number of times to call the callback function.
* @param callback The callback function to call.
* @returns An array containing the results of each callback function call.
* @example
* times(5, (i) => i * 2)
* // Returns [0, 2, 4, 6, 8]
*
*/
var times = function times(iterations, callback) {
// Initialize an empty array to store the results
var result = []; // Loop `iterations` number of times, calling the callback function on each iteration
for (var i = 0; i < iterations; i++) {
// Call the callback function with the current index and push the result to the results array
result.push(callback(i));
} // Return the results array
return result;
};
/**
* Maps an item in an array at a specified index to a new value.
* @param array The array to map the item in.
* @param index The index of the item to map.
* @param mapFn A function that maps the item to a new value.
* @returns A new array with the mapped item.
* @example
* const originalArray = [1, 2, 3, 4, 5]
* const mappedArray = mapAt(originalArray, 2, (item) => item * 2)
* // Returns [1, 2, 6, 4, 5]
*/
var mapAt = function mapAt(array, index, mapFn) {
// Check that the index is within the bounds of the array
if (index > array.length || index < 0) {
throw new Error('Index out of range');
} // Get the item at the specified index
var item = array[index]; // Map the item to a new value using the mapFn function
var newItem = mapFn(item); // If the new value is the same as the original value, return the original array
if (newItem === item) {
return array;
} // Create a new array with the mapped item
var newArray = array.slice();
newArray[index] = mapFn(array[index]);
return newArray;
};
/**
* Gets an item from an array based on a mapped index.
* @param index The index of the item to get.
* @param array The array to get the item from.
* @param indexMapFn A function that maps the index to a new index.
* @returns The item at the mapped index in the array.
* @example
* const array = ['a', 'b', 'c', 'd', 'e']
* const indexMapFn = (index, length) => (index * 2) % length
* getItem(2, array, indexMapFn)
* // Returns 'e'
*/
var getItem = function getItem(index, array, indexMapFn) {
// Map the index to a new index using the indexMapFn function
var mappedIndex = indexMapFn(index, array.length); // Return the item at the mapped index in the array
return array[mappedIndex];
};
/**
* Generates a sequence of values by applying a given function to an initial value for a specified number of iterations.
* @param iterations The number of iterations to perform.
* @param initialValue The initial value of the sequence.
* @param iteratorFn The function to apply to the initial value and each subsequent value.
* @returns An array containing all the iterations.
* @example
* generateSequence(5, 1, (value) => value * 2)
* // Returns [1, 2, 4, 8, 16]
*/
var generateSequence = function generateSequence(iterations, initialValue, iteratorFn) {
if (iterations < 0) {
throw new Error('Iterations must be a positive number');
}
if (iterations === 0) {
return [];
}
var sequence = [initialValue];
var value = initialValue;
for (var i = 1; i < iterations; i++) {
value = iteratorFn(value);
sequence.push(value);
}
return sequence;
};
/**
* Maps an index to a cyclic pattern.
* @param index The original index.
* @param length The length of the sequence.
* @returns The mapped index in the cyclic pattern.
* @example
* cyclic(6, 5)
* // Returns 1
*/
var cyclic = function cyclic(index, length) {
// Calculate the mapped index based on the cyclic pattern
var normalizedIndex = index % length;
return Math.abs(normalizedIndex >= 0 ? normalizedIndex : length + normalizedIndex);
};
/**
* Maps an index to a palindrome pattern.
* @param index The original index.
* @param length The length of the sequence.
* @returns The mapped index in the palindrome pattern.
* @example
* const length = 5
* const indexes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* indexes.map((index) => palindrome(index, length))
* // Returns [0, 1, 2, 1, 0, 1, 2, 1, 0, 1]
*/
var palindrome = function palindrome(index, length) {
// Save the length of the sequence in a variable
var arraySize = length; // If the sequence has only one element, return 0
if (arraySize === 1) {
return 0;
} // Calculate the mapped index based on the palindrome pattern
var normalizedIndex = index % (2 * arraySize - 2);
var id = normalizedIndex >= 0 ? normalizedIndex : 2 * arraySize - 2 + normalizedIndex;
if (id < arraySize) {
return Math.abs(id);
} else {
return 2 * arraySize - id - 2;
}
};
/**
* Returns the remainder of dividing the dividend by the divisor, with support for negative dividends.
* @param dividend The dividend to divide.
* @param divisor The divisor to divide by.
* @returns The remainder of dividing the dividend by the divisor.
* @example
* // Basic usage
* modulo(5, 3)
* // Returns 2
*
* // Support for negative dividends
* modulo(-5, -3)
* // Returns 2
*
* // Support for negative divisors
* modulo(-5, 3)
* // Returns -2
*
* // Support for negative dividends and divisors
* modulo(5, -3)
* // Returns -2
*
*/
var modulo = function modulo(dividend, divisor) {
// Compute the remainder of dividing the absolute value of the dividend by the absolute value of the divisor.
var remainder = Math.abs(dividend) % Math.abs(divisor); // Compute the sign of the result based on the sign of the dividend and divisor.
var sign = Math.sign(dividend) * Math.sign(divisor); // Compute the result by adjusting the remainder based on the sign.
var result = remainder * sign; // Return the result.
return result;
};
var ruleset110 = [0, 1, 1, 1, 1, 0, 0, 0];
/**
* Generates a new sequence using a one-dimensional cellular automaton.
* @param sequence The initial sequence.
* @param ruleset The ruleset for the cellular automaton.
* @param boundaryFn The boundary function to use.
* @returns The resulting sequence.
* @example
*
* generateSequence(10, sequence, cellularAutomata)
* // Returns [
* // [0, 0, 0, 0, 1, 0, 0, 0],
* // [0, 0, 0, 1, 1, 1, 0, 0],
* // [0, 0, 1, 1, 0, 0, 1, 0],
* // [0, 1, 1, 0, 1, 1, 1, 1],
* // [0, 1, 0, 0, 1, 0, 0, 0],
* // [1, 1, 1, 1, 1, 1, 0, 0],
* // [1, 0, 0, 0, 0, 0, 1, 1],
* // [0, 1, 0, 0, 0, 1, 1, 0],
* // [1, 1, 1, 0, 1, 1, 0, 1],
* // [0, 0, 0, 0, 1, 0, 0, 1],
* // ]
*
* @complexity This function has a time complexity of O(n), where n is the length of the input sequence, and
* a space complexity of O(n), where n is the length of the input sequence.
*/
var cellularAutomata = function cellularAutomata(sequence, ruleset, boundaryFn) {
if (ruleset === void 0) {
ruleset = ruleset110;
}
if (boundaryFn === void 0) {
boundaryFn = cyclic;
}
var nextSequence = new Array(sequence.length);
var len = sequence.length; // Apply the ruleset to each cell in the sequence
for (var i = 0; i < len; i++) {
var left = sequence[boundaryFn(i - 1, len)];
var middle = sequence[i];
var right = sequence[boundaryFn(i + 1, len)];
nextSequence[i] = ruleset[left * 4 + middle * 2 + right];
}
return nextSequence;
};
var numSort = function numSort(a, b) {
return a - b;
};
/**
* Generates a Euclidean rhythm sequence.
* @param steps The number of steps in the sequence.
* @param notes The number of notes in the sequence.
* @param rotation The rotation of the sequence (default: 0).
* @returns An array of indices representing the Euclidean rhythm sequence.
* @example
* euclideanSequencer(8, 3, 1)
* // Returns [1, 3, 6]
*/
var euclideanSequencer = function euclideanSequencer(steps, notes, rotation) {
if (rotation === void 0) {
rotation = 0;
}
// Check that inputs are positive integers
if (steps < 0 || notes < 0) {
throw new Error('Inputs must be positive integers');
} // Initialize an empty sequence array
var sequence = []; // Determine the maximum number of notes in the sequence
var maxNotes = notes > steps ? steps : notes; // Iterate through the number of notes and calculate the index for each note
for (var i = 0; i < maxNotes; i++) {
var index = Math.floor(i * steps / maxNotes); // Calculate the index using the Euclidean algorithm
sequence.push((index + rotation) % steps); // Add the index to the sequence, with rotation applied
} // Sort the sequence in ascending order
return sequence.sort(numSort);
};
/**
* Generates a sequence of indices representing the "silences" (i.e. rests) in a Euclidean rhythm.
* @param steps The number of steps in the rhythm.
* @param notes The number of notes in the rhythm.
* @param rotation The rotation of the rhythm (default: 0).
* @returns An array of indices representing the silences in the Euclidean rhythm.
* @example
* euclideanSilences(8, 3)
* // Returns [1, 3, 4, 6, 7]
*/
var euclideanSilences = function euclideanSilences(steps, notes, rotation) {
if (rotation === void 0) {
rotation = 0;
}
// Generate the Euclidean rhythm sequence using the euclideanSequencer function
var noteSequence = euclideanSequencer(steps, notes, rotation); // Initialize an empty silence sequence array
var silenceSequence = []; // Iterate through each step in the rhythm and add the index to the silence sequence if it is not in the note sequence
for (var i = 0; i < steps; i++) {
if (!noteSequence.includes(i)) {
silenceSequence.push(i);
}
} // Return the silence sequence
return silenceSequence;
};
/**
* Splits an array into chunks based on a pattern.
* @param array The array to split.
* @param pattern The pattern to split the array with.
* @returns An array of arrays representing the splits.
* @example
* patternChunks([1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3])
* // Returns [[1], [2, 3], [4, 5, 6], [7], [8]]
*
* @remarks
* This function splits the input array into chunks based on the specified pattern. The pattern is an array of
* numbers that determines the size of each chunk. If the pattern is shorter than the input array, it will be
* repeated cyclically. If the pattern is longer than the input array, the remaining elements will be discarded.
*/
var patternChunks = function patternChunks(array, pattern) {
var result = [];
var _array = [].concat(array);
var i = 0;
while (_array.length > 0) {
result.push(_array.splice(0, getItem(i, pattern, cyclic)));
i++;
}
return result;
};
/**
* Converts an array of binary digits to an array of indices where the digit is 1.
* @param binary An array of binary digits (0 or 1).
* @returns An array of indices where the digit is 1.
* @example
* binaryToIndices([1, 0, 1, 1, 0, 1])
* // Returns [0, 2, 3, 5]
*
* binaryToIndices([1, 1, 1, 1, 1])
* // Returns [0, 1, 2, 3, 4]
*
* binaryToIndices([0, 0, 0, 0, 0])
* // Returns []
*
*/
var binaryToIndices = function binaryToIndices(binary) {
// Initialize an empty array to store the indices.
var indices = []; // Loop through the binary array.
for (var i = 0; i < binary.length; i++) {
// If the digit is 1, add the index to the indices array.
if (binary[i] === 1) {
indices.push(i);
}
} // Return the array of indices.
return indices;
};
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _createForOfIteratorHelperLoose(o, allowArrayLike) {
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
if (it) return (it = it.call(o)).next.bind(it);
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
return function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
/**
* Converts an array of indices to a binary array where the indices are 1 and the other digits are 0.
* @param indices An array of indices.
* @param length The length of the binary array to be returned.
* @returns A binary array where the indices are 1 and the other digits are 0.
* @example
* // Basic usage
* indicesToBinary([0, 2, 4], 5)
* // Returns [1, 0, 1, 0, 1]
*
* // Ignoring negative indices
* indicesToBinary([0, -1, 2, -2, 4], 5)
* // Returns [1, 0, 1, 0, 1]
*
* // Indices outside range are ignored
* indicesToBinary([0, 2, 4, 6], 5)
* // Returns [1, 0, 1, 0, 1]
*/
var indicesToBinary = function indicesToBinary(indices, length) {
// Initialize a binary array of the given length, filled with 0s.
var binary = Array(length).fill(0); // Loop through the indices array.
for (var _iterator = _createForOfIteratorHelperLoose(indices), _step; !(_step = _iterator()).done;) {
var index = _step.value;
// Ignore negative indices.
if (index >= 0) {
// If the index is within range, set the corresponding digit to 1.
if (index < length) {
binary[index] = 1;
}
}
} // Return the binary array.
return binary;
};
export { binaryToIndices, cellularAutomata, cyclic, euclideanSequencer, euclideanSilences, generateSequence, getItem, indicesToBinary, mapAt, modulo, palindrome, patternChunks, times };
//# sourceMappingURL=data-fns.esm.js.map