Event superclass for Note and Sound
This commit is contained in:
@ -37,7 +37,7 @@
|
|||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"tone": "^14.8.49",
|
"tone": "^14.8.49",
|
||||||
"vite-plugin-markdown": "^2.1.0",
|
"vite-plugin-markdown": "^2.1.0",
|
||||||
"zifferjs": "^0.0.8",
|
"zifferjs": "^0.0.9",
|
||||||
"zzfx": "^1.2.0"
|
"zzfx": "^1.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
88
src/API.ts
88
src/API.ts
@ -1,4 +1,4 @@
|
|||||||
import { Pitch, Chord, Rest, Event, cachedPattern } from "zifferjs";
|
import { Pitch, Chord, Rest, Event, cachedPattern, seededRandom } from "zifferjs";
|
||||||
import { MidiConnection } from "./IO/MidiConnection";
|
import { MidiConnection } from "./IO/MidiConnection";
|
||||||
import { tryEvaluate } from "./Evaluator";
|
import { tryEvaluate } from "./Evaluator";
|
||||||
import { DrunkWalk } from "./Utils/Drunk";
|
import { DrunkWalk } from "./Utils/Drunk";
|
||||||
@ -6,6 +6,7 @@ import { LRUCache } from "lru-cache";
|
|||||||
import { scale } from "./Scales";
|
import { scale } from "./Scales";
|
||||||
import { Editor } from "./main";
|
import { Editor } from "./main";
|
||||||
import { Sound } from "./Sound";
|
import { Sound } from "./Sound";
|
||||||
|
import { Note } from "./Note";
|
||||||
import {
|
import {
|
||||||
samples,
|
samples,
|
||||||
initAudioOnFirstClick,
|
initAudioOnFirstClick,
|
||||||
@ -68,6 +69,9 @@ export class UserAPI {
|
|||||||
private variables: { [key: string]: any } = {};
|
private variables: { [key: string]: any } = {};
|
||||||
private iterators: { [key: string]: any } = {};
|
private iterators: { [key: string]: any } = {};
|
||||||
private _drunk: DrunkWalk = new DrunkWalk(-100, 100, false);
|
private _drunk: DrunkWalk = new DrunkWalk(-100, 100, false);
|
||||||
|
public randomGen = Math.random;
|
||||||
|
public currentSeed: string|undefined = undefined;
|
||||||
|
public localSeeds = new Map<string, Function>();
|
||||||
|
|
||||||
MidiConnection: MidiConnection = new MidiConnection();
|
MidiConnection: MidiConnection = new MidiConnection();
|
||||||
load: samples;
|
load: samples;
|
||||||
@ -105,6 +109,21 @@ export class UserAPI {
|
|||||||
return this.app._mouseY;
|
return this.app._mouseY;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public noteX = (): number => {
|
||||||
|
/**
|
||||||
|
* @returns The current x position scaled to 0-127 using screen width
|
||||||
|
*/
|
||||||
|
return Math.floor((this.app._mouseX / document.body.clientWidth) * 127);
|
||||||
|
};
|
||||||
|
|
||||||
|
public noteY = (): number => {
|
||||||
|
/**
|
||||||
|
* @returns The current y position scaled to 0-127 using screen height
|
||||||
|
*/
|
||||||
|
return Math.floor((this.app._mouseY / document.body.clientHeight) * 127);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
// Utility functions
|
// Utility functions
|
||||||
// =============================================================
|
// =============================================================
|
||||||
@ -179,9 +198,8 @@ export class UserAPI {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public note = (
|
public note = (
|
||||||
note: number,
|
value: number = 60
|
||||||
options: { [key: string]: number } = {}
|
): Note => {
|
||||||
): void => {
|
|
||||||
/**
|
/**
|
||||||
* Sends a MIDI note to the current MIDI output.
|
* Sends a MIDI note to the current MIDI output.
|
||||||
*
|
*
|
||||||
@ -189,10 +207,7 @@ export class UserAPI {
|
|||||||
* @param options - an object containing options for that note
|
* @param options - an object containing options for that note
|
||||||
* { channel: 0, velocity: 100, duration: 0.5 }
|
* { channel: 0, velocity: 100, duration: 0.5 }
|
||||||
*/
|
*/
|
||||||
const channel = options.channel ? options.channel : 0;
|
return new Note(value, this.app);
|
||||||
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 => {
|
public sysex = (data: Array<number>): void => {
|
||||||
@ -261,14 +276,13 @@ export class UserAPI {
|
|||||||
public zn = (
|
public zn = (
|
||||||
input: string,
|
input: string,
|
||||||
options: { [key: string]: string | number } = {}
|
options: { [key: string]: string | number } = {}
|
||||||
): Event => {
|
): Event|object => {
|
||||||
const pattern = cachedPattern(input, options);
|
const pattern = cachedPattern(input, options);
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
if (pattern.hasStarted()) {
|
if (pattern.hasStarted()) {
|
||||||
const event = pattern.peek();
|
const event = pattern.peek();
|
||||||
|
|
||||||
// Check if event is modified
|
// Check if event is modified
|
||||||
const node = event!.modifiedEvent ? event!.modifiedEvent : event;
|
const node = event.modifiedEvent ? event.modifiedEvent : event;
|
||||||
const channel = (options.channel ? options.channel : 0) as number;
|
const channel = (options.channel ? options.channel : 0) as number;
|
||||||
const velocity = (options.velocity ? options.velocity : 100) as number;
|
const velocity = (options.velocity ? options.velocity : 100) as number;
|
||||||
const sustain = (options.sustain ? options.sustain : 0.5) as number;
|
const sustain = (options.sustain ? options.sustain : 0.5) as number;
|
||||||
@ -299,7 +313,7 @@ export class UserAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove old modified event
|
// Remove old modified event
|
||||||
if (event!.modifiedEvent) event!.modifiedEvent = undefined;
|
if (event.modifiedEvent) event.modifiedEvent = undefined;
|
||||||
}
|
}
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
return pattern.next();
|
return pattern.next();
|
||||||
@ -577,7 +591,7 @@ export class UserAPI {
|
|||||||
*
|
*
|
||||||
* @param array - The array of values to pick from
|
* @param array - The array of values to pick from
|
||||||
*/
|
*/
|
||||||
return array[Math.floor(Math.random() * array.length)];
|
return array[Math.floor(this.randomGen() * array.length)];
|
||||||
};
|
};
|
||||||
|
|
||||||
seqbeat = <T>(...array: T[]): T => {
|
seqbeat = <T>(...array: T[]): T => {
|
||||||
@ -629,7 +643,7 @@ export class UserAPI {
|
|||||||
* @param max - The maximum value of the random number
|
* @param max - The maximum value of the random number
|
||||||
* @returns A random integer between min and max
|
* @returns A random integer between min and max
|
||||||
*/
|
*/
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
return Math.floor(this.randomGen() * (max - min + 1)) + min;
|
||||||
};
|
};
|
||||||
|
|
||||||
rand = (min: number, max: number): number => {
|
rand = (min: number, max: number): number => {
|
||||||
@ -640,11 +654,37 @@ export class UserAPI {
|
|||||||
* @param max - The maximum value of the random number
|
* @param max - The maximum value of the random number
|
||||||
* @returns A random float between min and max
|
* @returns A random float between min and max
|
||||||
*/
|
*/
|
||||||
return Math.random() * (max - min) + min;
|
return this.randomGen() * (max - min) + min;
|
||||||
};
|
};
|
||||||
|
irand = this.randI
|
||||||
rI = this.randI;
|
rI = this.randI;
|
||||||
r = this.rand;
|
r = this.rand;
|
||||||
|
|
||||||
|
seed = (seed: string | number): void => {
|
||||||
|
/**
|
||||||
|
* Seed the random numbers globally in UserAPI.
|
||||||
|
* @param seed - The seed to use
|
||||||
|
*/
|
||||||
|
if(typeof seed === "number") seed = seed.toString();
|
||||||
|
if(this.currentSeed!==seed) {
|
||||||
|
this.currentSeed = seed;
|
||||||
|
this.randomGen = seededRandom(seed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localSeededRandom = (seed: string | number): Function => {
|
||||||
|
if(typeof seed === "number") seed = seed.toString();
|
||||||
|
if(this.localSeeds.has(seed)) return this.localSeeds.get(seed) as Function;
|
||||||
|
const newSeededRandom = seededRandom(seed)
|
||||||
|
this.localSeeds.set(seed,newSeededRandom);
|
||||||
|
return newSeededRandom;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLocalSeed = (seed: string | number | undefined = undefined): void => {
|
||||||
|
if(seed) this.localSeeds.delete(seed.toString());
|
||||||
|
this.localSeeds.clear();
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
// Quantification functions
|
// Quantification functions
|
||||||
// =============================================================
|
// =============================================================
|
||||||
@ -748,7 +788,7 @@ export class UserAPI {
|
|||||||
*
|
*
|
||||||
* @returns True 10% of the time
|
* @returns True 10% of the time
|
||||||
*/
|
*/
|
||||||
return Math.random() > 0.9;
|
return this.randomGen() > 0.9;
|
||||||
};
|
};
|
||||||
|
|
||||||
public sometimes = (): boolean => {
|
public sometimes = (): boolean => {
|
||||||
@ -757,7 +797,7 @@ export class UserAPI {
|
|||||||
*
|
*
|
||||||
* @returns True 50% of the time
|
* @returns True 50% of the time
|
||||||
*/
|
*/
|
||||||
return Math.random() > 0.5;
|
return this.randomGen() > 0.5;
|
||||||
};
|
};
|
||||||
|
|
||||||
public rarely = (): boolean => {
|
public rarely = (): boolean => {
|
||||||
@ -766,7 +806,7 @@ export class UserAPI {
|
|||||||
*
|
*
|
||||||
* @returns True 25% of the time
|
* @returns True 25% of the time
|
||||||
*/
|
*/
|
||||||
return Math.random() > 0.75;
|
return this.randomGen() > 0.75;
|
||||||
};
|
};
|
||||||
|
|
||||||
public often = (): boolean => {
|
public often = (): boolean => {
|
||||||
@ -775,7 +815,7 @@ export class UserAPI {
|
|||||||
*
|
*
|
||||||
* @returns True 75% of the time
|
* @returns True 75% of the time
|
||||||
*/
|
*/
|
||||||
return Math.random() > 0.25;
|
return this.randomGen() > 0.25;
|
||||||
};
|
};
|
||||||
|
|
||||||
public almostAlways = (): boolean => {
|
public almostAlways = (): boolean => {
|
||||||
@ -784,7 +824,7 @@ export class UserAPI {
|
|||||||
*
|
*
|
||||||
* @returns True 90% of the time
|
* @returns True 90% of the time
|
||||||
*/
|
*/
|
||||||
return Math.random() > 0.1;
|
return this.randomGen() > 0.1;
|
||||||
};
|
};
|
||||||
|
|
||||||
public dice = (sides: number): number => {
|
public dice = (sides: number): number => {
|
||||||
@ -794,7 +834,7 @@ export class UserAPI {
|
|||||||
* @param sides - The number of sides on the dice
|
* @param sides - The number of sides on the dice
|
||||||
* @returns The value of a dice roll with n sides
|
* @returns The value of a dice roll with n sides
|
||||||
*/
|
*/
|
||||||
return Math.floor(Math.random() * sides) + 1;
|
return Math.floor(this.randomGen() * sides) + 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
@ -920,7 +960,7 @@ export class UserAPI {
|
|||||||
* @param p - The probability of returning true
|
* @param p - The probability of returning true
|
||||||
* @returns True p% of the time
|
* @returns True p% of the time
|
||||||
*/
|
*/
|
||||||
return Math.random() * 100 < p;
|
return this.randomGen() * 100 < p;
|
||||||
};
|
};
|
||||||
|
|
||||||
toss = (): boolean => {
|
toss = (): boolean => {
|
||||||
@ -934,7 +974,7 @@ export class UserAPI {
|
|||||||
* @see almostAlways
|
* @see almostAlways
|
||||||
* @see almostNever
|
* @see almostNever
|
||||||
*/
|
*/
|
||||||
return Math.random() > 0.5;
|
return this.randomGen() > 0.5;
|
||||||
};
|
};
|
||||||
|
|
||||||
min = (...values: number[]): number => {
|
min = (...values: number[]): number => {
|
||||||
@ -1212,7 +1252,7 @@ export class UserAPI {
|
|||||||
* @see sine
|
* @see sine
|
||||||
* @see noise
|
* @see noise
|
||||||
*/
|
*/
|
||||||
return Math.random() * 2 - 1;
|
return this.randomGen() * 2 - 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
|
|||||||
53
src/Event.ts
Normal file
53
src/Event.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { type Editor } from './main';
|
||||||
|
|
||||||
|
export class Event {
|
||||||
|
seedValue: string|undefined = undefined;
|
||||||
|
randomGen: Function = Math.random;
|
||||||
|
app: Editor;
|
||||||
|
|
||||||
|
constructor(app: Editor) {
|
||||||
|
this.app = app;
|
||||||
|
if(this.app.api.currentSeed) {
|
||||||
|
this.randomGen = this.app.api.randomGen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sometimesBy = (probability: number, func: Function): Event => {
|
||||||
|
if(this.randomGen() < probability) {
|
||||||
|
return this.modify(func);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
sometimes = (func: Function): Event => {
|
||||||
|
return this.sometimesBy(0.5, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
rarely = (func: Function): Event => {
|
||||||
|
return this.sometimesBy(0.1, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
often = (func: Function): Event => {
|
||||||
|
return this.sometimesBy(0.9, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
modify = (func: Function): Event => {
|
||||||
|
return func(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
seed = (value: string|number): Event => {
|
||||||
|
this.seedValue = value.toString();
|
||||||
|
this.randomGen = this.app.api.localSeededRandom(this.seedValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear = (): Event => {
|
||||||
|
this.app.api.clearLocalSeed(this.seedValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
apply = (func: Function): Event => {
|
||||||
|
return this.modify(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -30,6 +30,7 @@ export class MidiConnection{
|
|||||||
this.midiOutputs = Array.from(this.midiAccess.outputs.values());
|
this.midiOutputs = Array.from(this.midiAccess.outputs.values());
|
||||||
if (this.midiOutputs.length === 0) {
|
if (this.midiOutputs.length === 0) {
|
||||||
console.warn('No MIDI outputs available.');
|
console.warn('No MIDI outputs available.');
|
||||||
|
this.currentOutputIndex = -1;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize MIDI:', error);
|
console.error('Failed to initialize MIDI:', error);
|
||||||
@ -50,6 +51,20 @@ export class MidiConnection{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getCurrentMidiPortIndex(): number {
|
||||||
|
/**
|
||||||
|
* Returns the index of the currently selected MIDI output.
|
||||||
|
*
|
||||||
|
* @returns Index of the currently selected MIDI output or -1 if no MIDI output is selected or available.
|
||||||
|
*/
|
||||||
|
if(this.midiOutputs.length > 0 && this.currentOutputIndex >= 0 && this.currentOutputIndex < this.midiOutputs.length) {
|
||||||
|
return this.currentOutputIndex;
|
||||||
|
} else {
|
||||||
|
console.error('No MIDI output selected or available.');
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public sendMidiClock(): void {
|
public sendMidiClock(): void {
|
||||||
/**
|
/**
|
||||||
* Sends a single MIDI clock message to the currently selected MIDI output.
|
* Sends a single MIDI clock message to the currently selected MIDI output.
|
||||||
@ -69,16 +84,41 @@ export class MidiConnection{
|
|||||||
* @param outputName Name of the MIDI output to switch to
|
* @param outputName Name of the MIDI output to switch to
|
||||||
* @returns True if the MIDI output was found and switched to, false otherwise
|
* @returns True if the MIDI output was found and switched to, false otherwise
|
||||||
*/
|
*/
|
||||||
const index = this.midiOutputs.findIndex((output) => output.name === outputName);
|
const index = this.getMidiOutputIndex(outputName);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.currentOutputIndex = index;
|
this.currentOutputIndex = index;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
console.error(`MIDI output "${outputName}" not found.`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getMidiOutputIndex(output: string|number): number {
|
||||||
|
/**
|
||||||
|
* Returns the index of the MIDI output with the specified name.
|
||||||
|
*
|
||||||
|
* @param outputName Name of the MIDI output
|
||||||
|
* @returns Index of the new MIDI output or current output if new is not valid
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
if(typeof output === 'number') {
|
||||||
|
if (output < 0 || output >= this.midiOutputs.length) {
|
||||||
|
console.error(`Invalid MIDI output index. Index must be in the range 0-${this.midiOutputs.length - 1}.`);
|
||||||
|
return this.currentOutputIndex;
|
||||||
|
} else {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const index = this.midiOutputs.findIndex((o) => o.name === output);
|
||||||
|
if (index !== -1) {
|
||||||
|
return index;
|
||||||
|
} else {
|
||||||
|
console.error(`MIDI output "${output}" not found.`);
|
||||||
|
return this.currentOutputIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public listMidiOutputs(): void {
|
public listMidiOutputs(): void {
|
||||||
/**
|
/**
|
||||||
* Lists all available MIDI outputs to the console.
|
* Lists all available MIDI outputs to the console.
|
||||||
@ -89,7 +129,7 @@ export class MidiConnection{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendMidiNote(noteNumber: number, channel: number, velocity: number, duration: number): void {
|
public sendMidiNote(noteNumber: number, channel: number, velocity: number, duration: number, port: number|string = this.currentOutputIndex): void {
|
||||||
/**
|
/**
|
||||||
* Sending a MIDI Note on/off message with the same note number and channel. Automatically manages
|
* Sending a MIDI Note on/off message with the same note number and channel. Automatically manages
|
||||||
* the note off message after the specified duration.
|
* the note off message after the specified duration.
|
||||||
@ -100,7 +140,9 @@ export class MidiConnection{
|
|||||||
* @param duration Duration in milliseconds
|
* @param duration Duration in milliseconds
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const output = this.midiOutputs[this.currentOutputIndex];
|
|
||||||
|
if(typeof port === 'string') port = this.getMidiOutputIndex(port);
|
||||||
|
const output = this.midiOutputs[port];
|
||||||
noteNumber = Math.min(Math.max(noteNumber, 0), 127);
|
noteNumber = Math.min(Math.max(noteNumber, 0), 127);
|
||||||
if (output) {
|
if (output) {
|
||||||
const noteOnMessage = [0x90 + channel, noteNumber, velocity];
|
const noteOnMessage = [0x90 + channel, noteNumber, velocity];
|
||||||
|
|||||||
78
src/Note.ts
Normal file
78
src/Note.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { Event } from './Event';
|
||||||
|
import { type Editor } from './main';
|
||||||
|
import { MidiConnection } from "./IO/MidiConnection";
|
||||||
|
export class Note extends Event {
|
||||||
|
values: { [key: string]: any };
|
||||||
|
midiConnection: MidiConnection;
|
||||||
|
|
||||||
|
constructor(input: number|object, public app: Editor) {
|
||||||
|
super(app);
|
||||||
|
if(typeof input === 'number') input = { 'note': input };
|
||||||
|
this.values = input;
|
||||||
|
this.midiConnection = app.api.MidiConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
note = (value: number): this => {
|
||||||
|
this.values['note'] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel = (value: number): this => {
|
||||||
|
this.values['channel'] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
port = (value: number|string): this => {
|
||||||
|
this.values['port'] = this.midiConnection.getMidiOutputIndex(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
add = (value: number): this => {
|
||||||
|
this.values.note += value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
modify = (func: Function): this => {
|
||||||
|
const funcResult = func(this);
|
||||||
|
if(funcResult instanceof Object) return funcResult;
|
||||||
|
else {
|
||||||
|
func(this.values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add bend
|
||||||
|
freq = (value: number): this => {
|
||||||
|
this.values['freq'] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bend = (value: number): this => {
|
||||||
|
this.values['bend'] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
random = (min: number = 0, max: number = 127): this => {
|
||||||
|
min = Math.min(Math.max(min, 0), 127);
|
||||||
|
max = Math.min(Math.max(max, 0), 127);
|
||||||
|
this.values['note'] = Math.floor(this.randomGen() * (max - min + 1)) + min;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = (): void => {
|
||||||
|
const note = this.values.note ? this.values.note : 60;
|
||||||
|
const channel = this.values.channel ? this.values.channel : 0;
|
||||||
|
const velocity = this.values.velocity ? this.values.velocity : 100;
|
||||||
|
const duration = this.values.duration ? this.values.duration : 0.5;
|
||||||
|
const bend = this.values.bend ? this.values.bend : undefined;
|
||||||
|
|
||||||
|
const port = this.values.port ?
|
||||||
|
this.midiConnection.getMidiOutputIndex(this.values.port) :
|
||||||
|
this.midiConnection.getCurrentMidiPortIndex();
|
||||||
|
|
||||||
|
if (bend) this.midiConnection.sendPitchBend(bend, channel);
|
||||||
|
this.midiConnection.sendMidiNote(note, channel, velocity, duration, port);
|
||||||
|
if (bend) this.midiConnection.sendPitchBend(8192, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
101
src/Pattern.ts
101
src/Pattern.ts
@ -1,101 +0,0 @@
|
|||||||
export class Pattern {
|
|
||||||
|
|
||||||
events: Event[];
|
|
||||||
_current : Event | undefined = undefined;
|
|
||||||
|
|
||||||
constructor(values: number[]) {
|
|
||||||
this.events = values.map((value) => new Event(value));
|
|
||||||
this.buildLinks();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create links cyclic links between events
|
|
||||||
buildLinks(): void {
|
|
||||||
this.events.forEach((event, index) => {
|
|
||||||
event._next = index < this.events.length - 1 ? this.events[index + 1] : this.events[0];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current event for this pattern
|
|
||||||
current(): Event {
|
|
||||||
if(this._current) this._current = this._current.next();
|
|
||||||
else this._current = this.events[0];
|
|
||||||
return this._current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class Event {
|
|
||||||
/**
|
|
||||||
* Simple Event class with simple numerical value and link to next event
|
|
||||||
*/
|
|
||||||
_next!: Event;
|
|
||||||
_value: number;
|
|
||||||
// Used to store a modified version of the event
|
|
||||||
modifiedEvent: Event | undefined = undefined;
|
|
||||||
|
|
||||||
constructor(value: number) {
|
|
||||||
this._value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get value(): number {
|
|
||||||
if(this.modifiedEvent) return this.modifiedEvent._value;
|
|
||||||
return this._value;
|
|
||||||
}
|
|
||||||
|
|
||||||
add(value: number): Event {
|
|
||||||
if(!this.modifiedEvent) this.modifiedEvent = this.clone();
|
|
||||||
this.modifiedEvent._value += value;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
next() {
|
|
||||||
if(this.modifiedEvent) {
|
|
||||||
const next = this.modifiedEvent._next;
|
|
||||||
// Set modifiedEvent to undefined, cos we dont want to apply methods to earlier modified events
|
|
||||||
this.modifiedEvent = undefined;
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
return this._next;
|
|
||||||
}
|
|
||||||
|
|
||||||
clone(): Event {
|
|
||||||
const event = new Event(this._value);
|
|
||||||
event._next = this._next;
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple cache for patterns
|
|
||||||
let cache = new Map<string, Pattern>();
|
|
||||||
|
|
||||||
// Create a cache key from the values of a pattern somehow
|
|
||||||
const createCacheKey = (values: number[]) => values.join('-');
|
|
||||||
|
|
||||||
// Get a cached pattern or create a new one
|
|
||||||
const getCachedPattern = (values: number[]) => {
|
|
||||||
const key = createCacheKey(values);
|
|
||||||
const cachedPattern = cache.get(key);
|
|
||||||
if(cachedPattern) return cachedPattern;
|
|
||||||
const newPattern = new Pattern(values);
|
|
||||||
cache.set(key, newPattern);
|
|
||||||
return newPattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cached event function that includes the main logic
|
|
||||||
const cachedEvent = (values: number[]): Event => {
|
|
||||||
const pattern = getCachedPattern(values);
|
|
||||||
if(pattern._current) { console.log("Play: ", pattern._current.value) }
|
|
||||||
else { console.log("Current is undefined so just starting!") }
|
|
||||||
return pattern.current();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test it out
|
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
while(true) {
|
|
||||||
|
|
||||||
cachedEvent([1, 2, 3]).add(1).add(-2);
|
|
||||||
|
|
||||||
if(i++>10) break;
|
|
||||||
|
|
||||||
}
|
|
||||||
29
src/Sound.ts
29
src/Sound.ts
@ -1,18 +1,28 @@
|
|||||||
import { type Editor } from './main';
|
import { type Editor } from './main';
|
||||||
|
import { Event } from './Event';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
superdough,
|
superdough,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
} from "superdough";
|
} from "superdough";
|
||||||
|
|
||||||
export class Sound {
|
export class Sound extends Event {
|
||||||
|
|
||||||
values: { [key: string]: any }
|
values: { [key: string]: any }
|
||||||
|
|
||||||
constructor(sound: string, public app: Editor) {
|
constructor(sound: string|object, public app: Editor) {
|
||||||
this.values = { 's': sound }
|
super(app);
|
||||||
|
if (typeof sound === 'string') this.values = { 's': sound };
|
||||||
|
else this.values = sound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sound = (value: string): this => {
|
||||||
|
this.values['s'] = value
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
snd = this.sound;
|
||||||
|
|
||||||
unit = (value: number): this => {
|
unit = (value: number): this => {
|
||||||
this.values['unit'] = value
|
this.values['unit'] = value
|
||||||
return this;
|
return this;
|
||||||
@ -163,7 +173,16 @@ export class Sound {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
out = (): object => {
|
modify = (func: Function): this => {
|
||||||
return superdough(this.values, this.app.clock.pulse_duration);
|
const funcResult = func(this);
|
||||||
|
if(funcResult instanceof Object) return funcResult;
|
||||||
|
else {
|
||||||
|
func(this.values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out = (): void => {
|
||||||
|
superdough(this.values, this.app.clock.pulse_duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1441,10 +1441,10 @@ yaml@^2.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"
|
||||||
integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
|
integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
|
||||||
|
|
||||||
zifferjs@^0.0.8:
|
zifferjs@^0.0.9:
|
||||||
version "0.0.8"
|
version "0.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.8.tgz#4e165679f37d81f2a02399f617ddb3c7fc1738ba"
|
resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.9.tgz#47037cee6dd161838dd236bdbc3eda9b099e2281"
|
||||||
integrity sha512-yxRo+BVZiHDoZksLHtAgkE/e5qeRboj3jcx1DDmdr9zrQUGBea+WQzfeo0IOrFnzbN/D7A7g9Vy4acJ+1R6z6g==
|
integrity sha512-XS/JAc9nkmoiRaT/YFuX7r1ROvApQnY5BxOKyenAeDATvKZ80sIoXUw48U27KTsuJIsiPInNm5RieJGCJkoVmQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache "^10.0.0"
|
lru-cache "^10.0.0"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user