Merge branch 'main' of github.com:Bubobubobubobubo/Topos into dev

This commit is contained in:
2023-08-30 12:34:47 +02:00
19 changed files with 1334 additions and 1270 deletions

View File

@ -1,122 +1,126 @@
import { type Editor } from '../main';
import { freqToMidi, resolvePitchBend, getScale, isScale, parseScala } from 'zifferjs';
import { type Editor } from "../main";
import {
freqToMidi,
resolvePitchBend,
getScale,
isScale,
parseScala,
} from "zifferjs";
export abstract class Event {
seedValue: string|undefined = undefined;
randomGen: Function = Math.random;
app: Editor;
values: { [key: string]: any } = {};
seedValue: string | undefined = undefined;
randomGen: Function = Math.random;
app: Editor;
values: { [key: string]: any } = {};
constructor(app: Editor) {
this.app = app;
if(this.app.api.currentSeed) {
this.randomGen = this.app.api.randomGen;
}
constructor(app: Editor) {
this.app = app;
if (this.app.api.currentSeed) {
this.randomGen = this.app.api.randomGen;
}
}
odds = (probability: number, func: Function): Event => {
if(this.randomGen() < probability) {
return this.modify(func);
}
return this;
odds = (probability: number, func: Function): Event => {
if (this.randomGen() < probability) {
return this.modify(func);
}
return this;
};
almostNever = (func: Function): Event => {
return this.odds(0.025, func);
}
almostNever = (func: Function): Event => {
return this.odds(0.025, func);
};
rarely = (func: Function): Event => {
return this.odds(0.1, func);
}
rarely = (func: Function): Event => {
return this.odds(0.1, func);
};
scarcely = (func: Function): Event => {
return this.odds(0.25, func);
}
scarcely = (func: Function): Event => {
return this.odds(0.25, func);
};
sometimes = (func: Function): Event => {
return this.odds(0.5, func);
}
sometimes = (func: Function): Event => {
return this.odds(0.5, func);
};
often = (func: Function): Event => {
return this.odds(0.75, func);
}
often = (func: Function): Event => {
return this.odds(0.75, func);
};
frequently = (func: Function): Event => {
return this.odds(0.9, func);
}
frequently = (func: Function): Event => {
return this.odds(0.9, func);
};
almostAlways = (func: Function): Event => {
return this.odds(0.985, func);
}
almostAlways = (func: Function): Event => {
return this.odds(0.985, func);
};
modify = (func: Function): Event => {
return func(this);
}
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;
}
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;
}
clear = (): Event => {
this.app.api.clearLocalSeed(this.seedValue);
return this;
};
apply = (func: Function): Event => {
return this.modify(func);
}
duration = (value: number): Event => {
this.values['duration'] = value;
return this;
}
apply = (func: Function): Event => {
return this.modify(func);
};
duration = (value: number): Event => {
this.values["duration"] = value;
return this;
};
}
export abstract class AudibleEvent extends Event {
constructor(app: Editor) {
super(app);
}
constructor(app: Editor) {
super(app);
}
octave = (value: number): this => {
this.values['octave'] = value;
this.update();
return this;
}
octave = (value: number): this => {
this.values["octave"] = value;
this.update();
return this;
};
key = (value: string): this => {
this.values['key'] = value;
this.update();
return this;
}
key = (value: string): this => {
this.values["key"] = value;
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();
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();
return this;
};
freq = (value: number): this => {
this.values['freq'] = value;
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;
freq = (value: number): this => {
this.values["freq"] = value;
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;
};
update = (): void => {
// Overwrite in subclasses
}
}
update = (): void => {
// Overwrite in subclasses
};
}

View File

