Updating zifferjs and rearrange classes under folder

This commit is contained in:
2023-08-24 00:17:57 +03:00
parent f483262b7d
commit c6a2f5f1a8
8 changed files with 121 additions and 80 deletions

122
src/classes/Event.ts Normal file
View File

@ -0,0 +1,122 @@
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 } = {};
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;
}
almostNever = (func: Function): Event => {
return this.odds(0.025, func);
}
rarely = (func: Function): Event => {
return this.odds(0.1, func);
}
scarcely = (func: Function): Event => {
return this.odds(0.25, func);
}
sometimes = (func: Function): Event => {
return this.odds(0.5, func);
}
often = (func: Function): Event => {
return this.odds(0.75, func);
}
frequently = (func: Function): Event => {
return this.odds(0.9, func);
}
almostAlways = (func: Function): Event => {
return this.odds(0.985, 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);
}
duration = (value: number): Event => {
this.values['duration'] = value;
return this;
}
}
export abstract class SoundEvent extends Event {
constructor(app: Editor) {
super(app);
}
octave = (value: number): this => {
this.values['octave'] = 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;
}
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
}
}

92
src/classes/Note.ts Normal file
View File

@ -0,0 +1,92 @@
import { SoundEvent } from './Event';
import { type Editor } from '../main';
import { MidiConnection } from "../IO/MidiConnection";
import { midiToFreq, noteFromPc } from 'zifferjs';
export class Note extends SoundEvent {
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
}
note = (value: number): this => {
this.values['note'] = value;
return this;
}
sustain = (value: number): this => {
this.values['sustain'] = 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;
}
}
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;
}
update = (): void => {
if(this.values.key && this.values.pitch && this.values.parsedScale && this.values.octave) {
const [note,bend] = noteFromPc(this.values.key, this.values.pitch, this.values.parsedScale, this.values.octave);
this.values.note = note;
this.values.freq = midiToFreq(note);
if(bend) this.values.bend = 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);
}
}

37
src/classes/Rest.ts Normal file
View File

