adding the slice and seqslice functions

This commit is contained in:
2023-08-19 17:05:12 +02:00
parent eb6fa8b385
commit 0ab5aaa992
2 changed files with 195 additions and 170 deletions

View File

@ -2,10 +2,10 @@ import { Pitch, Chord, Rest, Event, cachedPattern } from "zifferjs";
import { MidiConnection } from "./IO/MidiConnection";
import { tryEvaluate } from "./Evaluator";
import { DrunkWalk } from "./Utils/Drunk";
import { LRUCache } from 'lru-cache';
import { LRUCache } from "lru-cache";
import { scale } from "./Scales";
import { Editor } from "./main";
import { Sound } from './Sound';
import { Sound } from "./Sound";
import {
samples,
initAudioOnFirstClick,
@ -14,14 +14,13 @@ import {
} from "superdough";
// This is an LRU cache used for storing persistent patterns
const cache = new LRUCache({max: 1000, ttl: 1000 * 60 * 5});
const cache = new LRUCache({ max: 1000, ttl: 1000 * 60 * 5 });
interface ControlChange {
channel: number
control: number
value: number
}
channel: number;
control: number;
value: number;
}
interface Pattern<T> {
pattern: any[];
@ -42,7 +41,6 @@ Array.prototype.in = function <T>(this: T[], value: T): boolean {
return this.includes(value);
};
async function loadSamples() {
const ds = "https://raw.githubusercontent.com/felixroos/dough-samples/main/";
return Promise.all([
@ -57,8 +55,7 @@ async function loadSamples() {
]);
}
loadSamples()
loadSamples();
export class UserAPI {
/**
@ -88,7 +85,7 @@ export class UserAPI {
* @returns the current AudioContext time (wall clock)
*/
return this.app.audioContext.currentTime;
}
};
// =============================================================
// Mouse functions
@ -99,14 +96,14 @@ export class UserAPI {
* @returns The current x position of the mouse
*/
return this.app._mouseX;
}
};
public mouseY = (): number => {
/**
* @returns The current y position of the mouse
*/
return this.app._mouseY;
}
};
// =============================================================
// Utility functions
@ -125,7 +122,7 @@ export class UserAPI {
this.app.universes[this.app.selected_universe].locals[arg]
);
});
}
};
s = this.script;
clear_script = (script: number): void => {
@ -139,7 +136,7 @@ export class UserAPI {
committed: "",
evaluations: 0,
};
}
};
cs = this.clear_script;
copy_script = (from: number, to: number): void => {
@ -151,7 +148,7 @@ export class UserAPI {
*/
this.app.universes[this.app.selected_universe].locals[to] =
this.app.universes[this.app.selected_universe].locals[from];
}
};
cps = this.copy_script;
// =============================================================
@ -166,7 +163,7 @@ export class UserAPI {
*/
console.log(this.MidiConnection.listMidiOutputs());
return this.MidiConnection.midiOutputs;
}
};
public midi_output = (outputName: string): void => {
/**
@ -179,9 +176,12 @@ export class UserAPI {
} else {
this.MidiConnection.switchMidiOutput(outputName);
}
}
};
public note = (note: number, options: { [key: string]: number } = {}): void => {
public note = (
note: number,
options: { [key: string]: number } = {}
): void => {
/**
* Sends a MIDI note to the current MIDI output.
*
@ -193,7 +193,7 @@ export class UserAPI {
const velocity = options.velocity ? options.velocity : 100;
const duration = options.duration ? options.duration : 0.5;
this.MidiConnection.sendMidiNote(note, channel, velocity, duration);
}
};
public sysex = (data: Array<number>): void => {
/**
@ -202,7 +202,7 @@ export class UserAPI {
* @param data - The sysex data to send
*/
this.MidiConnection.sendSysExMessage(data);
}
};
public pitch_bend = (value: number, channel: number): void => {
/**
@ -214,7 +214,7 @@ export class UserAPI {
* @returns The value of the pitch bend
*/
this.MidiConnection.sendPitchBend(value, channel);
}
};
public program_change = (program: number, channel: number): void => {
/**
@ -224,17 +224,20 @@ export class UserAPI {
* @param channel - The MIDI channel to send the program change on
*/
this.MidiConnection.sendProgramChange(program, channel);
}
};
public midi_clock = (): void => {
/**
* Sends a MIDI clock to the current MIDI output.
*/
this.MidiConnection.sendMidiClock();
}
};
public control_change = ({control= 20, value= 0, channel=0 }: ControlChange): void => {
public control_change = ({
control = 20,
value = 0,
channel = 0,
}: ControlChange): void => {
/**
* Sends a MIDI control change to the current MIDI output.
*
@ -242,53 +245,65 @@ export class UserAPI {
* @param value - The value of the control
*/
this.MidiConnection.sendMidiControlChange(control, value, channel);
}
};
public midi_panic = (): void => {
/**
* Sends a MIDI panic message to the current MIDI output.
*/
this.MidiConnection.panic();
}
};
// =============================================================
// Ziffers related functions
// =============================================================
public zn = (input: string,
options: {[key: string]: string|number} = {}): Event => {
const pattern = cachedPattern(input, options);
//@ts-ignore
if(pattern.hasStarted()) {
const event = pattern.peek();
// Check if event is modified
const node = event!.modifiedEvent ? event!.modifiedEvent : event;
const channel = (options.channel ? options.channel : 0) as number;
const velocity = (options.velocity ? options.velocity : 100) as number;
const sustain = (options.sustain ? options.sustain : 0.5) as number;
if(node instanceof Pitch) {
if(node.bend) this.MidiConnection.sendPitchBend(node.bend, channel);
this.MidiConnection.sendMidiNote(node.note!, channel, velocity, sustain);
if(node.bend) this.MidiConnection.sendPitchBend(8192, channel);
} else if(node instanceof Chord) {
node.pitches.forEach((pitch: Pitch) => {
if(pitch.bend) this.MidiConnection.sendPitchBend(pitch.bend, channel);
this.MidiConnection.sendMidiNote(pitch.note!, channel, velocity, sustain);
if(pitch.bend) this.MidiConnection.sendPitchBend(8192, channel);
});
} else if(node instanceof Rest) {
// do nothing for now ...
}
// Remove old modified event
if(event!.modifiedEvent) event!.modifiedEvent = undefined;
public zn = (
input: string,
options: { [key: string]: string | number } = {}
): Event => {
const pattern = cachedPattern(input, options);
//@ts-ignore
if (pattern.hasStarted()) {
const event = pattern.peek();
// Check if event is modified
const node = event!.modifiedEvent ? event!.modifiedEvent : event;
const channel = (options.channel ? options.channel : 0) as number;
const velocity = (options.velocity ? options.velocity : 100) as number;
const sustain = (options.sustain ? options.sustain : 0.5) as number;
if (node instanceof Pitch) {
if (node.bend) this.MidiConnection.sendPitchBend(node.bend, channel);
this.MidiConnection.sendMidiNote(
node.note!,
channel,
velocity,
sustain
);
if (node.bend) this.MidiConnection.sendPitchBend(8192, channel);
} else if (node instanceof Chord) {
node.pitches.forEach((pitch: Pitch) => {
if (pitch.bend)
this.MidiConnection.sendPitchBend(pitch.bend, channel);
this.MidiConnection.sendMidiNote(
pitch.note!,
channel,
velocity,
sustain
);
if (pitch.bend) this.MidiConnection.sendPitchBend(8192, channel);
});
} else if (node instanceof Rest) {
// do nothing for now ...
}
//@ts-ignore
return pattern.next();
}
// Remove old modified event
if (event!.modifiedEvent) event!.modifiedEvent = undefined;
}
//@ts-ignore
return pattern.next();
};
// =============================================================
// Iterator related functions
@ -339,7 +354,7 @@ export class UserAPI {
// Return current iterator value
return this.iterators[name].value;
}
};
$ = this.iterator;
// =============================================================
@ -361,7 +376,7 @@ export class UserAPI {
}
this._drunk.step();
return this._drunk.getPosition();
}
};
public drunk_max = (max: number) => {
/**
@ -370,7 +385,7 @@ export class UserAPI {
* @param max - The maximum value of the drunk mechanism
*/
this._drunk.max = max;
}
};
public drunk_min = (min: number) => {
/**
@ -379,7 +394,7 @@ export class UserAPI {
* @param min - The minimum value of the drunk mechanism
*/
this._drunk.min = min;
}
};
public drunk_wrap = (wrap: boolean) => {
/**
@ -388,7 +403,7 @@ export class UserAPI {
* @param wrap - Whether the drunk mechanism should wrap around
*/
this._drunk.toggleWrap(wrap);
}
};
// =============================================================
// Variable related functions
@ -408,7 +423,7 @@ export class UserAPI {
this.variables[a] = b;
return this.variables[a];
}
}
};
v = this.variable;
public delete_variable = (name: string): void => {
@ -418,7 +433,7 @@ export class UserAPI {
* @param name - The name of the variable to delete
*/
delete this.variables[name];
}
};
dv = this.delete_variable;
public clear_variables = (): void => {
@ -430,7 +445,7 @@ export class UserAPI {
* Use with caution.
*/
this.variables = {};
}
};
cv = this.clear_variables;
// =============================================================
@ -440,7 +455,7 @@ export class UserAPI {
private _sequence_key_generator(pattern: any[]) {
/**
* Generates a key for the sequence function.
*
*
* @param input - The input to generate a key for
* @returns A key for the sequence function
*/
@ -448,6 +463,20 @@ export class UserAPI {
return btoa(JSON.stringify(pattern));
}
public slice = (chunk: number): boolean => {
const time_pos = this.epulse();
const current_chunk = Math.floor(time_pos / chunk);
return current_chunk % 2 === 0;
};
public seqslice = (...args: any): any => {
const chunk_size = args[0]; // Get the first argument (chunk size)
const elements = args.slice(1); // Get the rest of the arguments as an array
const timepos = epulse();
const slice_count = Math.floor(timepos / chunk_size);
return elements[slice_count % elements.length];
};
public seqmod = (...input: any[]) => {
if (cache.has(this._sequence_key_generator(input))) {
let sequence = cache.get(
@ -458,82 +487,83 @@ export class UserAPI {
if (sequence.options.currentIteration === sequence.options.nextTarget) {
sequence.options.index++;
sequence.options.nextTarget = input[sequence.options.index % input.length];
sequence.options.nextTarget =
input[sequence.options.index % input.length];
sequence.options.currentIteration = 0;
}
cache.set(this._sequence_key_generator(input), {
pattern: input as any[],
options: sequence.options
pattern: input as any[],
options: sequence.options,
});
return sequence.options.currentIteration === 0;
} else {
let pattern_options = {
index: -1, nextTarget: this.app.clock.ticks_before_new_bar,
currentIteration: 0
let pattern_options = {
index: -1,
nextTarget: this.app.clock.ticks_before_new_bar,
currentIteration: 0,
};
if (typeof input[input.length - 1] === "object") {
pattern_options = {
...input.pop(),
...pattern_options as object
pattern_options = {
...input.pop(),
...(pattern_options as object),
};
}
}
// pattern_options.currentIteration++;
// TEST
pattern_options.nextTarget = this.app.clock.ticks_before_new_bar
pattern_options.nextTarget = this.app.clock.ticks_before_new_bar;
if (pattern_options.currentIteration === pattern_options.nextTarget) {
pattern_options.index++;
pattern_options.nextTarget = input[pattern_options.index % input.length];
pattern_options.nextTarget =
input[pattern_options.index % input.length];
pattern_options.currentIteration = 0;
}
cache.set(this._sequence_key_generator(input), {
pattern: input as any[],
options: pattern_options
pattern: input as any[],
options: pattern_options,
});
return pattern_options.currentIteration === 0;
}
}
};
public seq = (...input: any[]) => {
/**
* Returns a value in a sequence stored using an LRU Cache.
* The sequence is stored in the cache with an hash identifier
* made from a base64 encoding of the pattern. The pattern itself
* is composed of the pattern itself (a list of arbitrary typed
* is composed of the pattern itself (a list of arbitrary typed
* values) and a set of options (an object) detailing how the pattern
* should be iterated on.
*
*
* @param input - The input to generate a key for
* Note that the last element of the input can be an object
* containing options for the sequence function.
* @returns A value in a sequence stored using an LRU Cache
*/
if (cache.has(this._sequence_key_generator(input))) {
let sequence = cache.get(this._sequence_key_generator(input)) as Pattern<any>;
let sequence = cache.get(
this._sequence_key_generator(input)
) as Pattern<any>;
sequence.options.index += 1;
cache.set(this._sequence_key_generator(input), sequence);
return sequence.pattern[
sequence.options.index % sequence.pattern.length
];
return sequence.pattern[sequence.options.index % sequence.pattern.length];
} else {
let pattern_options = { index: 0 };
if (typeof input[input.length - 1] === "object") {
pattern_options = { ...input.pop(), ...pattern_options as object };
}
pattern_options = { ...input.pop(), ...(pattern_options as object) };
}
cache.set(this._sequence_key_generator(input), {
pattern: input as any[],
options: pattern_options
pattern: input as any[],
options: pattern_options,
});
return cache.get(this._sequence_key_generator(input));
}
}
};
pick = <T>(...array: T[]): T => {
/**
@ -542,7 +572,7 @@ export class UserAPI {
* @param array - The array of values to pick from
*/
return array[Math.floor(Math.random() * array.length)];
}
};
seqbeat = <T>(...array: T[]): T => {
/**
@ -551,7 +581,7 @@ export class UserAPI {
* @param array - The array of values to pick from
*/
return array[this.ebeat() % array.length];
}
};
mel = <T>(iterator: number, array: T[]): T => {
/**
@ -561,7 +591,7 @@ export class UserAPI {
* @param array - The array of values to pick from
*/
return array[iterator % array.length];
}
};
seqbar = <T>(...array: T[]): T => {
/**
@ -570,7 +600,7 @@ export class UserAPI {
* @param array - The array of values to pick from
*/
return array[(this.app.clock.time_position.bar + 1) % array.length];
}
};
seqpulse = <T>(...array: T[]): T => {
/**
@ -579,7 +609,7 @@ export class UserAPI {
* @param array - The array of values to pick from
*/
return array[this.app.clock.time_position.pulse % array.length];
}
};
// =============================================================
// Randomness functions
@ -594,7 +624,7 @@ export class UserAPI {
* @returns A random integer between min and max
*/
return Math.floor(Math.random() * (max - min + 1)) + min;
}
};
rand = (min: number, max: number): number => {
/**
@ -605,7 +635,7 @@ export class UserAPI {
* @returns A random float between min and max
*/
return Math.random() * (max - min) + min;
}
};
rI = this.randI;
r = this.rand;
@ -631,7 +661,7 @@ export class UserAPI {
}
});
return closest;
}
};
quant = this.quantize;
public clamp = (value: number, min: number, max: number): number => {
@ -644,7 +674,7 @@ export class UserAPI {
* @returns A value clamped between min and max
*/
return Math.min(Math.max(value, min), max);
}
};
cmp = this.clamp;
// =============================================================
@ -663,12 +693,12 @@ export class UserAPI {
if (n < 1 || n > 500) console.log(`Setting bpm to ${n}`);
this.app.clock.bpm = n;
return n;
}
};
tempo = this.bpm;
public bpb = (n?: number): number => {
/**
* Sets or returns the number of beats per bar.
* Sets or returns the number of beats per bar.
*
* @param bpb - [optional] The number of beats per bar to set
* @returns The current bpb
@ -678,7 +708,7 @@ export class UserAPI {
if (n < 1) console.log(`Setting bpb to ${n}`);
this.app.clock.time_signature[0] = n;
return n;
}
};
public ppqn = (n?: number) => {
/**
@ -689,7 +719,7 @@ export class UserAPI {
if (n < 1) console.log(`Setting ppqn to ${n}`);
this.app.clock.ppqn = n;
return n;
}
};
public time_signature = (numerator: number, denominator: number): void => {
/**
@ -700,7 +730,7 @@ export class UserAPI {
* @returns The current time signature
*/
this.app.clock.time_signature = [numerator, denominator];
}
};
// =============================================================
// Probability functions
@ -713,7 +743,7 @@ export class UserAPI {
* @returns True 10% of the time
*/
return Math.random() > 0.9;
}
};
public sometimes = (): boolean => {
/**
@ -722,7 +752,7 @@ export class UserAPI {
* @returns True 50% of the time
*/
return Math.random() > 0.5;
}
};
public rarely = (): boolean => {
/**
@ -731,7 +761,7 @@ export class UserAPI {
* @returns True 25% of the time
*/
return Math.random() > 0.75;
}
};
public often = (): boolean => {
/**
@ -740,7 +770,7 @@ export class UserAPI {
* @returns True 75% of the time
*/
return Math.random() > 0.25;
}
};
public almostAlways = (): boolean => {
/**
@ -749,7 +779,7 @@ export class UserAPI {
* @returns True 90% of the time
*/
return Math.random() > 0.1;
}
};
public dice = (sides: number): number => {
/**
@ -759,7 +789,7 @@ export class UserAPI {
* @returns The value of a dice roll with n sides
*/
return Math.floor(Math.random() * sides) + 1;
}
};
// =============================================================
// Iterator functions (for loops, with evaluation count, etc...)
@ -773,12 +803,11 @@ export class UserAPI {
*/
if (n !== undefined) {
this.app.universes[this.app.selected_universe].global.evaluations = n;
return this.app.universes[this.app.selected_universe]
return this.app.universes[this.app.selected_universe];
}
return this.app.universes[this.app.selected_universe]
.global
return this.app.universes[this.app.selected_universe].global
.evaluations as number;
}
};
// =============================================================
// Time markers
@ -791,7 +820,7 @@ export class UserAPI {
* @returns The current bar number
*/
return this.app.clock.time_position.bar;
}
};
tick = (): number => {
/**
@ -800,7 +829,7 @@ export class UserAPI {
* @returns The current tick number
*/
return this.app.clock.tick;
}
};
pulse = (): number => {
/**
@ -809,7 +838,7 @@ export class UserAPI {
* @returns The current pulse number
*/
return this.app.clock.time_position.pulse;
}
};
beat = (): number => {
/**
@ -818,28 +847,28 @@ export class UserAPI {
* @returns The current beat number
*/
return this.app.clock.time_position.beat;
}
};
ebeat = (): number => {
/**
* Returns the current beat number since the origin of time
*/
return this.app.clock.beats_since_origin;
}
};
epulse = (): number => {
/**
* Returns the current number of pulses elapsed since origin of time
*/
return this.app.clock.pulses_since_origin;
}
};
onbar = (n: number, ...bar: number[]): boolean => {
// n is acting as a modulo on the bar number
const bar_list = [...Array(n).keys()].map((i) => i + 1);
console.log(bar.some((b) => bar_list.includes(b % n)));
return bar.some((b) => bar_list.includes(b % n));
}
};
onbeat = (...beat: number[]): boolean => {
/**
@ -853,7 +882,7 @@ export class UserAPI {
*/
let final_pulses: boolean[] = [];
beat.forEach((b) => {
b = (b % this.app.clock.time_signature[0]) + 1;
b = (b % this.app.clock.time_signature[0]) + 1;
let integral_part = Math.floor(b);
let decimal_part = b - integral_part;
final_pulses.push(
@ -863,7 +892,7 @@ export class UserAPI {
);
});
return final_pulses.some((p) => p == true);
}
};
stop = (): void => {
/**
@ -874,7 +903,7 @@ export class UserAPI {
*/
this.app.clock.pause();
this.app.setButtonHighlighting("pause", true);
}
};
silence = this.stop;
hush = this.stop;
@ -886,7 +915,7 @@ export class UserAPI {
* @returns True p% of the time
*/
return Math.random() * 100 < p;
}
};
toss = (): boolean => {
/**
@ -900,7 +929,7 @@ export class UserAPI {
* @see almostNever
*/
return Math.random() > 0.5;
}
};
min = (...values: number[]): number => {
/**
@ -910,7 +939,7 @@ export class UserAPI {
* @returns The minimum value of the list of numbers
*/
return Math.min(...values);
}
};
max = (...values: number[]): number => {
/**
@ -920,7 +949,7 @@ export class UserAPI {
* @returns The maximum value of the list of numbers
*/
return Math.max(...values);
}
};
limit = (value: number, min: number, max: number): number => {
/**
@ -932,7 +961,7 @@ export class UserAPI {
* @returns The limited value
*/
return Math.min(Math.max(value, min), max);
}
};
delay = (ms: number, func: Function): void => {
/**
@ -943,7 +972,7 @@ export class UserAPI {
* @returns The current time signature
*/
setTimeout(func, ms);
}
};
delayr = (ms: number, nb: number, func: Function): void => {
/**
@ -958,7 +987,7 @@ export class UserAPI {
list.forEach((ms, _) => {
setTimeout(func, ms);
});
}
};
mod = (...pulse: number[]): boolean => {
/**
@ -968,7 +997,7 @@ export class UserAPI {
* @returns True if the current pulse is a modulo of any of the given pulses
*/
return pulse.some((p) => this.app.clock.time_position.pulse % p === 0);
}
};
modbar = (...bar: number[]): boolean => {
/**
@ -979,7 +1008,7 @@ export class UserAPI {
*
*/
return bar.some((b) => this.app.clock.time_position.bar % b === 0);
}
};
// =============================================================
// Rythmic generators
@ -1001,7 +1030,7 @@ export class UserAPI {
* @returns boolean value based on the euclidian sequence
*/
return this._euclidean_cycle(pulses, length, rotate)[iterator % length];
}
};
_euclidean_cycle(
pulses: number,
@ -1037,7 +1066,7 @@ export class UserAPI {
let convert: string = n.toString(2);
let tobin: boolean[] = convert.split("").map((x: string) => x === "1");
return tobin[iterator % tobin.length];
}
};
// =============================================================
// Low Frequency Oscillators
@ -1063,7 +1092,7 @@ export class UserAPI {
}
return result;
}
};
sine = (freq: number = 1, offset: number = 0): number => {
/**
@ -1076,19 +1105,19 @@ export class UserAPI {
return (
Math.sin(this.app.clock.ctx.currentTime * Math.PI * 2 * freq) + offset
);
}
};
usine = (freq: number = 1, offset: number = 0): number => {
/**
* Returns a sine wave between 0 and 1.
*
*
* @param freq - The frequency of the sine wave
* @param offset - The offset of the sine wave
* @returns A sine wave between 0 and 1
* @see sine
*/
return (this.sine(freq, offset) + 1) / 2;
}
};
saw = (freq: number = 1, offset: number = 0): number => {
/**
@ -1103,19 +1132,19 @@ export class UserAPI {
* @see noise
*/
return ((this.app.clock.ctx.currentTime * freq) % 1) * 2 - 1 + offset;
}
};
usaw = (freq: number = 1, offset: number = 0): number => {
/**
* Returns a saw wave between 0 and 1.
*
*
* @param freq - The frequency of the saw wave
* @param offset - The offset of the saw wave
* @returns A saw wave between 0 and 1
* @see saw
*/
return (this.saw(freq, offset) + 1) / 2;
}
};
triangle = (freq: number = 1, offset: number = 0): number => {
/**
@ -1128,19 +1157,19 @@ export class UserAPI {
* @see noise
*/
return Math.abs(this.saw(freq, offset)) * 2 - 1;
}
};
utriangle = (freq: number = 1, offset: number = 0): number => {
/**
* Returns a triangle wave between 0 and 1.
*
*
* @param freq - The frequency of the triangle wave
* @param offset - The offset of the triangle wave
* @returns A triangle wave between 0 and 1
* @see triangle
*/
return (this.triangle(freq, offset) + 1) / 2;
}
};
square = (freq: number = 1, offset: number = 0): number => {
/**
@ -1153,19 +1182,19 @@ export class UserAPI {
* @see noise
*/
return this.saw(freq, offset) > 0 ? 1 : -1;
}
};
usquare = (freq: number = 1, offset: number = 0): number => {
/**
* Returns a square wave between 0 and 1.
*
*
* @param freq - The frequency of the square wave
* @param offset - The offset of the square wave
* @returns A square wave between 0 and 1
* @see square
*/
return (this.square(freq, offset) + 1) / 2;
}
};
noise = (): number => {
/**
@ -1179,7 +1208,7 @@ export class UserAPI {
* @see noise
*/
return Math.random() * 2 - 1;
}
};
// =============================================================
// Math functions
@ -1193,7 +1222,7 @@ export class UserAPI {
sound = (sound: string) => {
return new Sound(sound, this.app);
}
};
samples = samples;
@ -1206,7 +1235,5 @@ export class UserAPI {
// TODO: Implement this. This function should change the rate at which the global script
// is evaluated. This is useful for slowing down the script, or speeding it up. The default
// would be 1.0, which is the current rate (very speedy).
}
};
}