Merge pull request #81 from Bubobubobubobubo/polyphony
Adding global parametric polyphony
This commit is contained in:
34
src/API.ts
34
src/API.ts
@ -9,7 +9,7 @@ import { tryEvaluate, evaluateOnce } from "./Evaluator";
|
|||||||
import { DrunkWalk } from "./Utils/Drunk";
|
import { DrunkWalk } from "./Utils/Drunk";
|
||||||
import { Editor } from "./main";
|
import { Editor } from "./main";
|
||||||
import { SoundEvent } from "./classes/SoundEvent";
|
import { SoundEvent } from "./classes/SoundEvent";
|
||||||
import { MidiEvent } from "./classes/MidiEvent";
|
import { MidiEvent, MidiParams } from "./classes/MidiEvent";
|
||||||
import { LRUCache } from "lru-cache";
|
import { LRUCache } from "lru-cache";
|
||||||
import { InputOptions, Player } from "./classes/ZPlayer";
|
import { InputOptions, Player } from "./classes/ZPlayer";
|
||||||
import {
|
import {
|
||||||
@ -28,6 +28,7 @@ import {
|
|||||||
import { Speaker } from "./StringExtensions";
|
import { Speaker } from "./StringExtensions";
|
||||||
import { getScaleNotes } from "zifferjs";
|
import { getScaleNotes } from "zifferjs";
|
||||||
import { OscilloscopeConfig, blinkScript } from "./AudioVisualisation";
|
import { OscilloscopeConfig, blinkScript } from "./AudioVisualisation";
|
||||||
|
import { SkipEvent } from './classes/SkipEvent';
|
||||||
|
|
||||||
interface ControlChange {
|
interface ControlChange {
|
||||||
channel: number;
|
channel: number;
|
||||||
@ -390,9 +391,10 @@ export class UserAPI {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public midi = (
|
public midi = (
|
||||||
value: number | object = 60,
|
value: number | number[] = 60,
|
||||||
velocity?: number,
|
velocity?: number | number[],
|
||||||
channel?: number
|
channel?: number | number[],
|
||||||
|
port?: number | string | number[] | string[]
|
||||||
): MidiEvent => {
|
): MidiEvent => {
|
||||||
/**
|
/**
|
||||||
* Sends a MIDI note to the current MIDI output.
|
* Sends a MIDI note to the current MIDI output.
|
||||||
@ -402,24 +404,9 @@ export class UserAPI {
|
|||||||
* { channel: 0, velocity: 100, duration: 0.5 }
|
* { channel: 0, velocity: 100, duration: 0.5 }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (velocity !== undefined) {
|
const event = {note: value, velocity, channel, port} as MidiParams
|
||||||
// Check if value is of type number
|
|
||||||
if (typeof value === "number") {
|
|
||||||
value = { note: value };
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
value["velocity"] = velocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channel !== undefined) {
|
return new MidiEvent(event, this.app);
|
||||||
if (typeof value === "number") {
|
|
||||||
value = { note: value };
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
value["channel"] = channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MidiEvent(value, this.app);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public sysex = (data: Array<number>): void => {
|
public sysex = (data: Array<number>): void => {
|
||||||
@ -1893,8 +1880,9 @@ export class UserAPI {
|
|||||||
// Trivial functions
|
// Trivial functions
|
||||||
// =============================================================
|
// =============================================================
|
||||||
|
|
||||||
sound = (sound: string | object) => {
|
sound = (sound: string | string[] | null | undefined) => {
|
||||||
return new SoundEvent(sound, this.app);
|
if(sound) return new SoundEvent(sound, this.app);
|
||||||
|
else return new SkipEvent();
|
||||||
};
|
};
|
||||||
|
|
||||||
snd = this.sound;
|
snd = this.sound;
|
||||||
|
|||||||
71
src/Utils/Generic.ts
Normal file
71
src/Utils/Generic.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Transforms object with arrays into array of objects
|
||||||
|
*
|
||||||
|
* @param {Record<string, any>} input - Object with arrays
|
||||||
|
* @param {string[]} ignoredKeys - Keys to ignore
|
||||||
|
* @returns {Record<string, any>[]} Array of objects
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function objectWithArraysToArrayOfObjects(input: Record<string, any>, ignoredKeys: string[]): Record<string, any>[] {
|
||||||
|
ignoredKeys = ignoredKeys.map((k) => Array.isArray(input[k]) ? undefined : k).filter((k) => k !== undefined) as string[];
|
||||||
|
const keys = Object.keys(input).filter((k) => !ignoredKeys.includes(k));
|
||||||
|
const maxLength = Math.max(
|
||||||
|
...keys.map((k) =>
|
||||||
|
Array.isArray(input[k]) ? (input[k] as any[]).length : 1
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const output: Record<string, any>[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < maxLength; i++) {
|
||||||
|
const event: Record<string, any> = {};
|
||||||
|
for (const k of keys) {
|
||||||
|
if (ignoredKeys.includes(k)) {
|
||||||
|
event[k] = input[k];
|
||||||
|
} else {
|
||||||
|
if (Array.isArray(input[k])) {
|
||||||
|
event[k] = (input[k] as any[])[i % (input[k] as any[]).length];
|
||||||
|
} else {
|
||||||
|
event[k] = input[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.push(event);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transforms array of objects into object with arrays
|
||||||
|
*
|
||||||
|
* @param {Record<string, any>[]} array - Array of objects
|
||||||
|
* @param {Record<string, any>} mergeObject - Object that is merged to each object in the array
|
||||||
|
* @returns {object} Merged object with arrays
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function arrayOfObjectsToObjectWithArrays<T extends Record<string, any>>(array: T[], mergeObject: Record<string, any> = {}): Record<string, any> {
|
||||||
|
return array.reduce((acc, obj) => {
|
||||||
|
Object.keys(mergeObject).forEach((key) => {
|
||||||
|
obj[key as keyof T] = mergeObject[key];
|
||||||
|
});
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
if (!acc[key as keyof T]) {
|
||||||
|
acc[key as keyof T] = [];
|
||||||
|
}
|
||||||
|
(acc[key as keyof T] as unknown[]).push(obj[key]);
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<keyof T, any[]>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Filter certain keys from object
|
||||||
|
*
|
||||||
|
* @param {Record<string, any>} obj - Object to filter
|
||||||
|
* @param {string[]} filter - Keys to filter
|
||||||
|
* @returns {object} Filtered object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function filterObject(obj: Record<string, any>, filter: string[]): Record<string, any> {
|
||||||
|
return Object.fromEntries(Object.entries(obj).filter(([key]) => filter.includes(key)));
|
||||||
|
}
|
||||||
@ -2,9 +2,7 @@ import { type Editor } from "../main";
|
|||||||
import {
|
import {
|
||||||
freqToMidi,
|
freqToMidi,
|
||||||
resolvePitchBend,
|
resolvePitchBend,
|
||||||
getScale,
|
safeScale
|
||||||
isScale,
|
|
||||||
parseScala,
|
|
||||||
} from "zifferjs";
|
} from "zifferjs";
|
||||||
|
|
||||||
export abstract class Event {
|
export abstract class Event {
|
||||||
@ -204,8 +202,20 @@ export abstract class Event {
|
|||||||
return this.modify(func);
|
return this.modify(func);
|
||||||
};
|
};
|
||||||
|
|
||||||
length = (value: number): Event => {
|
noteLength = (value: number | number[], ...kwargs: number[]): Event => {
|
||||||
this.values["length"] = value;
|
/**
|
||||||
|
* This function is used to set the note length of the Event.
|
||||||
|
*/
|
||||||
|
if(kwargs.length > 0) {
|
||||||
|
value = (Array.isArray(value) ? value.concat(kwargs) : [value, ...kwargs]);
|
||||||
|
}
|
||||||
|
if(Array.isArray(value)) {
|
||||||
|
this.values["noteLength"] = value;
|
||||||
|
this.values.dur = value.map((v) => this.app.clock.convertPulseToSecond(v*4*this.app.clock.ppqn));
|
||||||
|
} else {
|
||||||
|
this.values["noteLength"] = value;
|
||||||
|
this.values.dur = this.app.clock.convertPulseToSecond(value*4*this.app.clock.ppqn);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -215,37 +225,101 @@ export abstract class AudibleEvent extends Event {
|
|||||||
super(app);
|
super(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
octave = (value: number): this => {
|
pitch = (value: number | number[], ...kwargs: number[]): this => {
|
||||||
this.values["octave"] = value;
|
/*
|
||||||
this.update();
|
* This function is used to set the pitch of the Event.
|
||||||
return this;
|
* @param value - The pitch value
|
||||||
};
|
* @returns The Event
|
||||||
|
*/
|
||||||
key = (value: string): this => {
|
if(kwargs.length > 0) {
|
||||||
this.values["key"] = value;
|
value = (Array.isArray(value) ? value.concat(kwargs) : [value, ...kwargs]);
|
||||||
this.update();
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
scale = (value: string): this => {
|
|
||||||
if (!isScale(value)) {
|
|
||||||
this.values.parsedScale = parseScala(value) as number[];
|
|
||||||
} else {
|
|
||||||
this.values.scaleName = value;
|
|
||||||
this.values.parsedScale = getScale(value) as number[];
|
|
||||||
}
|
}
|
||||||
this.update();
|
this.values["pitch"] = value;
|
||||||
|
if(this.values.key && this.values.parsedScale) this.update();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
pc = this.pitch;
|
||||||
|
|
||||||
|
octave = (value: number | number[], ...kwargs: number[]): this => {
|
||||||
|
/*
|
||||||
|
* This function is used to set the octave of the Event.
|
||||||
|
* @param value - The octave value
|
||||||
|
* @returns The Event
|
||||||
|
*/
|
||||||
|
if(kwargs.length > 0) {
|
||||||
|
value = (Array.isArray(value) ? value.concat(kwargs) : [value, ...kwargs]);
|
||||||
|
}
|
||||||
|
this.values["octave"] = value;
|
||||||
|
if(this.values.key && (this.values.pitch || this.values.pitch === 0) && this.values.parsedScale) this.update();
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
freq = (value: number): this => {
|
key = (value: string | string[], ...kwargs: string[]): this => {
|
||||||
|
/*
|
||||||
|
* This function is used to set the key of the Event.
|
||||||
|
* @param value - The key value
|
||||||
|
* @returns The Event
|
||||||
|
*/
|
||||||
|
if(kwargs.length > 0) {
|
||||||
|
value = (Array.isArray(value) ? value.concat(kwargs) : [value, ...kwargs]);
|
||||||
|
}
|
||||||
|
this.values["key"] = value;
|
||||||
|
if((this.values.pitch || this.values.pitch === 0) && this.values.parsedScale) this.update();
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
scale = (value: string | number | (number|string)[], ...kwargs: (string|number)[]): this => {
|
||||||
|
/*
|
||||||
|
* This function is used to set the scale of the Event.
|
||||||
|
* @param value - The scale value
|
||||||
|
* @returns The Event
|
||||||
|
*/
|
||||||
|
if(kwargs.length > 0) {
|
||||||
|
value = (Array.isArray(value) ? value.concat(kwargs) : [value, ...kwargs]);
|
||||||
|
}
|
||||||
|
if (typeof value === "string" || typeof value === "number") {
|
||||||
|
this.values.parsedScale = safeScale(value) as number[];
|
||||||
|
} else if(Array.isArray(value)) {
|
||||||
|
this.values.parsedScale = value.map((v) => safeScale(v));
|
||||||
|
}
|
||||||
|
if(this.values.key && (this.values.pitch || this.values.pitch === 0)) {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
freq = (value: number | number[], ...kwargs: number[]): this => {
|
||||||
|
/*
|
||||||
|
* This function is used to set the frequency of the Event.
|
||||||
|
* @param value - The frequency value
|
||||||
|
* @returns The Event
|
||||||
|
*/
|
||||||
|
if(kwargs.length > 0) {
|
||||||
|
value = (Array.isArray(value) ? value.concat(kwargs) : [value, ...kwargs]);
|
||||||
|
}
|
||||||
this.values["freq"] = value;
|
this.values["freq"] = value;
|
||||||
const midiNote = freqToMidi(value);
|
if(Array.isArray(value)) {
|
||||||
if (midiNote % 1 !== 0) {
|
this.values["note"] = [];
|
||||||
this.values["note"] = Math.floor(midiNote);
|
this.values["bend"] = [];
|
||||||
this.values["bend"] = resolvePitchBend(midiNote)[1];
|
for(const v of value) {
|
||||||
|
const midiNote = freqToMidi(v);
|
||||||
|
if (midiNote % 1 !== 0) {
|
||||||
|
this.values["note"].push(Math.floor(midiNote));
|
||||||
|
this.values["bend"].push(resolvePitchBend(midiNote)[1]);
|
||||||
|
} else {
|
||||||
|
this.values["note"].push(midiNote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(this.values.bend.length === 0) delete this.values.bend;
|
||||||
} else {
|
} else {
|
||||||
this.values["note"] = midiNote;
|
const midiNote = freqToMidi(value);
|
||||||
|
if (midiNote % 1 !== 0) {
|
||||||
|
this.values["note"] = Math.floor(midiNote);
|
||||||
|
this.values["bend"] = resolvePitchBend(midiNote)[1];
|
||||||
|
} else {
|
||||||
|
this.values["note"] = midiNote;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { AudibleEvent } from "./AbstractEvents";
|
import { AudibleEvent } from "./AbstractEvents";
|
||||||
import { type Editor } from "../main";
|
import { type Editor } from "../main";
|
||||||
import { MidiConnection } from "../IO/MidiConnection";
|
import { MidiConnection } from "../IO/MidiConnection";
|
||||||
import { midiToFreq, noteFromPc } from "zifferjs";
|
import { noteFromPc, chord as parseChord } from "zifferjs";
|
||||||
|
import { filterObject, arrayOfObjectsToObjectWithArrays, objectWithArraysToArrayOfObjects } from "../Utils/Generic";
|
||||||
|
|
||||||
export type MidiParams = {
|
export type MidiParams = {
|
||||||
note: number;
|
note: number;
|
||||||
@ -9,40 +10,49 @@ export type MidiParams = {
|
|||||||
channel?: number;
|
channel?: number;
|
||||||
port?: number;
|
port?: number;
|
||||||
sustain?: number;
|
sustain?: number;
|
||||||
|
velocity?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MidiEvent extends AudibleEvent {
|
export class MidiEvent extends AudibleEvent {
|
||||||
midiConnection: MidiConnection;
|
midiConnection: MidiConnection;
|
||||||
|
|
||||||
constructor(input: number | object, public app: Editor) {
|
constructor(input: MidiParams, public app: Editor) {
|
||||||
super(app);
|
super(app);
|
||||||
if (typeof input === "number") this.values["note"] = input;
|
this.values = input;
|
||||||
else this.values = input;
|
|
||||||
this.midiConnection = app.api.MidiConnection;
|
this.midiConnection = app.api.MidiConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
chord = (value: MidiParams[]): this => {
|
public chord = (value: string) => {
|
||||||
this.values["chord"] = value;
|
this.values.note = parseChord(value);
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
note = (value: number): this => {
|
note = (value: number | number[]): this => {
|
||||||
this.values["note"] = value;
|
this.values["note"] = value;
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
sustain = (value: number): this => {
|
sustain = (value: number | number[]): this => {
|
||||||
this.values["sustain"] = value;
|
this.values["sustain"] = value;
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
channel = (value: number): this => {
|
velocity = (value: number | number[]): this => {
|
||||||
|
this.values["velocity"] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel = (value: number | number[]): this => {
|
||||||
this.values["channel"] = value;
|
this.values["channel"] = value;
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
port = (value: number | string): this => {
|
port = (value: number | string | number[] | string[]): this => {
|
||||||
this.values["port"] = this.midiConnection.getMidiOutputIndex(value);
|
if(typeof value === "string"){
|
||||||
|
this.values["port"] = this.midiConnection.getMidiOutputIndex(value);
|
||||||
|
} else if(Array.isArray(value)){
|
||||||
|
this.values["port"] = value.map((v) => typeof v === "string" ? this.midiConnection.getMidiOutputIndex(v) : v);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,36 +85,45 @@ export class MidiEvent extends AudibleEvent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
update = (): void => {
|
update = (): void => {
|
||||||
const [note, bend] = noteFromPc(
|
// Get key, pitch, parsedScale and octave from this.values object
|
||||||
this.values.key || "C4",
|
const filteredValues = filterObject(this.values, ["key", "pitch", "parsedScale", "octave"]);
|
||||||
this.values.pitch || 0,
|
|
||||||
this.values.parsedScale || "MAJOR",
|
const events = objectWithArraysToArrayOfObjects(filteredValues,["parsedScale"]);
|
||||||
this.values.octave || 0
|
|
||||||
);
|
events.forEach((event) => {
|
||||||
this.values.note = note;
|
const [note, bend] = noteFromPc(
|
||||||
this.values.freq = midiToFreq(note);
|
event.key as number || "C4",
|
||||||
if (bend) this.values.bend = bend;
|
event.pitch as number || 0,
|
||||||
|
event.parsedScale as number[] || event.scale || "MAJOR",
|
||||||
|
event.octave as number || 0
|
||||||
|
);
|
||||||
|
event.note = note;
|
||||||
|
if(bend) event.bend = bend;
|
||||||
|
});
|
||||||
|
|
||||||
|
const newArrays = arrayOfObjectsToObjectWithArrays(events) as MidiParams;
|
||||||
|
|
||||||
|
this.values.note = newArrays.note;
|
||||||
|
if(newArrays.bend) this.values.bend = newArrays.bend;
|
||||||
};
|
};
|
||||||
|
|
||||||
out = (): void => {
|
out = (): void => {
|
||||||
function play(event: MidiEvent, params?: MidiParams): void {
|
function play(event: MidiEvent, params: MidiParams): void {
|
||||||
const paramChannel = params && params.channel ? params.channel : 0;
|
const channel = params.channel ? params.channel : 0;
|
||||||
const channel = event.values.channel ? event.values.channel : paramChannel;
|
const velocity = params.velocity ? params.velocity : 100;
|
||||||
const velocity = event.values.velocity ? event.values.velocity : 100;
|
const note = params.note ? params.note : 60;
|
||||||
const paramNote = params && params.note ? params.note : 60;
|
|
||||||
const note = event.values.note ? event.values.note : paramNote;
|
|
||||||
|
|
||||||
const sustain = event.values.sustain
|
const sustain = params.sustain
|
||||||
? event.values.sustain *
|
? params.sustain *
|
||||||
event.app.clock.pulse_duration *
|
event.app.clock.pulse_duration *
|
||||||
event.app.api.ppqn()
|
event.app.api.ppqn()
|
||||||
: event.app.clock.pulse_duration * event.app.api.ppqn();
|
: event.app.clock.pulse_duration * event.app.api.ppqn();
|
||||||
|
|
||||||
const bend = event.values.bend ? event.values.bend : undefined;
|
const bend = params.bend ? params.bend : undefined;
|
||||||
|
|
||||||
const port = event.values.port
|
const port = params.port
|
||||||
? event.midiConnection.getMidiOutputIndex(event.values.port)
|
? event.midiConnection.getMidiOutputIndex(params.port)
|
||||||
: event.midiConnection.getCurrentMidiPortIndex();
|
: event.midiConnection.getCurrentMidiPortIndex() || 0;
|
||||||
|
|
||||||
event.midiConnection.sendMidiNote(
|
event.midiConnection.sendMidiNote(
|
||||||
note,
|
note,
|
||||||
@ -116,13 +135,11 @@ export class MidiEvent extends AudibleEvent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.values.chord) {
|
const events = objectWithArraysToArrayOfObjects(this.values,["parsedScale"]) as MidiParams[];
|
||||||
this.values.chord.forEach((p: MidiParams) => {
|
|
||||||
play(this, p);
|
events.forEach((p: MidiParams) => {
|
||||||
});
|
play(this,p);
|
||||||
} else {
|
});
|
||||||
play(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,11 @@ import { Event } from "./AbstractEvents";
|
|||||||
export class RestEvent extends Event {
|
export class RestEvent extends Event {
|
||||||
constructor(length: number, app: Editor) {
|
constructor(length: number, app: Editor) {
|
||||||
super(app);
|
super(app);
|
||||||
this.values["length"] = length;
|
this.values["noteLength"] = length;
|
||||||
}
|
}
|
||||||
|
|
||||||
_fallbackMethod = (): Event => {
|
_fallbackMethod = (): Event => {
|
||||||
return RestEvent.createRestProxy(this.values["length"], this.app);
|
return RestEvent.createRestProxy(this.values["noteLength"], this.app);
|
||||||
};
|
};
|
||||||
|
|
||||||
public static createRestProxy = (
|
public static createRestProxy = (
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { type Editor } from "../main";
|
import { type Editor } from "../main";
|
||||||
import { AudibleEvent } from "./AbstractEvents";
|
import { AudibleEvent } from "./AbstractEvents";
|
||||||
|
import { filterObject, arrayOfObjectsToObjectWithArrays, objectWithArraysToArrayOfObjects } from "../Utils/Generic";
|
||||||
import {
|
import {
|
||||||
chord as parseChord,
|
chord as parseChord,
|
||||||
midiToFreq,
|
midiToFreq,
|
||||||
@ -13,328 +14,375 @@ import {
|
|||||||
} from "superdough";
|
} from "superdough";
|
||||||
|
|
||||||
export type SoundParams = {
|
export type SoundParams = {
|
||||||
dur: number;
|
dur: number | number[];
|
||||||
s?: string;
|
s?: undefined | string | string[];
|
||||||
|
n?: undefined | number | number[];
|
||||||
|
analyze?: boolean;
|
||||||
|
note?: number | number[];
|
||||||
|
freq?: number | number[];
|
||||||
|
pitch?: number | number[];
|
||||||
|
key?: string;
|
||||||
|
scale?: string;
|
||||||
|
parsedScale?: number[];
|
||||||
|
octave?: number | number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export class SoundEvent extends AudibleEvent {
|
export class SoundEvent extends AudibleEvent {
|
||||||
nudge: number;
|
nudge: number;
|
||||||
|
sound: any;
|
||||||
|
|
||||||
constructor(sound: string | object, public app: Editor) {
|
private methodMap = {
|
||||||
|
volume: ["volume", "vol"],
|
||||||
|
zrand: ["zrand", "zr"],
|
||||||
|
curve: ["curve"],
|
||||||
|
slide: ["slide", "sld"],
|
||||||
|
deltaSlide: ["deltaSlide", "dslide"],
|
||||||
|
pitchJump: ["pitchJump", "pj"],
|
||||||
|
pitchJumpTime: ["pitchJumpTime", "pjt"],
|
||||||
|
lfo: ["lfo"],
|
||||||
|
znoise: ["znoise"],
|
||||||
|
noise: ["noise"],
|
||||||
|
zmod: ["zmod"],
|
||||||
|
zcrush: ["zcrush"],
|
||||||
|
zdelay: ["zdelay"],
|
||||||
|
sustainVolume: ["sustainVolume"],
|
||||||
|
tremolo: ["tremolo"],
|
||||||
|
dur: ["dur"],
|
||||||
|
zzfx: ["zzfx"],
|
||||||
|
fmi: ["fmi"],
|
||||||
|
fmh: ["fmh"],
|
||||||
|
fmenv: ["fmenv"],
|
||||||
|
fmattack: ["fmattack", "fmatk"],
|
||||||
|
fmdecay: ["fmdecay", "fmdec"],
|
||||||
|
fmsustain: ["fmsustain", "fmsus"],
|
||||||
|
fmrelease: ["fmrelease", "fmrel"],
|
||||||
|
fmvelocity: ["fmvelocity", "fmvel"],
|
||||||
|
fmwave: ["fmwave", "fmw"],
|
||||||
|
fmadsr: (a: number, d: number, s: number, r: number) => {
|
||||||
|
this.updateValue("fmattack", a);
|
||||||
|
this.updateValue("fmdecay", d);
|
||||||
|
this.updateValue("fmsustain", s);
|
||||||
|
this.updateValue("fmrelease", r);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
fmad: (a: number, d: number) => {
|
||||||
|
this.updateValue("fmattack", a);
|
||||||
|
this.updateValue("fmdecay", d);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
ftype: ["ftype"],
|
||||||
|
fanchor: ["fanchor"],
|
||||||
|
attack: ["attack", "atk"],
|
||||||
|
decay: ["decay", "dec"],
|
||||||
|
sustain: ["sustain", "sus"],
|
||||||
|
release: ["release", "rel"],
|
||||||
|
adsr: (a: number, d: number, s: number, r: number) => {
|
||||||
|
this.updateValue("attack", a);
|
||||||
|
this.updateValue("decay", d);
|
||||||
|
this.updateValue("sustain", s);
|
||||||
|
this.updateValue("release", r);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
ad: (a: number, d: number) => {
|
||||||
|
this.updateValue("attack", a);
|
||||||
|
this.updateValue("decay", d);
|
||||||
|
this.updateValue("sustain", 0.0);
|
||||||
|
this.updateValue("release", 0.0);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
lpenv: ["lpenv", "lpe"],
|
||||||
|
lpattack: ["lpattack", "lpa"],
|
||||||
|
lpdecay: ["lpdecay", "lpd"],
|
||||||
|
lpsustain: ["lpsustain", "lps"],
|
||||||
|
lprelease: ["lprelease", "lpr"],
|
||||||
|
cutoff: (value: number, resonance?: number) => {
|
||||||
|
this.updateValue("cutoff", value);
|
||||||
|
if (resonance) {
|
||||||
|
this.updateValue("resonance", resonance);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
lpf: (value: number, resonance?: number) => {
|
||||||
|
this.updateValue("cutoff", value);
|
||||||
|
if (resonance) {
|
||||||
|
this.updateValue("resonance", resonance);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
resonance: (value: number) => {
|
||||||
|
if (value >= 0 && value <= 1) {
|
||||||
|
this.updateValue("resonance", 50 * value);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
lpadsr: (depth: number, a: number, d: number, s: number, r: number) => {
|
||||||
|
this.updateValue("lpenv", depth);
|
||||||
|
this.updateValue("lpattack", a);
|
||||||
|
this.updateValue("lpdecay", d);
|
||||||
|
this.updateValue("lpsustain", s);
|
||||||
|
this.updateValue("lprelease", r);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
lpad: (depth: number, a: number, d: number) => {
|
||||||
|
this.updateValue("lpenv", depth);
|
||||||
|
this.updateValue("lpattack", a);
|
||||||
|
this.updateValue("lpdecay", d);
|
||||||
|
this.updateValue("lpsustain", 0);
|
||||||
|
this.updateValue("lprelease", 0);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
hpenv: ["hpenv", "hpe"],
|
||||||
|
hpattack: ["hpattack", "hpa"],
|
||||||
|
hpdecay: ["hpdecay", "hpd"],
|
||||||
|
hpsustain: ["hpsustain", "hpsus"],
|
||||||
|
hprelease: ["hprelease", "hpr"],
|
||||||
|
hcutoff: (value: number, resonance?: number) => {
|
||||||
|
this.updateValue("hcutoff", value);
|
||||||
|
if (resonance) {
|
||||||
|
this.updateValue("hresonance", resonance);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
hpq: (value: number) => {
|
||||||
|
this.updateValue("hresonance", value);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
hpadsr: (depth: number, a: number, d: number, s: number, r: number) => {
|
||||||
|
this.updateValue("hpenv", depth);
|
||||||
|
this.updateValue("hpattack", a);
|
||||||
|
this.updateValue("hpdecay", d);
|
||||||
|
this.updateValue("hpsustain", s);
|
||||||
|
this.updateValue("hprelease", r);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
hpad: (depth: number, a: number, d: number) => {
|
||||||
|
this.updateValue("hpenv", depth);
|
||||||
|
this.updateValue("hpattack", a);
|
||||||
|
this.updateValue("hpdecay", d);
|
||||||
|
this.updateValue("hpsustain", 0);
|
||||||
|
this.updateValue("hprelease", 0);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
bpenv: ["bpenv", "bpe"],
|
||||||
|
bpattack: ["bpattack", "bpa"],
|
||||||
|
bpdecay: ["bpdecay", "bpd"],
|
||||||
|
bpsustain: ["bpsustain", "bps"],
|
||||||
|
bprelease: ["bprelease", "bpr"],
|
||||||
|
bandf: (value: number, resonance?: number) => {
|
||||||
|
this.updateValue("bandf", value);
|
||||||
|
if (resonance) {
|
||||||
|
this.updateValue("bandq", resonance);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
bpf: (value: number, resonance?: number) => {
|
||||||
|
this.updateValue("bandf", value);
|
||||||
|
if (resonance) {
|
||||||
|
this.updateValue("bandq", resonance);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
bandq: ["bandq", "bpq"],
|
||||||
|
bpadsr: (depth: number, a: number, d: number, s: number, r: number) => {
|
||||||
|
this.updateValue("bpenv", depth);
|
||||||
|
this.updateValue("bpattack", a);
|
||||||
|
this.updateValue("bpdecay", d);
|
||||||
|
this.updateValue("bpsustain", s);
|
||||||
|
this.updateValue("bprelease", r);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
bpad: (depth: number, a: number, d: number) => {
|
||||||
|
this.updateValue("bpenv", depth);
|
||||||
|
this.updateValue("bpattack", a);
|
||||||
|
this.updateValue("bpdecay", d);
|
||||||
|
this.updateValue("bpsustain", 0);
|
||||||
|
this.updateValue("bprelease", 0);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
vib: ["vib"],
|
||||||
|
vibmod: ["vibmod"],
|
||||||
|
fm: (value: number | string) => {
|
||||||
|
if (typeof value === "number") {
|
||||||
|
this.values["fmi"] = value;
|
||||||
|
} else {
|
||||||
|
let values = value.split(":");
|
||||||
|
this.values["fmi"] = parseFloat(values[0]);
|
||||||
|
if (values.length > 1) this.values["fmh"] = parseFloat(values[1]);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
loop: ["loop"],
|
||||||
|
loopBegin: ["loopBegin", "loopb"],
|
||||||
|
loopEnd: ["loopEnd", "loope"],
|
||||||
|
begin: ["begin"],
|
||||||
|
end: ["end"],
|
||||||
|
gain: ["gain"],
|
||||||
|
dbgain: (value: number) => {
|
||||||
|
this.updateValue("gain", Math.min(Math.pow(10, value / 20), 10));
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
db: (value: number) => {
|
||||||
|
this.updateValue("gain", Math.min(Math.pow(10, value / 20), 10));
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
velocity: ["velocity", "vel"],
|
||||||
|
pan: ["pan"],
|
||||||
|
cut: ["cut"],
|
||||||
|
clip: ["clip"],
|
||||||
|
n: ["n"],
|
||||||
|
speed: ["speed", "spd"],
|
||||||
|
coarse: ["coarse"],
|
||||||
|
crush: ["crush"],
|
||||||
|
shape: ["shape"],
|
||||||
|
vowel: ["vowel", "vow"],
|
||||||
|
delay: ["delay", "del"],
|
||||||
|
delayfeedback: ["delayfeedback", "delayfb"],
|
||||||
|
delaytime: ["delaytime", "delayt"],
|
||||||
|
orbit: ["orbit", "o"],
|
||||||
|
room: ["room", "rm"],
|
||||||
|
roomfade: ["roomfade", "rfade"],
|
||||||
|
roomlp: ["roomlp", "rlp"],
|
||||||
|
roomdim: ["roomdim", "rdim"],
|
||||||
|
sound: ["sound", "s"],
|
||||||
|
size: (value: number) => {
|
||||||
|
this.updateValue("roomsize", value);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
sz: (value: number) => {
|
||||||
|
this.updateValue("roomsize", value);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
comp: ["compressor", "cmp"],
|
||||||
|
ratio: (value: number) => {
|
||||||
|
this.updateValue("compressorRatio", value);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
knee: (value: number) => {
|
||||||
|
this.updateValue("compressorKnee", value);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
compAttack: (value: number) => {
|
||||||
|
this.updateValue("compressorAttack", value);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
compRelease: (value: number) => {
|
||||||
|
this.updateValue("compressorRelease", value);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
stretch: (beat: number) => {
|
||||||
|
this.updateValue("unit", "c");
|
||||||
|
this.updateValue("speed", 1 / beat);
|
||||||
|
this.updateValue("cut", beat);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
constructor(sound: string | string[] | SoundParams, public app: Editor) {
|
||||||
super(app);
|
super(app);
|
||||||
this.nudge = app.dough_nudge / 100;
|
this.nudge = app.dough_nudge / 100;
|
||||||
if (typeof sound === "string") {
|
|
||||||
|
for (const [methodName, keys] of Object.entries(this.methodMap)) {
|
||||||
|
if (Symbol.iterator in Object(keys)) {
|
||||||
|
for (const key of keys as string[]) {
|
||||||
|
// @ts-ignore
|
||||||
|
this[key] = (value: number) => this.updateValue(keys[0], value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
this[methodName] = keys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.values = this.processSound(sound);
|
||||||
|
}
|
||||||
|
|
||||||
|
private processSound = (sound: string | string[] | SoundParams | SoundParams[]): SoundParams => {
|
||||||
|
if (Array.isArray(sound) && typeof sound[0] === 'string') {
|
||||||
|
const s: string[] = [];
|
||||||
|
const n: number[] = [];
|
||||||
|
sound.forEach(str => {
|
||||||
|
const parts = (str as string).split(":");
|
||||||
|
s.push(parts[0]);
|
||||||
|
if (parts[1]) {
|
||||||
|
n.push(parseInt(parts[1]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
s,
|
||||||
|
n: n.length > 0 ? n : undefined,
|
||||||
|
dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn),
|
||||||
|
analyze: true
|
||||||
|
};
|
||||||
|
} else if (typeof sound === 'object') {
|
||||||
|
const validatedObj: SoundParams = {
|
||||||
|
dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn),
|
||||||
|
analyze: true,
|
||||||
|
...sound as Partial<SoundParams>
|
||||||
|
};
|
||||||
|
return validatedObj;
|
||||||
|
} else {
|
||||||
if (sound.includes(":")) {
|
if (sound.includes(":")) {
|
||||||
this.values = {
|
const vals = sound.split(":");
|
||||||
s: sound.split(":")[0],
|
const s = vals[0];
|
||||||
n: sound.split(":")[1],
|
const n = parseInt(vals[1]);
|
||||||
dur: app.clock.convertPulseToSecond(app.clock.ppqn),
|
return {
|
||||||
analyze: true,
|
s,
|
||||||
|
n,
|
||||||
|
dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn),
|
||||||
|
analyze: true
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
this.values = { s: sound, dur: 0.5, analyze: true };
|
return { s: sound, dur: 0.5, analyze: true };
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.values = sound;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateValue<T>(key: string, value: T): this {
|
private updateValue<T>(key: string, value: T | T[] | SoundParams[] | null): this {
|
||||||
|
if (value == null) return this;
|
||||||
this.values[key] = value;
|
this.values[key] = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================================================================================
|
// ================================================================================
|
||||||
// ZZFX Sound Parameters
|
// AbstactEvent overrides
|
||||||
// ================================================================================
|
// ================================================================================
|
||||||
|
|
||||||
public volume = (value: number) => this.updateValue("volume", value);
|
modify = (func: Function): this => {
|
||||||
public vol = this.volume;
|
const funcResult = func(this);
|
||||||
public zrand = (value: number) => this.updateValue("zrand", value);
|
if (funcResult instanceof Object) return funcResult;
|
||||||
public curve = (value: number) => this.updateValue("curve", value);
|
else {
|
||||||
public slide = (value: number) => this.updateValue("slide", value);
|
func(this.values);
|
||||||
public sld = this.slide;
|
this.update();
|
||||||
public deltaSlide = (value: number) => this.updateValue("deltaSlide", value);
|
return this;
|
||||||
public dslide = this.deltaSlide;
|
|
||||||
public pitchJump = (value: number) => this.updateValue("pitchJump", value);
|
|
||||||
public pj = this.pitchJump;
|
|
||||||
public pitchJumpTime = (value: number) =>
|
|
||||||
this.updateValue("pitchJumpTime", value);
|
|
||||||
public pjt = this.pitchJumpTime;
|
|
||||||
public lfo = (value: number) => this.updateValue("lfo", value);
|
|
||||||
public znoise = (value: number) => this.updateValue("znoise", value);
|
|
||||||
public noise = (value: number) => this.updateValue("noise", value);
|
|
||||||
public zmod = (value: number) => this.updateValue("zmod", value);
|
|
||||||
public zcrush = (value: number) => this.updateValue("zcrush", value);
|
|
||||||
public zdelay = (value: number) => this.updateValue("zdelay", value);
|
|
||||||
public sustainVolume = (value: number) =>
|
|
||||||
this.updateValue("sustainVolume", value);
|
|
||||||
public tremolo = (value: number) => this.updateValue("tremolo", value);
|
|
||||||
public dur = (value: number) => this.updateValue("dur", value);
|
|
||||||
public zzfx = (value: number[]) => this.updateValue("zzfx", value);
|
|
||||||
|
|
||||||
// ================================================================================
|
|
||||||
// Basic Audio Engine Parameters
|
|
||||||
// ================================================================================
|
|
||||||
|
|
||||||
// FM Synthesis
|
|
||||||
public fmi = (value: number) => this.updateValue("fmi", value);
|
|
||||||
public fmh = (value: number) => this.updateValue("fmh", value);
|
|
||||||
public fmenv = (value: "lin" | "exp") => this.updateValue("fmenv", value);
|
|
||||||
public fmattack = (value: number) => this.updateValue("fmattack", value);
|
|
||||||
public fmatk = this.fmattack;
|
|
||||||
public fmdecay = (value: number) => this.updateValue("fmdecay", value);
|
|
||||||
public fmdec = this.fmdecay;
|
|
||||||
public fmsustain = (value: number) => this.updateValue("fmsustain", value);
|
|
||||||
public fmsus = this.fmsustain;
|
|
||||||
public fmrelease = (value: number) => this.updateValue("fmrelease", value);
|
|
||||||
public fmrel = this.fmrelease;
|
|
||||||
public fmvelocity = (value: number) => this.updateValue("fmvelocity", value);
|
|
||||||
public fmvel = this.fmvelocity;
|
|
||||||
public fmwave = (value: "sine" | "triangle" | "sawtooth" | "square") =>
|
|
||||||
this.updateValue("fmwave", value);
|
|
||||||
public fmw = this.fmwave;
|
|
||||||
|
|
||||||
// Filter type
|
|
||||||
public ftype = (value: "12db" | "24db") => this.updateValue("ftype", value);
|
|
||||||
public fanchor = (value: number) => this.updateValue("fanchor", value);
|
|
||||||
|
|
||||||
// Amplitude Envelope
|
|
||||||
public attack = (value: number) => this.updateValue("attack", value);
|
|
||||||
public atk = this.attack;
|
|
||||||
public decay = (value: number) => this.updateValue("decay", value);
|
|
||||||
public dec = this.decay;
|
|
||||||
public sustain = (value: number) => this.updateValue("sustain", value);
|
|
||||||
public sus = this.sustain;
|
|
||||||
public release = (value: number) => this.updateValue("release", value);
|
|
||||||
public rel = this.release;
|
|
||||||
public adsr = (a: number, d: number, s: number, r: number) => {
|
|
||||||
this.attack(a);
|
|
||||||
this.decay(d);
|
|
||||||
this.sustain(s);
|
|
||||||
this.release(r);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
public ad = (a: number, d: number) => {
|
|
||||||
this.attack(a);
|
|
||||||
this.decay(d);
|
|
||||||
this.sustain(0.0);
|
|
||||||
this.release(0.0);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Lowpass filter
|
|
||||||
public lpenv = (value: number) => this.updateValue("lpenv", value);
|
|
||||||
public lpe = (value: number) => this.updateValue("lpenv", value);
|
|
||||||
public lpattack = (value: number) => this.updateValue("lpattack", value);
|
|
||||||
public lpa = this.lpattack;
|
|
||||||
public lpdecay = (value: number) => this.updateValue("lpdecay", value);
|
|
||||||
public lpd = this.lpdecay;
|
|
||||||
public lpsustain = (value: number) => this.updateValue("lpsustain", value);
|
|
||||||
public lps = this.lpsustain;
|
|
||||||
public lprelease = (value: number) => this.updateValue("lprelease", value);
|
|
||||||
public lpr = this.lprelease;
|
|
||||||
public cutoff = (value: number, resonance?: number) => {
|
|
||||||
this.updateValue("cutoff", value);
|
|
||||||
if (resonance) {
|
|
||||||
this.resonance(resonance)
|
|
||||||
}
|
}
|
||||||
return this;
|
};
|
||||||
}
|
|
||||||
public lpf = this.cutoff;
|
update = (): void => {
|
||||||
public resonance = (value: number) => {
|
const filteredValues = filterObject(this.values, ["key", "pitch", "parsedScale", "octave"]);
|
||||||
if (value >= 0 && value <= 1) {
|
const events = objectWithArraysToArrayOfObjects(filteredValues,["parsedScale"]);
|
||||||
this.updateValue(
|
|
||||||
"resonance",
|
events.forEach((event) => {
|
||||||
50 * value
|
const [note, _] = noteFromPc(
|
||||||
|
event.key as number || "C4",
|
||||||
|
event.pitch as number || 0,
|
||||||
|
event.parsedScale as number[] || event.scale || "MAJOR",
|
||||||
|
event.octave as number || 0
|
||||||
);
|
);
|
||||||
}
|
event.note = note;
|
||||||
return this;
|
event.freq = midiToFreq(note);
|
||||||
}
|
});
|
||||||
public lpq = this.resonance;
|
|
||||||
public lpadsr = (
|
const newArrays = arrayOfObjectsToObjectWithArrays(events) as SoundParams;
|
||||||
depth: number,
|
|
||||||
a: number,
|
this.values.note = newArrays.note;
|
||||||
d: number,
|
this.values.freq = newArrays.freq;
|
||||||
s: number,
|
|
||||||
r: number
|
|
||||||
) => {
|
|
||||||
this.lpenv(depth);
|
|
||||||
this.lpattack(a);
|
|
||||||
this.lpdecay(d);
|
|
||||||
this.lpsustain(s);
|
|
||||||
this.lprelease(r);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
public lpad = (
|
|
||||||
depth: number,
|
|
||||||
a: number,
|
|
||||||
d: number,
|
|
||||||
) => {
|
|
||||||
this.lpenv(depth);
|
|
||||||
this.lpattack(a);
|
|
||||||
this.lpdecay(d);
|
|
||||||
this.lpsustain(0);
|
|
||||||
this.lprelease(0);
|
|
||||||
return this;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public chord = (value: string) => {
|
||||||
// Highpass filter
|
|
||||||
|
|
||||||
public hpenv = (value: number) => this.updateValue("hpenv", value);
|
|
||||||
public hpe = (value: number) => this.updateValue("hpe", value);
|
|
||||||
public hpattack = (value: number) => this.updateValue("hpattack", value);
|
|
||||||
public hpa = this.hpattack;
|
|
||||||
public hpdecay = (value: number) => this.updateValue("hpdecay", value);
|
|
||||||
public hpd = this.hpdecay;
|
|
||||||
public hpsustain = (value: number) => this.updateValue("hpsustain", value);
|
|
||||||
public hpsus = this.hpsustain;
|
|
||||||
public hprelease = (value: number) => this.updateValue("hprelease", value);
|
|
||||||
public hpr = this.hprelease;
|
|
||||||
public hcutoff = (value: number) => this.updateValue("hcutoff", value);
|
|
||||||
public hpf = this.hcutoff;
|
|
||||||
public hresonance = (value: number, resonance?: number) => {
|
|
||||||
this.updateValue("hresonance", value);
|
|
||||||
if (resonance) {
|
|
||||||
this.resonance(resonance)
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
public hpq = this.hresonance;
|
|
||||||
public hpadsr = (
|
|
||||||
depth: number,
|
|
||||||
a: number,
|
|
||||||
d: number,
|
|
||||||
s: number,
|
|
||||||
r: number
|
|
||||||
) => {
|
|
||||||
this.hpenv(depth);
|
|
||||||
this.hpattack(a);
|
|
||||||
this.hpdecay(d);
|
|
||||||
this.hpsustain(s);
|
|
||||||
this.hprelease(r);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
public hpad = (
|
|
||||||
depth: number,
|
|
||||||
a: number,
|
|
||||||
d: number,
|
|
||||||
) => {
|
|
||||||
this.hpenv(depth);
|
|
||||||
this.hpattack(a);
|
|
||||||
this.hpdecay(d);
|
|
||||||
this.hpsustain(0);
|
|
||||||
this.hprelease(0);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Bandpass filter
|
|
||||||
|
|
||||||
public bpenv = (value: number) => this.updateValue("bpenv", value);
|
|
||||||
public bpe = (value: number) => this.updateValue("bpe", value);
|
|
||||||
public bpattack = (value: number) => this.updateValue("bpattack", value);
|
|
||||||
public bpa = this.bpattack;
|
|
||||||
public bpdecay = (value: number) => this.updateValue("bpdecay", value);
|
|
||||||
public bpd = this.bpdecay;
|
|
||||||
public bpsustain = (value: number) => this.updateValue("bpsustain", value);
|
|
||||||
public bps = this.bpsustain;
|
|
||||||
public bprelease = (value: number) => this.updateValue("bprelease", value);
|
|
||||||
public bpr = this.bprelease;
|
|
||||||
public bandf = (value: number, resonance?: number) => {
|
|
||||||
this.updateValue("bandf", value);
|
|
||||||
if (resonance) {
|
|
||||||
this.resonance(resonance)
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
public bpf = this.bandf;
|
|
||||||
public bandq = (value: number) => this.updateValue("bandq", value);
|
|
||||||
public bpq = this.bandq;
|
|
||||||
public bpadsr = (
|
|
||||||
depth: number,
|
|
||||||
a: number,
|
|
||||||
d: number,
|
|
||||||
s: number,
|
|
||||||
r: number
|
|
||||||
) => {
|
|
||||||
this.bpenv(depth);
|
|
||||||
this.bpattack(a);
|
|
||||||
this.bpdecay(d);
|
|
||||||
this.bpsustain(s);
|
|
||||||
this.bprelease(r);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
public bpad = (
|
|
||||||
depth: number,
|
|
||||||
a: number,
|
|
||||||
d: number,
|
|
||||||
) => {
|
|
||||||
this.bpenv(depth);
|
|
||||||
this.bpattack(a);
|
|
||||||
this.bpdecay(d);
|
|
||||||
this.bpsustain(0);
|
|
||||||
this.bprelease(0);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
public freq = (value: number) => this.updateValue("freq", value);
|
|
||||||
public f = this.freq;
|
|
||||||
public vib = (value: number) => this.updateValue("vib", value);
|
|
||||||
public vibmod = (value: number) => this.updateValue("vibmod", value);
|
|
||||||
public fm = (value: number | string) => {
|
|
||||||
if (typeof value === "number") {
|
|
||||||
this.values["fmi"] = value;
|
|
||||||
} else {
|
|
||||||
let values = value.split(":");
|
|
||||||
this.values["fmi"] = parseFloat(values[0]);
|
|
||||||
if (values.length > 1) this.values["fmh"] = parseFloat(values[1]);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sampler looping
|
|
||||||
public loop = (value: number) => this.updateValue("loop", value);
|
|
||||||
public loopBegin = (value: number) => this.updateValue("loopBegin", value);
|
|
||||||
public loopEnd = (value: number) => this.updateValue("loopEnd", value);
|
|
||||||
public begin = (value: number) => this.updateValue("begin", value);
|
|
||||||
public end = (value: number) => this.updateValue("end", value);
|
|
||||||
|
|
||||||
// Gain management
|
|
||||||
public gain = (value: number) => this.updateValue("gain", value);
|
|
||||||
public dbgain = (value: number) =>
|
|
||||||
this.updateValue("gain", Math.min(Math.pow(10, value / 20), 10));
|
|
||||||
public db = this.dbgain;
|
|
||||||
public velocity = (value: number) => this.updateValue("velocity", value);
|
|
||||||
public vel = this.velocity;
|
|
||||||
|
|
||||||
// Panoramic control (stereo)
|
|
||||||
public pan = (value: number) => this.updateValue("pan", value);
|
|
||||||
|
|
||||||
// Frequency management
|
|
||||||
|
|
||||||
public sound = (value: string) => this.updateValue("s", value);
|
|
||||||
public chord = (
|
|
||||||
value: string | object[] | number[] | number,
|
|
||||||
...kwargs: number[]
|
|
||||||
) => {
|
|
||||||
if (typeof value === "string") {
|
|
||||||
const chord = parseChord(value);
|
const chord = parseChord(value);
|
||||||
value = chord.map((note: number) => {
|
return this.updateValue("note", chord);
|
||||||
return { note: note, freq: midiToFreq(note) };
|
|
||||||
});
|
|
||||||
} else if (value instanceof Array && typeof value[0] === "number") {
|
|
||||||
value = (value as number[]).map((note: number) => {
|
|
||||||
return { note: note, freq: midiToFreq(note) };
|
|
||||||
});
|
|
||||||
} else if (typeof value === "number" && kwargs.length > 0) {
|
|
||||||
value = [value, ...kwargs].map((note: number) => {
|
|
||||||
return { note: note, freq: midiToFreq(note) };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.updateValue("chord", value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public invert = (howMany: number = 0) => {
|
public invert = (howMany: number = 0) => {
|
||||||
if (this.values.chord) {
|
if (this.values.chord) {
|
||||||
let notes = this.values.chord.map(
|
let notes = this.values.chord.map(
|
||||||
@ -352,10 +400,6 @@ export class SoundEvent extends AudibleEvent {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
public snd = this.sound;
|
|
||||||
public cut = (value: number) => this.updateValue("cut", value);
|
|
||||||
public clip = (value: number) => this.updateValue("clip", value);
|
|
||||||
public n = (value: number) => this.updateValue("n", value);
|
|
||||||
public note = (value: number | string | null) => {
|
public note = (value: number | string | null) => {
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
return this.updateValue("note", noteNameToMidi(value));
|
return this.updateValue("note", noteNameToMidi(value));
|
||||||
@ -365,108 +409,15 @@ export class SoundEvent extends AudibleEvent {
|
|||||||
return this.updateValue("note", value);
|
return this.updateValue("note", value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
public speed = (value: number) => this.updateValue("speed", value);
|
|
||||||
public spd = this.speed;
|
|
||||||
|
|
||||||
// Creative sampler effects
|
|
||||||
public coarse = (value: number) => this.updateValue("coarse", value);
|
|
||||||
public crush = (value: number) => this.updateValue("crush", value);
|
|
||||||
public shape = (value: number) => this.updateValue("shape", value);
|
|
||||||
public vowel = (value: number) => this.updateValue("vowel", value);
|
|
||||||
public vow = this.vowel;
|
|
||||||
|
|
||||||
// Delay control
|
|
||||||
public delay = (value: number) => this.updateValue("delay", value);
|
|
||||||
public del = this.delay;
|
|
||||||
public delayfeedback = (value: number) =>
|
|
||||||
this.updateValue("delayfeedback", value);
|
|
||||||
public delayfb = this.delayfeedback;
|
|
||||||
public delaytime = (value: number) => this.updateValue("delaytime", value);
|
|
||||||
public delayt = this.delaytime;
|
|
||||||
|
|
||||||
// Orbit management
|
|
||||||
public orbit = (value: number) => this.updateValue("orbit", value);
|
|
||||||
public o = this.orbit;
|
|
||||||
|
|
||||||
// Reverb management
|
|
||||||
public room = (value: number) => this.updateValue("room", value);
|
|
||||||
public rm = this.room;
|
|
||||||
public roomfade = (value: number) => this.updateValue("roomfade", value);
|
|
||||||
public rfade = this.roomfade;
|
|
||||||
public roomlp = (value: number) => this.updateValue("roomlp", value);
|
|
||||||
public rlp = this.roomlp;
|
|
||||||
public roomdim = (value: number) => this.updateValue("roomdim", value);
|
|
||||||
public rdim = this.roomdim;
|
|
||||||
public size = (value: number) => this.updateValue("roomsize", value);
|
|
||||||
public sz = this.size;
|
|
||||||
public rev = (room: number, size: number, fade?: number, lp?: number, dim?: number) => {
|
|
||||||
this.updateValue("room", room)
|
|
||||||
this.updateValue("roomsize", size)
|
|
||||||
if (fade)
|
|
||||||
this.updateValue("roomfade", fade)
|
|
||||||
if (lp)
|
|
||||||
this.updateValue("roomlp", lp)
|
|
||||||
if (dim)
|
|
||||||
this.updateValue("roomdim", dim)
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compressor
|
|
||||||
public comp = (value: number) => this.updateValue("compressor", value);
|
|
||||||
public cmp = this.comp;
|
|
||||||
public ratio = (value: number) => this.updateValue("compressorRatio", value);
|
|
||||||
public rt = this.ratio;
|
|
||||||
public knee = (value: number) => this.updateValue("compressorKnee", value);
|
|
||||||
public kn = this.knee;
|
|
||||||
public compAttack = (value: number) =>
|
|
||||||
this.updateValue("compressorAttack", value);
|
|
||||||
public cmpa = this.compAttack;
|
|
||||||
public compRelease = (value: number) =>
|
|
||||||
this.updateValue("compressorRelease", value);
|
|
||||||
public cmpr = this.compRelease;
|
|
||||||
|
|
||||||
// Unit
|
|
||||||
public stretch = (beat: number) => {
|
|
||||||
this.updateValue("unit", "c");
|
|
||||||
this.updateValue("speed", 1 / beat);
|
|
||||||
this.updateValue("cut", beat);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ================================================================================
|
|
||||||
// AbstactEvent overrides
|
|
||||||
// ================================================================================
|
|
||||||
|
|
||||||
modify = (func: Function): this => {
|
|
||||||
const funcResult = func(this);
|
|
||||||
if (funcResult instanceof Object) return funcResult;
|
|
||||||
else {
|
|
||||||
func(this.values);
|
|
||||||
this.update();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
update = (): void => {
|
|
||||||
const [note, _] = noteFromPc(
|
|
||||||
this.values.key || "C4",
|
|
||||||
this.values.pitch || 0,
|
|
||||||
this.values.parsedScale || "MAJOR",
|
|
||||||
this.values.octave || 0
|
|
||||||
);
|
|
||||||
this.values.freq = midiToFreq(note);
|
|
||||||
};
|
|
||||||
|
|
||||||
out = (): void => {
|
out = (): void => {
|
||||||
if (this.values.chord) {
|
const events = objectWithArraysToArrayOfObjects(this.values,["parsedScale"]);
|
||||||
this.values.chord.forEach((obj: { [key: string]: number }) => {
|
for (const event of events) {
|
||||||
const copy = { ...this.values };
|
superdough(
|
||||||
copy.freq = obj.freq;
|
event,
|
||||||
superdough(copy, this.nudge, this.values.dur);
|
this.nudge,
|
||||||
});
|
event.dur
|
||||||
} else {
|
);
|
||||||
superdough(this.values, this.nudge, this.values.dur);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { SkipEvent } from "./SkipEvent";
|
|||||||
import { SoundEvent, SoundParams } from "./SoundEvent";
|
import { SoundEvent, SoundParams } from "./SoundEvent";
|
||||||
import { MidiEvent, MidiParams } from "./MidiEvent";
|
import { MidiEvent, MidiParams } from "./MidiEvent";
|
||||||
import { RestEvent } from "./RestEvent";
|
import { RestEvent } from "./RestEvent";
|
||||||
|
import { arrayOfObjectsToObjectWithArrays } from "../Utils/Generic";
|
||||||
|
|
||||||
export type InputOptions = { [key: string]: string | number };
|
export type InputOptions = { [key: string]: string | number };
|
||||||
|
|
||||||
@ -139,37 +140,39 @@ export class Player extends Event {
|
|||||||
};
|
};
|
||||||
|
|
||||||
sound(name?: string) {
|
sound(name?: string) {
|
||||||
|
|
||||||
if (this.areWeThereYet()) {
|
if (this.areWeThereYet()) {
|
||||||
const event = this.next() as Pitch | Chord | ZRest;
|
const event = this.next() as Pitch | Chord | ZRest;
|
||||||
const noteLengthInSeconds = this.app.clock.convertPulseToSecond(event.duration*4*this.app.clock.ppqn);
|
const noteLengthInSeconds = this.app.clock.convertPulseToSecond(event.duration*4*this.app.clock.ppqn);
|
||||||
if (event instanceof Pitch) {
|
if (event instanceof Pitch) {
|
||||||
const obj = event.getExisting(
|
const obj = event.getExisting(
|
||||||
"freq",
|
"freq",
|
||||||
|
"note",
|
||||||
"pitch",
|
"pitch",
|
||||||
"key",
|
"key",
|
||||||
"scale",
|
"scale",
|
||||||
"octave",
|
"octave",
|
||||||
"parsedScale"
|
"parsedScale"
|
||||||
);
|
) as SoundParams;
|
||||||
if(event.sound) name = event.sound as string;
|
if(event.sound) name = event.sound as string;
|
||||||
if(event.soundIndex) obj.n = event.soundIndex;
|
if(event.soundIndex) obj.n = event.soundIndex as number;
|
||||||
obj.dur = noteLengthInSeconds;
|
obj.dur = noteLengthInSeconds;
|
||||||
return new SoundEvent(obj, this.app).sound(name || "sine");
|
return new SoundEvent(obj, this.app).sound(name || "sine");
|
||||||
} else if (event instanceof Chord) {
|
} else if (event instanceof Chord) {
|
||||||
const pitches = event.pitches.map((p) => {
|
const pitches = event.pitches.map((p) => {
|
||||||
return p.getExisting(
|
return p.getExisting(
|
||||||
"freq",
|
"freq",
|
||||||
|
"note",
|
||||||
"pitch",
|
"pitch",
|
||||||
"key",
|
"key",
|
||||||
"scale",
|
"scale",
|
||||||
"octave",
|
"octave",
|
||||||
"parsedScale"
|
"parsedScale"
|
||||||
);
|
);
|
||||||
});
|
}) as SoundParams[];
|
||||||
const sound: SoundParams = {dur: noteLengthInSeconds};
|
const add = { dur: noteLengthInSeconds } as SoundParams;
|
||||||
if(name) sound.s = name;
|
if(name) add.s = name;
|
||||||
return new SoundEvent(sound, this.app).chord(pitches);
|
let sound = arrayOfObjectsToObjectWithArrays(pitches,add) as SoundParams;
|
||||||
|
return new SoundEvent(sound, this.app);
|
||||||
} else if (event instanceof ZRest) {
|
} else if (event instanceof ZRest) {
|
||||||
return RestEvent.createRestProxy(event.duration, this.app);
|
return RestEvent.createRestProxy(event.duration, this.app);
|
||||||
}
|
}
|
||||||
@ -189,16 +192,17 @@ export class Player extends Event {
|
|||||||
"scale",
|
"scale",
|
||||||
"octave",
|
"octave",
|
||||||
"parsedScale",
|
"parsedScale",
|
||||||
);
|
) as MidiParams;
|
||||||
if (event instanceof Pitch) {
|
if (event instanceof Pitch) {
|
||||||
if(event.soundIndex) obj.channel = event.soundIndex;
|
if(event.soundIndex) obj.channel = event.soundIndex as number;
|
||||||
const note = new MidiEvent(obj, this.app);
|
const note = new MidiEvent(obj, this.app);
|
||||||
return value ? note.note(value) : note;
|
return value ? note.note(value) : note;
|
||||||
} else if (event instanceof ZRest) {
|
} else if (event instanceof ZRest) {
|
||||||
return RestEvent.createRestProxy(event.duration, this.app);
|
return RestEvent.createRestProxy(event.duration, this.app);
|
||||||
} else if (event instanceof Chord) {
|
} else if (event instanceof Chord) {
|
||||||
const pitches = event.midiChord() as MidiParams[];
|
const pitches = event.midiChord() as MidiParams[];
|
||||||
return new MidiEvent(obj, this.app).chord(pitches);
|
const obj = arrayOfObjectsToObjectWithArrays(pitches) as MidiParams;
|
||||||
|
return new MidiEvent(obj, this.app);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return SkipEvent.createSkipProxy();
|
return SkipEvent.createSkipProxy();
|
||||||
|
|||||||
Reference in New Issue
Block a user