@ -0,0 +1,37 @@
import { type Editor } from '../main';
import { Event } from "./Event";
export class Rest extends Event {
constructor(duration: number, app: Editor) {
super(app);
this.values["duration"] = duration;
}
_fallbackMethod = (): Event => {
return this;
}
public static createRestProxy = (duration: number, app: Editor) => {
const instance = new Rest(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?
}
}

242
src/classes/Sound.ts Normal file
View File

@ -0,0 +1,242 @@
import { type Editor } from '../main';
import { SoundEvent } from './Event';
import { midiToFreq, noteFromPc } from 'zifferjs';
import {
superdough,
// @ts-ignore
} from "superdough";
export class Sound extends SoundEvent {
constructor(sound: string|object, public app: Editor) {
super(app);
if (typeof sound === 'string') this.values = { 's': sound, 'dur': 0.5 };
else this.values = sound;
}
attack = (value: number): this => {
this.values["attack"] = value;
return this;
};
atk = this.attack;
decay = (value: number): this => {
this.values["decay"] = value;
return this;
};
dec = this.decay;
release = (value: number): this => {
this.values["release"] = value;
return this;
};
rel = this.release;
unit = (value: number): this => {
this.values["unit"] = value;
return this;
};
freq = (value: number): this => {
this.values["freq"] = value;
return this;
};
fm = (value: number | string): this => {
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;
};
sound = (value: string): this => {
this.values['s'] = value
return this;
};
fmi = (value: number): this => {
this.values["fmi"] = value;
return this;
};
fmh = (value: number): this => {
this.values["fmh"] = value;
return this;
};
nudge = (value: number): this => {
this.values["nudge"] = value;
return this;
};
cut = (value: number): this => {
this.values["cut"] = value;
return this;
};
loop = (value: number): this => {
this.values["loop"] = value;
return this;
};
clip = (value: number): this => {
this.values["clip"] = value;
return this;
};
n = (value: number): this => {
this.values["n"] = value;
return this;
};
note = (value: number): this => {
this.values["note"] = value;
return this;
};
speed = (value: number): this => {
this.values["speed"] = value;
return this;
};
begin = (value: number): this => {
this.values["begin"] = value;
return this;
};
end = (value: number): this => {
this.values["end"] = value;
return this;
};
gain = (value: number): this => {
this.values["gain"] = value;
return this;
};
cutoff = (value: number): this => {
this.values["cutoff"] = value;
return this;
};
resonance = (value: number): this => {
this.values["resonance"] = value;
return this;
};
hcutoff = (value: number): this => {
this.values["hcutoff"] = value;
return this;
};
hresonance = (value: number): this => {
this.values["hresonance"] = value;
return this;
};
bandf = (value: number): this => {
this.values["bandf"] = value;
return this;
};
bandq = (value: number): this => {
this.values["bandq"] = value;
return this;
};
coarse = (value: number): this => {
this.values["coarse"] = value;
return this;
};
crush = (value: number): this => {
this.values["crush"] = value;
return this;
};
shape = (value: number): this => {
this.values["shape"] = value;
return this;
};
pan = (value: number): this => {
this.values["pan"] = value;
return this;
};
vowel = (value: number): this => {
this.values["vowel"] = value;
return this;
};
delay = (value: number): this => {
this.values["delay"] = value;
return this;
};
delayfeedback = (value: number): this => {
this.values["delayfeedback"] = value;
return this;
};
delaytime = (value: number): this => {
this.values["delaytime"] = value;
return this;
};
orbit = (value: number): this => {
this.values["orbit"] = value;
return this;
};
room = (value: number): this => {
this.values["room"] = value;
return this;
};
size = (value: number): this => {
this.values["size"] = value;
return this;
};
velocity = (value: number): this => {
this.values["velocity"] = value;
return this;
};
modify = (func: Function): this => {
const funcResult = func(this);
if(funcResult instanceof Object) return funcResult;
else {
func(this.values);
return this;
}
};
// NOTE: Sustain of the sound. duration() from the superclass Event is used to change the note length.
sustain = (value: number): this => {
this.values["dur"] = value;
return this;
};
update = (): void => {
if(this.values.key && this.values.pitch && this.values.parsedScale && this.values.octave) {
const [note,_] = noteFromPc(this.values.key, this.values.pitch, this.values.parsedScale, this.values.octave);
this.values.freq = midiToFreq(note);
}
}
out = (): object => {
return superdough(
this.values,
this.app.clock.pulse_duration,
this.values.dur
);
};
}

99
src/classes/ZPlayer.ts Normal file
View File

@ -0,0 +1,99 @@
import { Chord, Pitch, Rest as ZRest, Ziffers } from "zifferjs";
import { Editor } from "../main";
import { Event } from "./Event";
import { Sound } from "./Sound";
import { Note } from "./Note";
import { Rest } from "./Rest";
export class Player extends Event {
input: string;
ziffers: Ziffers;
callTime: number = 0;
played: boolean = false;
current!: Pitch|Chord|ZRest;
retro: boolean = false;
tick: number = 0;
constructor(input: string, options: object, public app: Editor) {
super(app);
this.input = input;
this.ziffers = new Ziffers(input, options);
}
next = (): Pitch|Chord|ZRest => {
this.current = this.ziffers.next() as Pitch|Chord|ZRest;
this.played = true;
return this.current;
}
areWeThereYet = (): boolean => {
const howAboutNow = (this.ziffers.notStarted() || this.app.api.epulse() > this.callTime+(this.current.duration*this.app.api.ppqn()));
if(howAboutNow) {
this.tick = 0;
} else {
this.tick++;
}
return howAboutNow;
}
sound(name: string) {
if(this.areWeThereYet()) {
const event = this.next() as Pitch|Chord|ZRest;
if(event instanceof Pitch) {
// TODO: Quick hack. Select which attributes to use, but some ziffers stuff is needed for chaining key change etc.
const obj = event.getExisting("freq","pitch","key","scale","octave");
return new Sound(obj, this.app).sound(name);
} else if(event instanceof Rest) {
return Rest.createRestProxy(event.duration, this.app);
}
} else {
// Not really a rest, but calling for skipping undefined methods
return Rest.createRestProxy(0, this.app);
}
}
note(value: number|undefined = undefined) {
if(this.areWeThereYet()) {
const event = this.next() as Pitch|Chord|ZRest;
if(event instanceof Pitch) {
// TODO: Quick hack. Select which attributes to use, but some ziffers stuff is needed for chaining key change etc.
const obj = event.getExisting("note","pitch","bend","key","scale","octave");
const note = new Note(obj, this.app);
return value ? note.note(value) : note;
} else if(event instanceof ZRest) {
return Rest.createRestProxy(event.duration, this.app);
}
} else {
// Not really a rest, but calling for skipping undefined methods
return Rest.createRestProxy(0, this.app);
}
}
scale(name: string) {
this.ziffers.scale(name);
return this;
}
key(name: string) {
this.ziffers.key(name);
return this;
}
octave(value: number) {
this.ziffers.octave(value);
return this;
}
retrograde() {
if(this.tick === 0 && this.ziffers.index === 0) {
this.ziffers.retrograde();
}
return this;
}
out = (): void => {
// TODO?
}
}