Files
topos/src/ArrayExtensions.ts
2023-09-21 20:34:16 +02:00

369 lines
9.6 KiB
TypeScript

import { type UserAPI } from "./API";
import { SCALES } from "./Scales";
export {};
declare global {
interface Array<T> {
add(amount: number): number[];
sub(amount: number): number[];
mult(amount: number): number[];
div(amount: number): number[];
palindrome(): T[];
random(index: number): T;
rand(index: number): T;
degrade(amount: number): T;
repeatAll(amount: number): T;
repeatPair(amount: number): T;
repeatOdd(amount: number): T;
beat(division: number): T;
bar(): T;
pulse(): T;
pick(): T;
loop(index: number): T;
shuffle(): this;
rotate(steps: number): this;
unique(): this;
in(value: T): boolean;
square(): number[];
sqrt(): number[];
gen(min: number, max: number, times: number): number[];
}
}
export const makeArrayExtensions = (api: UserAPI) => {
Array.prototype.in = function <T>(this: T[], value: T): boolean {
return this.includes(value);
};
Array.prototype.square = function (): number[] {
/**
* @returns New array with squared values.
*/
return this.map((x: number) => x * x);
};
Array.prototype.sqrt = function (): number[] {
/**
* @returns New array with square roots of values. Throws if any element is negative.
*/
if (this.some((x) => x < 0))
throw new Error("Cannot take square root of negative number");
return this.map((x: number) => Math.sqrt(x));
};
Array.prototype.add = function (amount: number): number[] {
/**
* @param amount - The value to add to each element in the array.
* @returns New array with added values.
*/
return this.map((x: number) => x + amount);
};
Array.prototype.sub = function (amount: number): number[] {
/**
* @param amount - The value to subtract from each element in the array.
* @returns New array with subtracted values.
*/
return this.map((x: number) => x - amount);
};
Array.prototype.mult = function (amount: number): number[] {
/**
* @param amount - The value to multiply with each element in the array.
* @returns New array with multiplied values.
*/
return this.map((x: number) => x * amount);
};
Array.prototype.div = function (amount: number): number[] {
/**
* @param amount - The value to divide each element in the array by.
* @returns New array with divided values. Throws if division by zero.
*/
if (amount === 0) throw new Error("Division by zero");
return this.map((x: number) => x / amount);
};
Array.prototype.pick = function () {
/**
* Returns a random element from an array.
*
* @param array - The array of values to pick from
*/
return this[Math.floor(api.randomGen() * this.length)];
};
Array.prototype.gen = function (min: number, max: number, times: number) {
/**
* Returns an array of random numbers.
* @param min - The minimum value of the random numbers
* @param max - The maximum value of the random numbers
* @param times - The number of random numbers to generate
* @returns An array of random numbers
*/
if (times < 1) {
return [];
}
return Array.from(
{ length: times },
() => Math.floor(api.randomGen() * (max - min + 1)) + min
);
};
Array.prototype.bar = function () {
/**
* Returns an element from an array based on the current bar.
*
* @param array - The array of values to pick from
*/
return this[api.app.clock.time_position.bar % this.length];
};
Array.prototype.pulse = function () {
/**
* Returns an element from an array based on the current pulse.
*
* @param array - The array of values to pick from
*/
return this[api.app.clock.time_position.pulse % this.length];
};
Array.prototype.beat = function (divisor: number = 1) {
const chunk_size = divisor; // Get the first argument (chunk size)
const timepos = api.app.clock.pulses_since_origin;
const slice_count = Math.floor(
timepos / Math.floor(chunk_size * api.ppqn())
);
return this[slice_count % this.length];
};
Array.prototype.shuffle = function () {
/**
* Shuffles the array in place.
*
* @returns The shuffled array
*/
let currentIndex = this.length,
randomIndex;
while (currentIndex !== 0) {
randomIndex = Math.floor(api.randomGen() * currentIndex);
currentIndex--;
[this[currentIndex], this[randomIndex]] = [
this[randomIndex],
this[currentIndex],
];
}
return this;
};
Array.prototype.rotate = function (steps: number) {
/**
* Rotates the array in place.
*
* @param steps - The number of steps to rotate the array by
* @returns The rotated array
*/
const length = this.length;
if (steps < 0) {
steps = length + (steps % length);
} else if (steps > 0) {
steps = steps % length;
} else {
return this;
}
const rotated = this.splice(-steps, steps);
this.unshift(...rotated);
return this;
};
Array.prototype.unique = function () {
/**
* Removes duplicate elements from the array in place.
*
* @returns The array without duplicates
*/
const seen = new Set();
let writeIndex = 0;
for (let readIndex = 0; readIndex < this.length; readIndex++) {
const value = this[readIndex];
if (!seen.has(value)) {
seen.add(value);
this[writeIndex++] = value;
}
}
this.length = writeIndex;
return this;
};
Array.prototype.degrade = function <T>(this: T[], amount: number) {
/**
* Removes elements from the array at random. If the array has
* only one element left, it will not be removed.
*
* @param amount - The amount of elements to remove
* @returns The degraded array
*/
if (amount < 0 || amount > 100) {
throw new Error("Amount should be between 0 and 100");
}
if (this.length <= 1) {
return this;
}
for (let i = 0; i < this.length; ) {
const rand = api.randomGen() * 100;
if (rand < amount) {
if (this.length > 1) {
this.splice(i, 1);
} else {
return this;
}
} else {
i++;
}
}
return this;
};
Array.prototype.repeatAll = function <T>(this: T[], amount: number = 1) {
/**
* Repeats all elements in the array n times.
*
* @param amount - The amount of times to repeat the elements
* @returns The repeated array
*/
if (amount < 1) {
throw new Error("Amount should be at least 1");
}
let result = [];
for (let i = 0; i < this.length; i++) {
for (let j = 0; j < amount; j++) {
result.push(this[i]);
}
}
this.length = 0;
this.push(...result);
return this;
};
Array.prototype.repeatPair = function <T>(this: T[], amount: number = 1) {
/**
* Repeats all elements in the array n times, except for the
* elements at odd indexes.
*
* @param amount - The amount of times to repeat the elements
* @returns The repeated array
*/
if (amount < 1) {
throw new Error("Amount should be at least 1");
}
let result = [];
for (let i = 0; i < this.length; i++) {
// If the index is even, repeat the element
if (i % 2 === 0) {
for (let j = 0; j < amount; j++) {
result.push(this[i]);
}
} else {
result.push(this[i]);
}
}
this.length = 0;
this.push(...result);
return this;
};
Array.prototype.repeatOdd = function <T>(this: T[], amount: number = 1) {
/**
* Repeats all elements in the array n times, except for the
* elements at even indexes.
*
* @param amount - The amount of times to repeat the elements
* @returns The repeated array
*
* @remarks
* This function is the opposite of repeatPair.
*/
if (amount < 1) {
throw new Error("Amount should be at least 1");
}
let result = [];
for (let i = 0; i < this.length; i++) {
// If the index is odd, repeat the element
if (i % 2 !== 0) {
for (let j = 0; j < amount; j++) {
result.push(this[i]);
}
} else {
result.push(this[i]);
}
}
// Update the original array
this.length = 0;
this.push(...result);
return this;
};
// @ts-ignore
Array.prototype.palindrome = function <T>() {
/**
* Returns a palindrome of the array.
*
* @returns The palindrome of the array
*/
let left_to_right = Array.from(this);
let right_to_left = Array.from(this.reverse());
return left_to_right.concat(right_to_left);
};
Array.prototype.loop = function (index: number) {
/**
* Returns an element from the array based on the index.
* The index will wrap over the array.
*
* @param index - The index of the element to return
* @returns The element at the given index
*/
return this[index % this.length];
};
Array.prototype.random = function () {
/**
* Returns a random element from the array.
*
* @returns A random element from the array
*/
return this[Math.floor(api.randomGen() * this.length)];
};
Array.prototype.rand = Array.prototype.random;
};
Array.prototype.scale = function <T>(this: T[], scaleName: string = "major") {
const scale = SCALES[scaleName];
//input protect from unknow scale
if (!scale) {
throw new Error(`Unknown scale ${scaleName}`);
}
let result = [];
for (let j = 0; j < scale.length; j++) {
for (let i = 0; i < this.length; i++) {
result.push(this[i] + scale[j]);
}
}
return result;
};