@ -1,95 +1,102 @@
import { AudibleEvent } from './AbstractEvents';
import { type Editor } from '../main';
import { AudibleEvent } from "./AbstractEvents";
import { type Editor } from "../main";
import { MidiConnection } from "../IO/MidiConnection";
import { midiToFreq, noteFromPc } from 'zifferjs';
import { midiToFreq, noteFromPc } from "zifferjs";
export class NoteEvent extends AudibleEvent {
midiConnection: MidiConnection;
midiConnection: MidiConnection;
constructor(input: number|object, public app: Editor) {
super(app);
if(typeof input === 'number') this.values['note'] = input;
else this.values = input;
this.midiConnection = app.api.MidiConnection
}
constructor(input: number | object, public app: Editor) {
super(app);
if (typeof input === "number") this.values["note"] = input;
else this.values = input;
this.midiConnection = app.api.MidiConnection;
}
note = (value: number): this => {
this.values['note'] = value;
return this;
}
note = (value: number): this => {
this.values["note"] = value;
return this;
};
sustain = (value: number): this => {
this.values['sustain'] = value;
return this;
}
sustain = (value: number): this => {
this.values["sustain"] = value;
return this;
};
channel = (value: number): this => {
this.values['channel'] = 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;
}
port = (value: number | string): this => {
this.values["port"] = this.midiConnection.getMidiOutputIndex(value);
return this;
};
add = (value: number): this => {
this.values.note += 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);
this.update();
return this;
}
modify = (func: Function): this => {
const funcResult = func(this);
if (funcResult instanceof Object) {
return funcResult;
} else {
func(this.values);
this.update();
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;
}
bend = (value: number): this => {
this.values["bend"] = value;
return this;
};
update = (): void => {
const [note, bend] = noteFromPc(
this.values.key || "C4",
this.values.pitch || 0,
this.values.parsedScale || "MAJOR",
this.values.octave || 0
);
this.values.note = note;
this.values.freq = midiToFreq(note);
if(bend) this.values.bend = bend;
}
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 sustain = this.values.sustain ?
this.values.sustain * this.app.clock.pulse_duration * this.app.api.ppqn() :
this.app.clock.pulse_duration * this.app.api.ppqn();
const bend = this.values.bend ? this.values.bend : undefined;
const port = this.values.port ?
this.midiConnection.getMidiOutputIndex(this.values.port) :
this.midiConnection.getCurrentMidiPortIndex();
update = (): void => {
const [note, bend] = noteFromPc(
this.values.key || "C4",
this.values.pitch || 0,
this.values.parsedScale || "MAJOR",
this.values.octave || 0
);
this.values.note = note;
this.values.freq = midiToFreq(note);
if (bend) this.values.bend = bend;
};
this.midiConnection.sendMidiNote(note, channel, velocity, sustain, port, bend);
}
}
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 sustain = this.values.sustain
? this.values.sustain *
this.app.clock.pulse_duration *
this.app.api.ppqn()
: this.app.clock.pulse_duration * this.app.api.ppqn();
const bend = this.values.bend ? this.values.bend : undefined;
const port = this.values.port
? this.midiConnection.getMidiOutputIndex(this.values.port)
: this.midiConnection.getCurrentMidiPortIndex();
this.midiConnection.sendMidiNote(
note,
channel,
velocity,
sustain,
port,
bend
);
};
}

View File

@ -1,37 +1,39 @@
import { type Editor } from '../main';
import { type Editor } from "../main";
import { Event } from "./AbstractEvents";
export class RestEvent extends Event {
constructor(duration: number, app: Editor) {
super(app);
this.values["duration"] = duration;
}
constructor(duration: number, app: Editor) {
super(app);
this.values["duration"] = duration;
}
_fallbackMethod = (): Event => {
return RestEvent.createRestProxy(this.values["duration"], this.app);
}
_fallbackMethod = (): Event => {
return RestEvent.createRestProxy(this.values["duration"], this.app);
};
public static createRestProxy = (duration: number, app: Editor): RestEvent => {
const instance = new RestEvent(duration, app);
return new Proxy(instance, {
// @ts-ignore
get(target, propKey, receiver) {
// @ts-ignore
if (typeof target[propKey] === 'undefined') {
return target._fallbackMethod;
}
// @ts-ignore
return target[propKey];
},
// @ts-ignore
set(target, propKey, value, receiver) {
return false;
}
});
}
public static createRestProxy = (
duration: number,
app: Editor
): RestEvent => {
const instance = new RestEvent(duration, app);
return new Proxy(instance, {
// @ts-ignore
get(target, propKey, receiver) {
// @ts-ignore
if (typeof target[propKey] === "undefined") {
return target._fallbackMethod;
}
// @ts-ignore
return target[propKey];
},
// @ts-ignore
set(target, propKey, value, receiver) {
return false;
},
});
};
out = (): void => {
// TODO?
}
}
out = (): void => {
// TODO?
};
}

View File

@ -1,28 +1,26 @@
export class SkipEvent {
_fallbackMethod = (): SkipEvent => {
return SkipEvent.createSkipProxy();
};
_fallbackMethod = (): SkipEvent => {
return SkipEvent.createSkipProxy();
}
public static createSkipProxy = (): SkipEvent => {
const instance = new SkipEvent();
return new Proxy(instance, {
// @ts-ignore
get(target, propKey, receiver) {
// @ts-ignore
if (typeof target[propKey] === 'undefined') {
return target._fallbackMethod;
}
// @ts-ignore
return target[propKey];
},
// @ts-ignore
set(target, propKey, value, receiver) {
return false;
}
});
}
public static createSkipProxy = (): SkipEvent => {
const instance = new SkipEvent();
return new Proxy(instance, {
// @ts-ignore
get(target, propKey, receiver) {
// @ts-ignore
if (typeof target[propKey] === "undefined") {
return target._fallbackMethod;
}
// @ts-ignore
return target[propKey];
},
// @ts-ignore
set(target, propKey, value, receiver) {
return false;
},
});
};
out = (): void => {}
}
out = (): void => {};
}

View File

@ -238,13 +238,13 @@ export class SoundEvent extends AudibleEvent {
sus = this.sustain;
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);
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 = (): object => {

View File

@ -9,192 +9,204 @@ import { RestEvent } from "./RestEvent";
export type InputOptions = { [key: string]: string | number };
export class Player extends Event {
input: string;
ziffers: Ziffers;
initCallTime: number = 0;
startCallTime: number = 0;
lastCallTime: number = 0;
waitTime = 0;
startBeat: number = 0;
played: boolean = false;
current!: Pitch|Chord|ZRest;
retro: boolean = false;
index: number = -1;
zid: string|undefined = undefined;
options: InputOptions = {};
input: string;
ziffers: Ziffers;
initCallTime: number = 1;
startCallTime: number = 1;
lastCallTime: number = 1;
waitTime = 0;
startBeat: number = 0;
played: boolean = false;
current!: Pitch | Chord | ZRest;
retro: boolean = false;
index: number = -1;
zid: string = "";
options: InputOptions = {};
skipIndex = 0;
endTime = 0;
constructor(input: string, options: InputOptions, public app: Editor) {
super(app);
this.input = input;
this.options = options;
this.ziffers = new Ziffers(input, options);
constructor(input: string, options: InputOptions, public app: Editor) {
super(app);
this.input = input;
this.options = options;
this.ziffers = new Ziffers(input, options);
}
get ticks(): number {
const dur = this.ziffers.duration;
return dur * 4 * this.app.clock.ppqn;
}
nextEndTime(): number {
return this.startCallTime + this.ticks;
}
updateLastCallTime(): void {
if (this.notStarted() || this.played) {
this.lastCallTime = this.app.clock.pulses_since_origin;
this.played = false;
}
}
notStarted(): boolean {
return this.ziffers.notStarted();
}
next = (): Pitch | Chord | ZRest => {
this.current = this.ziffers.next() as Pitch | Chord | ZRest;
this.played = true;
return this.current;
};
pulseToSecond = (pulse: number): number => {
return this.app.clock.convertPulseToSecond(pulse);
};
firstRun = (): boolean => {
return this.notStarted();
};
atTheBeginning = (): boolean => {
return this.skipIndex === 0 && this.ziffers.index <= 0;
};
origin = (): number => {
return this.app.clock.pulses_since_origin;
};
pulse = (): number => {
return this.app.clock.time_position.pulse;
};
beat = (): number => {
return this.app.clock.time_position.beat;
};
nextBeat = (): number => {
return this.app.clock.next_beat_in_ticks;
};
// Check if it's time to play the event
areWeThereYet = (): boolean => {
// If clock has stopped
if (this.app.clock.pulses_since_origin < this.lastCallTime) {
this.lastCallTime = 1;
this.startCallTime = 1;
this.index = 0;
this.waitTime = 0;
}
get ticks(): number {
const dur = this.ziffers.duration;
return dur * 4 * this.app.clock.ppqn;
// Main logic
const howAboutNow =
// If pattern is just starting
(this.notStarted() &&
(this.app.clock.time_position.pulse === 1 ||
this.app.clock.pulses_since_origin >=
this.app.clock.next_beat_in_ticks) &&
this.app.clock.pulses_since_origin >= this.waitTime) || // If pattern is already playing
(this.current &&
this.pulseToSecond(this.app.clock.pulses_since_origin) >=
this.pulseToSecond(this.lastCallTime) +
this.current.duration *
4 *
this.pulseToSecond(this.app.api.ppqn()) &&
this.app.clock.pulses_since_origin >= this.waitTime);
// Increment index of how many times call is skipped
this.skipIndex = howAboutNow ? 0 : this.skipIndex + 1;
// Increment index of how many times sound/midi have been called
this.index = howAboutNow ? this.index + 1 : this.index;
if (howAboutNow && this.notStarted()) {
this.initCallTime = this.app.clock.pulses_since_origin;
}
nextEndTime(): number {
return this.startCallTime + this.ticks;
if (this.atTheBeginning()) {
this.startCallTime = this.app.clock.pulses_since_origin;
}
updateLastCallTime(): void {
if (this.notStarted() || this.played) {
this.lastCallTime = this.app.clock.pulses_since_origin;
this.played = false;
}
}
return howAboutNow;
};
notStarted(): boolean {
return this.ziffers.notStarted();
}
next = (): Pitch|Chord|ZRest => {
this.current = this.ziffers.next() as Pitch|Chord|ZRest;
this.played = true;
return this.current;
}
pulseToSecond = (pulse: number): number => {
return this.app.clock.convertPulseToSecond(pulse);
}
firstRun = (): boolean => {
return this.origin()<=0 && this.notStarted();
}
atTheBeginning = (): boolean => {
return this.pulse()===0 && this.ziffers.index===0;
}
origin = (): number => {
return this.app.clock.pulses_since_origin+1;
}
pulse = (): number => {
return this.app.clock.time_position.pulse;
}
nextBeat = (): number => {
return this.app.clock.next_beat_in_ticks;
}
// Check if it's time to play the event
areWeThereYet = (): boolean => {
// If clock has stopped
if(this.app.clock.pulses_since_origin<this.lastCallTime) {
this.lastCallTime = 0;
this.index = 0;
}
// Main logic
const howAboutNow = (
( // If pattern is just starting
this.notStarted() &&
(this.app.clock.time_position.pulse === 1 ||
this.app.clock.pulses_since_origin+1 >= this.app.clock.next_beat_in_ticks) &&
(this.app.clock.pulses_since_origin+1 >= this.waitTime)
)
||
( // If pattern is already playing
this.current &&
(this.pulseToSecond(this.app.clock.pulses_since_origin+1) >=
this.pulseToSecond(this.lastCallTime) +
(this.current.duration*4) * this.pulseToSecond(this.app.api.ppqn())) &&
(this.app.clock.pulses_since_origin+1 >= this.waitTime)
)
sound(name: string) {
if (this.areWeThereYet()) {
const event = this.next() as Pitch | Chord | ZRest;
if (event instanceof Pitch) {
const obj = event.getExisting(
"freq",
"pitch",
"key",
"scale",
"octave",
"parsedScale"
);
// Increment index of how many times sound/midi have been called
this.index = howAboutNow ? this.index+1 : this.index;
if(howAboutNow && this.notStarted()) {
this.initCallTime = this.app.clock.pulses_since_origin+1;
}
if(this.atTheBeginning()) {
this.startCallTime = this.app.clock.pulses_since_origin;
}
return howAboutNow;
return new SoundEvent(obj, this.app).sound(name);
} else if (event instanceof ZRest) {
return RestEvent.createRestProxy(event.duration, this.app);
}
} else {
return SkipEvent.createSkipProxy();
}
}
sound(name: string) {
if(this.areWeThereYet()) {
const event = this.next() as Pitch|Chord|ZRest;
if(event instanceof Pitch) {
const obj = event.getExisting("freq","pitch","key","scale","octave","parsedScale");
return new SoundEvent(obj, this.app).sound(name);
} else if(event instanceof ZRest) {
return RestEvent.createRestProxy(event.duration, this.app);
}
} else {
return SkipEvent.createSkipProxy();
}
midi(value: number | undefined = undefined) {
if (this.areWeThereYet()) {
const event = this.next() as Pitch | Chord | ZRest;
if (event instanceof Pitch) {
const obj = event.getExisting(
"note",
"pitch",
"bend",
"key",
"scale",
"octave",
"parsedScale"
);
const note = new NoteEvent(obj, this.app);
return value ? note.note(value) : note;
} else if (event instanceof ZRest) {
return RestEvent.createRestProxy(event.duration, this.app);
}
} else {
return SkipEvent.createSkipProxy();
}
}
midi(value: number|undefined = undefined) {
if(this.areWeThereYet()) {
const event = this.next() as Pitch|Chord|ZRest;
if(event instanceof Pitch) {
const obj = event.getExisting("note","pitch","bend","key","scale","octave","parsedScale");
const note = new NoteEvent(obj, this.app);
return value ? note.note(value) : note;
} else if(event instanceof ZRest) {
return RestEvent.createRestProxy(event.duration, this.app);
}
} else {
return SkipEvent.createSkipProxy();
}
}
scale(name: string) {
if (this.atTheBeginning()) this.ziffers.scale(name);
return this;
}
scale(name: string) {
this.ziffers.scale(name);
key(name: string) {
if (this.atTheBeginning()) this.ziffers.key(name);
return this;
}
octave(value: number) {
if (this.atTheBeginning()) this.ziffers.octave(value);
return this;
}
retrograde() {
if (this.atTheBeginning()) this.ziffers.retrograde();
return this;
}
wait(value: number | Function) {
if (this.atTheBeginning()) {
if (typeof value === "function") {
const refPat = this.app.api.patternCache.get(value.name) as Player;
if (refPat) this.waitTime = refPat.nextEndTime();
return this;
}
this.waitTime =
this.origin() + Math.ceil(value * 4 * this.app.clock.ppqn);
}
return this;
}
key(name: string) {
this.ziffers.key(name);
return this;
}
octave(value: number) {
this.ziffers.octave(value);
return this;
}
retrograde() {
if(this.index === -1 && this.ziffers.index === -1) {
this.ziffers.retrograde();
}
return this;
}
wait(value: number) {
if(this.index === -1 && this.ziffers.index === -1) {
// TODO: THIS LATER!
/* if(typeof value === "string") {
const cueKey = this.app.api.patternCues.get(value);
if(cueKey) {
const waitedPatter = this.app.api.patternCache.get(cueKey) as Player;
if(waitedPatter) {
this.waitTime = waitedPatter.nextEndTime();
}
}
} */
this.waitTime = this.origin() + Math.ceil(value*4*this.app.clock.ppqn);
}
return this;
}
out = (): void => {
// TODO?
}
}
out = (): void => {
// TODO?
};
}