From 1156b31c0120ab28dc07ab516db4bf7d5cf61728 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Tue, 31 Oct 2023 11:39:27 +0100 Subject: [PATCH 01/10] mockup of polyphony system --- src/classes/SoundEvent.ts | 300 +++++++++++++++++++++----------------- 1 file changed, 165 insertions(+), 135 deletions(-) diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index 80ac9f8..03ddcd6 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -12,6 +12,9 @@ import { // @ts-ignore } from "superdough"; + +type Param = number | number[]; + export type SoundParams = { dur: number; s?: string; @@ -20,76 +23,101 @@ export type SoundParams = { export class SoundEvent extends AudibleEvent { nudge: number; - constructor(sound: string | object, public app: Editor) { + constructor(sound: string | object | (string | object)[], public app: Editor) { super(app); this.nudge = app.dough_nudge / 100; - if (typeof sound === "string") { - if (sound.includes(":")) { - this.values = { - s: sound.split(":")[0], - n: sound.split(":")[1], - dur: app.clock.convertPulseToSecond(app.clock.ppqn), - analyze: true, - }; - } else { - this.values = { s: sound, dur: 0.5, analyze: true }; - } + if (Array.isArray(sound)) { + this.values = sound.map(s => this.initializeSound(s)); } else { - this.values = sound; + this.values = this.initializeSound(sound); } } - private updateValue(key: string, value: T): this { - this.values[key] = value; + private initializeSound(sound: string | object): object { + if (typeof sound === "string") { + if (sound.includes(":")) { + return { + s: sound.split(":")[0], + n: sound.split(":")[1], + dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn), + analyze: true, + }; + } else { + return { s: sound, dur: 0.5, analyze: true }; + } + } else { + return sound; + } + } + + public updateValue = (key: string, value: T): this => { + if (Array.isArray(this.values)) { + if (Array.isArray(value)) { + let originalLength = this.values.length; + for (let i = 0; i < value.length; i++) { + if (i >= originalLength) { + let newObject = { ...this.values[i % originalLength] }; + this.values.push(newObject); + } + this.values[i][key] = value[i]; + } + } else { + this.values.forEach(obj => obj[key] = value); + } + } else { + this.values[key] = value; + } return this; } + // ================================================================================ // ZZFX Sound Parameters // ================================================================================ - public volume = (value: number) => this.updateValue("volume", value); + + public volume = (value: Param) => this.updateValue("volume", value); public vol = this.volume; - public zrand = (value: number) => this.updateValue("zrand", value); - public curve = (value: number) => this.updateValue("curve", value); - public slide = (value: number) => this.updateValue("slide", value); + public zrand = (value: Param) => this.updateValue("zrand", value); + public curve = (value: Param) => this.updateValue("curve", value); + public slide = (value: Param) => this.updateValue("slide", value); public sld = this.slide; - public deltaSlide = (value: number) => this.updateValue("deltaSlide", value); + public deltaSlide = (value: number | number[]) => this.updateValue("deltaSlide", value); 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) => + public lfo = (value: Param) => this.updateValue("lfo", value); + public znoise = (value: Param) => this.updateValue("znoise", value); + public noise = (value: Param) => this.updateValue("noise", value); + public zmod = (value: Param) => this.updateValue("zmod", value); + public zcrush = (value: Param) => this.updateValue("zcrush", value); + public zdelay = (value: Param) => this.updateValue("zdelay", value); + public sustainVolume = (value: Param) => 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); + public tremolo = (value: Param) => this.updateValue("tremolo", value); + public dur = (value: Param) => this.updateValue("dur", value); + public zzfx = (value: Param[]) => 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 fmi = (value: Param) => this.updateValue("fmi", value); + public fmh = (value: Param) => this.updateValue("fmh", value); public fmenv = (value: "lin" | "exp") => this.updateValue("fmenv", value); - public fmattack = (value: number) => this.updateValue("fmattack", value); + public fmattack = (value: Param) => this.updateValue("fmattack", value); public fmatk = this.fmattack; - public fmdecay = (value: number) => this.updateValue("fmdecay", value); + public fmdecay = (value: Param) => this.updateValue("fmdecay", value); public fmdec = this.fmdecay; - public fmsustain = (value: number) => this.updateValue("fmsustain", value); + public fmsustain = (value: Param) => this.updateValue("fmsustain", value); public fmsus = this.fmsustain; - public fmrelease = (value: number) => this.updateValue("fmrelease", value); + public fmrelease = (value: Param) => this.updateValue("fmrelease", value); public fmrel = this.fmrelease; - public fmvelocity = (value: number) => this.updateValue("fmvelocity", value); + public fmvelocity = (value: Param) => this.updateValue("fmvelocity", value); public fmvel = this.fmvelocity; public fmwave = (value: "sine" | "triangle" | "sawtooth" | "square") => this.updateValue("fmwave", value); @@ -97,25 +125,25 @@ export class SoundEvent extends AudibleEvent { // Filter type public ftype = (value: "12db" | "24db") => this.updateValue("ftype", value); - public fanchor = (value: number) => this.updateValue("fanchor", value); + public fanchor = (value: Param) => this.updateValue("fanchor", value); // Amplitude Envelope - public attack = (value: number) => this.updateValue("attack", value); + public attack = (value: Param) => this.updateValue("attack", value); public atk = this.attack; - public decay = (value: number) => this.updateValue("decay", value); + public decay = (value: Param) => this.updateValue("decay", value); public dec = this.decay; - public sustain = (value: number) => this.updateValue("sustain", value); + public sustain = (value: Param) => this.updateValue("sustain", value); public sus = this.sustain; - public release = (value: number) => this.updateValue("release", value); + public release = (value: Param) => this.updateValue("release", value); public rel = this.release; - public adsr = (a: number, d: number, s: number, r: number) => { + public adsr = (a: Param, d: Param, s: Param, r: Param) => { this.attack(a); this.decay(d); this.sustain(s); this.release(r); return this; }; - public ad = (a: number, d: number) => { + public ad = (a: Param, d: Param) => { this.attack(a); this.decay(d); this.sustain(0.0); @@ -124,17 +152,17 @@ export class SoundEvent extends AudibleEvent { }; // 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 lpenv = (value: Param) => this.updateValue("lpenv", value); + public lpe = (value: Param) => this.updateValue("lpenv", value); + public lpattack = (value: Param) => this.updateValue("lpattack", value); public lpa = this.lpattack; - public lpdecay = (value: number) => this.updateValue("lpdecay", value); + public lpdecay = (value: Param) => this.updateValue("lpdecay", value); public lpd = this.lpdecay; - public lpsustain = (value: number) => this.updateValue("lpsustain", value); + public lpsustain = (value: Param) => this.updateValue("lpsustain", value); public lps = this.lpsustain; - public lprelease = (value: number) => this.updateValue("lprelease", value); + public lprelease = (value: Param) => this.updateValue("lprelease", value); public lpr = this.lprelease; - public cutoff = (value: number, resonance?: number) => { + public cutoff = (value: Param, resonance?: Param) => { this.updateValue("cutoff", value); if (resonance) { this.resonance(resonance) @@ -142,7 +170,7 @@ export class SoundEvent extends AudibleEvent { return this; } public lpf = this.cutoff; - public resonance = (value: number) => { + public resonance = (value: Param | number) => { if (value >= 0 && value <= 1) { this.updateValue( "resonance", @@ -153,11 +181,11 @@ export class SoundEvent extends AudibleEvent { } public lpq = this.resonance; public lpadsr = ( - depth: number, - a: number, - d: number, - s: number, - r: number + depth: Param, + a: Param, + d: Param, + s: Param, + r: Param ) => { this.lpenv(depth); this.lpattack(a); @@ -167,9 +195,9 @@ export class SoundEvent extends AudibleEvent { return this; }; public lpad = ( - depth: number, - a: number, - d: number, + depth: Param, + a: Param, + d: Param, ) => { this.lpenv(depth); this.lpattack(a); @@ -182,19 +210,19 @@ export class SoundEvent extends AudibleEvent { // 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 hpenv = (value: Param) => this.updateValue("hpenv", value); + public hpe = (value: Param) => this.updateValue("hpe", value); + public hpattack = (value: Param) => this.updateValue("hpattack", value); public hpa = this.hpattack; - public hpdecay = (value: number) => this.updateValue("hpdecay", value); + public hpdecay = (value: Param) => this.updateValue("hpdecay", value); public hpd = this.hpdecay; - public hpsustain = (value: number) => this.updateValue("hpsustain", value); + public hpsustain = (value: Param) => this.updateValue("hpsustain", value); public hpsus = this.hpsustain; - public hprelease = (value: number) => this.updateValue("hprelease", value); + public hprelease = (value: Param) => this.updateValue("hprelease", value); public hpr = this.hprelease; - public hcutoff = (value: number) => this.updateValue("hcutoff", value); + public hcutoff = (value: Param) => this.updateValue("hcutoff", value); public hpf = this.hcutoff; - public hresonance = (value: number, resonance?: number) => { + public hresonance = (value: Param, resonance?: Param) => { this.updateValue("hresonance", value); if (resonance) { this.resonance(resonance) @@ -203,11 +231,11 @@ export class SoundEvent extends AudibleEvent { } public hpq = this.hresonance; public hpadsr = ( - depth: number, - a: number, - d: number, - s: number, - r: number + depth: Param, + a: Param, + d: Param, + s: Param, + r: Param ) => { this.hpenv(depth); this.hpattack(a); @@ -217,9 +245,9 @@ export class SoundEvent extends AudibleEvent { return this; }; public hpad = ( - depth: number, - a: number, - d: number, + depth: Param, + a: Param, + d: Param, ) => { this.hpenv(depth); this.hpattack(a); @@ -231,17 +259,17 @@ export class SoundEvent extends AudibleEvent { // 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 bpenv = (value: Param) => this.updateValue("bpenv", value); + public bpe = (value: Param) => this.updateValue("bpe", value); + public bpattack = (value: Param) => this.updateValue("bpattack", value); public bpa = this.bpattack; - public bpdecay = (value: number) => this.updateValue("bpdecay", value); + public bpdecay = (value: Param) => this.updateValue("bpdecay", value); public bpd = this.bpdecay; - public bpsustain = (value: number) => this.updateValue("bpsustain", value); + public bpsustain = (value: Param) => this.updateValue("bpsustain", value); public bps = this.bpsustain; - public bprelease = (value: number) => this.updateValue("bprelease", value); + public bprelease = (value: Param) => this.updateValue("bprelease", value); public bpr = this.bprelease; - public bandf = (value: number, resonance?: number) => { + public bandf = (value: Param, resonance?: Param) => { this.updateValue("bandf", value); if (resonance) { this.resonance(resonance) @@ -249,14 +277,14 @@ export class SoundEvent extends AudibleEvent { return this; } public bpf = this.bandf; - public bandq = (value: number) => this.updateValue("bandq", value); + public bandq = (value: Param) => this.updateValue("bandq", value); public bpq = this.bandq; public bpadsr = ( - depth: number, - a: number, - d: number, - s: number, - r: number + depth: Param, + a: Param, + d: Param, + s: Param, + r: Param ) => { this.bpenv(depth); this.bpattack(a); @@ -266,9 +294,9 @@ export class SoundEvent extends AudibleEvent { return this; }; public bpad = ( - depth: number, - a: number, - d: number, + depth: Param, + a: Param, + d: Param, ) => { this.bpenv(depth); this.bpattack(a); @@ -279,11 +307,11 @@ export class SoundEvent extends AudibleEvent { }; - public freq = (value: number) => this.updateValue("freq", value); + public freq = (value: Param) => 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) => { + public vib = (value: Param) => this.updateValue("vib", value); + public vibmod = (value: Param) => this.updateValue("vibmod", value); + public fm = (value: Param | string) => { if (typeof value === "number") { this.values["fmi"] = value; } else { @@ -295,22 +323,22 @@ export class SoundEvent extends AudibleEvent { }; // 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); + public loop = (value: Param) => this.updateValue("loop", value); + public loopBegin = (value: Param) => this.updateValue("loopBegin", value); + public loopEnd = (value: Param) => this.updateValue("loopEnd", value); + public begin = (value: Param) => this.updateValue("begin", value); + public end = (value: Param) => this.updateValue("end", value); // Gain management - public gain = (value: number) => this.updateValue("gain", value); - public dbgain = (value: number) => + public gain = (value: Param) => this.updateValue("gain", value); + public dbgain = (value: Param) => this.updateValue("gain", Math.min(Math.pow(10, value / 20), 10)); public db = this.dbgain; - public velocity = (value: number) => this.updateValue("velocity", value); + public velocity = (value: Param) => this.updateValue("velocity", value); public vel = this.velocity; // Panoramic control (stereo) - public pan = (value: number) => this.updateValue("pan", value); + public pan = (value: Param) => this.updateValue("pan", value); // Frequency management @@ -353,10 +381,10 @@ export class SoundEvent extends AudibleEvent { } }; 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 cut = (value: Param) => this.updateValue("cut", value); + public clip = (value: Param) => this.updateValue("clip", value); + public n = (value: Param) => this.updateValue("n", value); + public note = (value: Param | string | null) => { if (typeof value === "string") { return this.updateValue("note", noteNameToMidi(value)); } else if (typeof value == null || value == undefined) { @@ -365,41 +393,41 @@ export class SoundEvent extends AudibleEvent { return this.updateValue("note", value); } }; - public speed = (value: number) => this.updateValue("speed", value); + public speed = (value: Param) => 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 coarse = (value: Param) => this.updateValue("coarse", value); + public crush = (value: Param) => this.updateValue("crush", value); + public shape = (value: Param) => this.updateValue("shape", value); + public vowel = (value: Param) => this.updateValue("vowel", value); public vow = this.vowel; // Delay control - public delay = (value: number) => this.updateValue("delay", value); + public delay = (value: Param) => this.updateValue("delay", value); public del = this.delay; - public delayfeedback = (value: number) => + public delayfeedback = (value: Param) => this.updateValue("delayfeedback", value); public delayfb = this.delayfeedback; - public delaytime = (value: number) => this.updateValue("delaytime", value); + public delaytime = (value: Param) => this.updateValue("delaytime", value); public delayt = this.delaytime; // Orbit management - public orbit = (value: number) => this.updateValue("orbit", value); + public orbit = (value: Param) => this.updateValue("orbit", value); public o = this.orbit; // Reverb management - public room = (value: number) => this.updateValue("room", value); + public room = (value: Param) => this.updateValue("room", value); public rm = this.room; - public roomfade = (value: number) => this.updateValue("roomfade", value); + public roomfade = (value: Param) => this.updateValue("roomfade", value); public rfade = this.roomfade; - public roomlp = (value: number) => this.updateValue("roomlp", value); + public roomlp = (value: Param) => this.updateValue("roomlp", value); public rlp = this.roomlp; - public roomdim = (value: number) => this.updateValue("roomdim", value); + public roomdim = (value: Param) => this.updateValue("roomdim", value); public rdim = this.roomdim; - public size = (value: number) => this.updateValue("roomsize", value); + public size = (value: Param) => this.updateValue("roomsize", value); public sz = this.size; - public rev = (room: number, size: number, fade?: number, lp?: number, dim?: number) => { + public rev = (room: Param, size: Param, fade?: Param, lp?: Param, dim?: Param) => { this.updateValue("room", room) this.updateValue("roomsize", size) if (fade) @@ -413,21 +441,21 @@ export class SoundEvent extends AudibleEvent { } // Compressor - public comp = (value: number) => this.updateValue("compressor", value); + public comp = (value: Param) => this.updateValue("compressor", value); public cmp = this.comp; - public ratio = (value: number) => this.updateValue("compressorRatio", value); + public ratio = (value: Param) => this.updateValue("compressorRatio", value); public rt = this.ratio; - public knee = (value: number) => this.updateValue("compressorKnee", value); + public knee = (value: Param) => this.updateValue("compressorKnee", value); public kn = this.knee; - public compAttack = (value: number) => + public compAttack = (value: Param) => this.updateValue("compressorAttack", value); public cmpa = this.compAttack; - public compRelease = (value: number) => + public compRelease = (value: Param) => this.updateValue("compressorRelease", value); public cmpr = this.compRelease; // Unit - public stretch = (beat: number) => { + public stretch = (beat: Param) => { this.updateValue("unit", "c"); this.updateValue("speed", 1 / beat); this.updateValue("cut", beat); @@ -459,14 +487,16 @@ export class SoundEvent extends AudibleEvent { }; out = (): void => { - if (this.values.chord) { - this.values.chord.forEach((obj: { [key: string]: number }) => { - const copy = { ...this.values }; - copy.freq = obj.freq; - superdough(copy, this.nudge, this.values.dur); + console.log(this.values) + if (Array.isArray(this.values)) { + this.values.forEach((soundObj) => { + superdough(soundObj, this.nudge, soundObj.dur); }); } else { superdough(this.values, this.nudge, this.values.dur); } }; + + + } From 04dbac2cd712e197feb81e952c43c4e08e5b548e Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Tue, 31 Oct 2023 19:38:56 +0100 Subject: [PATCH 02/10] Revert "mockup of polyphony system" This reverts commit 1156b31c0120ab28dc07ab516db4bf7d5cf61728. --- src/classes/SoundEvent.ts | 284 +++++++++++++++++--------------------- 1 file changed, 127 insertions(+), 157 deletions(-) diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index 03ddcd6..80ac9f8 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -12,9 +12,6 @@ import { // @ts-ignore } from "superdough"; - -type Param = number | number[]; - export type SoundParams = { dur: number; s?: string; @@ -23,101 +20,76 @@ export type SoundParams = { export class SoundEvent extends AudibleEvent { nudge: number; - constructor(sound: string | object | (string | object)[], public app: Editor) { + constructor(sound: string | object, public app: Editor) { super(app); this.nudge = app.dough_nudge / 100; - if (Array.isArray(sound)) { - this.values = sound.map(s => this.initializeSound(s)); - } else { - this.values = this.initializeSound(sound); - } - } - - private initializeSound(sound: string | object): object { if (typeof sound === "string") { if (sound.includes(":")) { - return { + this.values = { s: sound.split(":")[0], n: sound.split(":")[1], - dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn), + dur: app.clock.convertPulseToSecond(app.clock.ppqn), analyze: true, }; } else { - return { s: sound, dur: 0.5, analyze: true }; + this.values = { s: sound, dur: 0.5, analyze: true }; } } else { - return sound; + this.values = sound; } } - public updateValue = (key: string, value: T): this => { - if (Array.isArray(this.values)) { - if (Array.isArray(value)) { - let originalLength = this.values.length; - for (let i = 0; i < value.length; i++) { - if (i >= originalLength) { - let newObject = { ...this.values[i % originalLength] }; - this.values.push(newObject); - } - this.values[i][key] = value[i]; - } - } else { - this.values.forEach(obj => obj[key] = value); - } - } else { - this.values[key] = value; - } + private updateValue(key: string, value: T): this { + this.values[key] = value; return this; } - // ================================================================================ // ZZFX Sound Parameters // ================================================================================ - - public volume = (value: Param) => this.updateValue("volume", value); + public volume = (value: number) => this.updateValue("volume", value); public vol = this.volume; - public zrand = (value: Param) => this.updateValue("zrand", value); - public curve = (value: Param) => this.updateValue("curve", value); - public slide = (value: Param) => this.updateValue("slide", value); + public zrand = (value: number) => this.updateValue("zrand", value); + public curve = (value: number) => this.updateValue("curve", value); + public slide = (value: number) => this.updateValue("slide", value); public sld = this.slide; - public deltaSlide = (value: number | number[]) => this.updateValue("deltaSlide", value); + public deltaSlide = (value: number) => this.updateValue("deltaSlide", value); 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: Param) => this.updateValue("lfo", value); - public znoise = (value: Param) => this.updateValue("znoise", value); - public noise = (value: Param) => this.updateValue("noise", value); - public zmod = (value: Param) => this.updateValue("zmod", value); - public zcrush = (value: Param) => this.updateValue("zcrush", value); - public zdelay = (value: Param) => this.updateValue("zdelay", value); - public sustainVolume = (value: Param) => + 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: Param) => this.updateValue("tremolo", value); - public dur = (value: Param) => this.updateValue("dur", value); - public zzfx = (value: Param[]) => this.updateValue("zzfx", 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: Param) => this.updateValue("fmi", value); - public fmh = (value: Param) => this.updateValue("fmh", value); + 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: Param) => this.updateValue("fmattack", value); + public fmattack = (value: number) => this.updateValue("fmattack", value); public fmatk = this.fmattack; - public fmdecay = (value: Param) => this.updateValue("fmdecay", value); + public fmdecay = (value: number) => this.updateValue("fmdecay", value); public fmdec = this.fmdecay; - public fmsustain = (value: Param) => this.updateValue("fmsustain", value); + public fmsustain = (value: number) => this.updateValue("fmsustain", value); public fmsus = this.fmsustain; - public fmrelease = (value: Param) => this.updateValue("fmrelease", value); + public fmrelease = (value: number) => this.updateValue("fmrelease", value); public fmrel = this.fmrelease; - public fmvelocity = (value: Param) => this.updateValue("fmvelocity", value); + public fmvelocity = (value: number) => this.updateValue("fmvelocity", value); public fmvel = this.fmvelocity; public fmwave = (value: "sine" | "triangle" | "sawtooth" | "square") => this.updateValue("fmwave", value); @@ -125,25 +97,25 @@ export class SoundEvent extends AudibleEvent { // Filter type public ftype = (value: "12db" | "24db") => this.updateValue("ftype", value); - public fanchor = (value: Param) => this.updateValue("fanchor", value); + public fanchor = (value: number) => this.updateValue("fanchor", value); // Amplitude Envelope - public attack = (value: Param) => this.updateValue("attack", value); + public attack = (value: number) => this.updateValue("attack", value); public atk = this.attack; - public decay = (value: Param) => this.updateValue("decay", value); + public decay = (value: number) => this.updateValue("decay", value); public dec = this.decay; - public sustain = (value: Param) => this.updateValue("sustain", value); + public sustain = (value: number) => this.updateValue("sustain", value); public sus = this.sustain; - public release = (value: Param) => this.updateValue("release", value); + public release = (value: number) => this.updateValue("release", value); public rel = this.release; - public adsr = (a: Param, d: Param, s: Param, r: Param) => { + 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: Param, d: Param) => { + public ad = (a: number, d: number) => { this.attack(a); this.decay(d); this.sustain(0.0); @@ -152,17 +124,17 @@ export class SoundEvent extends AudibleEvent { }; // Lowpass filter - public lpenv = (value: Param) => this.updateValue("lpenv", value); - public lpe = (value: Param) => this.updateValue("lpenv", value); - public lpattack = (value: Param) => this.updateValue("lpattack", value); + 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: Param) => this.updateValue("lpdecay", value); + public lpdecay = (value: number) => this.updateValue("lpdecay", value); public lpd = this.lpdecay; - public lpsustain = (value: Param) => this.updateValue("lpsustain", value); + public lpsustain = (value: number) => this.updateValue("lpsustain", value); public lps = this.lpsustain; - public lprelease = (value: Param) => this.updateValue("lprelease", value); + public lprelease = (value: number) => this.updateValue("lprelease", value); public lpr = this.lprelease; - public cutoff = (value: Param, resonance?: Param) => { + public cutoff = (value: number, resonance?: number) => { this.updateValue("cutoff", value); if (resonance) { this.resonance(resonance) @@ -170,7 +142,7 @@ export class SoundEvent extends AudibleEvent { return this; } public lpf = this.cutoff; - public resonance = (value: Param | number) => { + public resonance = (value: number) => { if (value >= 0 && value <= 1) { this.updateValue( "resonance", @@ -181,11 +153,11 @@ export class SoundEvent extends AudibleEvent { } public lpq = this.resonance; public lpadsr = ( - depth: Param, - a: Param, - d: Param, - s: Param, - r: Param + depth: number, + a: number, + d: number, + s: number, + r: number ) => { this.lpenv(depth); this.lpattack(a); @@ -195,9 +167,9 @@ export class SoundEvent extends AudibleEvent { return this; }; public lpad = ( - depth: Param, - a: Param, - d: Param, + depth: number, + a: number, + d: number, ) => { this.lpenv(depth); this.lpattack(a); @@ -210,19 +182,19 @@ export class SoundEvent extends AudibleEvent { // Highpass filter - public hpenv = (value: Param) => this.updateValue("hpenv", value); - public hpe = (value: Param) => this.updateValue("hpe", value); - public hpattack = (value: Param) => this.updateValue("hpattack", value); + 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: Param) => this.updateValue("hpdecay", value); + public hpdecay = (value: number) => this.updateValue("hpdecay", value); public hpd = this.hpdecay; - public hpsustain = (value: Param) => this.updateValue("hpsustain", value); + public hpsustain = (value: number) => this.updateValue("hpsustain", value); public hpsus = this.hpsustain; - public hprelease = (value: Param) => this.updateValue("hprelease", value); + public hprelease = (value: number) => this.updateValue("hprelease", value); public hpr = this.hprelease; - public hcutoff = (value: Param) => this.updateValue("hcutoff", value); + public hcutoff = (value: number) => this.updateValue("hcutoff", value); public hpf = this.hcutoff; - public hresonance = (value: Param, resonance?: Param) => { + public hresonance = (value: number, resonance?: number) => { this.updateValue("hresonance", value); if (resonance) { this.resonance(resonance) @@ -231,11 +203,11 @@ export class SoundEvent extends AudibleEvent { } public hpq = this.hresonance; public hpadsr = ( - depth: Param, - a: Param, - d: Param, - s: Param, - r: Param + depth: number, + a: number, + d: number, + s: number, + r: number ) => { this.hpenv(depth); this.hpattack(a); @@ -245,9 +217,9 @@ export class SoundEvent extends AudibleEvent { return this; }; public hpad = ( - depth: Param, - a: Param, - d: Param, + depth: number, + a: number, + d: number, ) => { this.hpenv(depth); this.hpattack(a); @@ -259,17 +231,17 @@ export class SoundEvent extends AudibleEvent { // Bandpass filter - public bpenv = (value: Param) => this.updateValue("bpenv", value); - public bpe = (value: Param) => this.updateValue("bpe", value); - public bpattack = (value: Param) => this.updateValue("bpattack", value); + 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: Param) => this.updateValue("bpdecay", value); + public bpdecay = (value: number) => this.updateValue("bpdecay", value); public bpd = this.bpdecay; - public bpsustain = (value: Param) => this.updateValue("bpsustain", value); + public bpsustain = (value: number) => this.updateValue("bpsustain", value); public bps = this.bpsustain; - public bprelease = (value: Param) => this.updateValue("bprelease", value); + public bprelease = (value: number) => this.updateValue("bprelease", value); public bpr = this.bprelease; - public bandf = (value: Param, resonance?: Param) => { + public bandf = (value: number, resonance?: number) => { this.updateValue("bandf", value); if (resonance) { this.resonance(resonance) @@ -277,14 +249,14 @@ export class SoundEvent extends AudibleEvent { return this; } public bpf = this.bandf; - public bandq = (value: Param) => this.updateValue("bandq", value); + public bandq = (value: number) => this.updateValue("bandq", value); public bpq = this.bandq; public bpadsr = ( - depth: Param, - a: Param, - d: Param, - s: Param, - r: Param + depth: number, + a: number, + d: number, + s: number, + r: number ) => { this.bpenv(depth); this.bpattack(a); @@ -294,9 +266,9 @@ export class SoundEvent extends AudibleEvent { return this; }; public bpad = ( - depth: Param, - a: Param, - d: Param, + depth: number, + a: number, + d: number, ) => { this.bpenv(depth); this.bpattack(a); @@ -307,11 +279,11 @@ export class SoundEvent extends AudibleEvent { }; - public freq = (value: Param) => this.updateValue("freq", value); + public freq = (value: number) => this.updateValue("freq", value); public f = this.freq; - public vib = (value: Param) => this.updateValue("vib", value); - public vibmod = (value: Param) => this.updateValue("vibmod", value); - public fm = (value: Param | string) => { + 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 { @@ -323,22 +295,22 @@ export class SoundEvent extends AudibleEvent { }; // Sampler looping - public loop = (value: Param) => this.updateValue("loop", value); - public loopBegin = (value: Param) => this.updateValue("loopBegin", value); - public loopEnd = (value: Param) => this.updateValue("loopEnd", value); - public begin = (value: Param) => this.updateValue("begin", value); - public end = (value: Param) => this.updateValue("end", value); + 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: Param) => this.updateValue("gain", value); - public dbgain = (value: Param) => + 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: Param) => this.updateValue("velocity", value); + public velocity = (value: number) => this.updateValue("velocity", value); public vel = this.velocity; // Panoramic control (stereo) - public pan = (value: Param) => this.updateValue("pan", value); + public pan = (value: number) => this.updateValue("pan", value); // Frequency management @@ -381,10 +353,10 @@ export class SoundEvent extends AudibleEvent { } }; public snd = this.sound; - public cut = (value: Param) => this.updateValue("cut", value); - public clip = (value: Param) => this.updateValue("clip", value); - public n = (value: Param) => this.updateValue("n", value); - public note = (value: Param | string | null) => { + 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) => { if (typeof value === "string") { return this.updateValue("note", noteNameToMidi(value)); } else if (typeof value == null || value == undefined) { @@ -393,41 +365,41 @@ export class SoundEvent extends AudibleEvent { return this.updateValue("note", value); } }; - public speed = (value: Param) => this.updateValue("speed", value); + public speed = (value: number) => this.updateValue("speed", value); public spd = this.speed; // Creative sampler effects - public coarse = (value: Param) => this.updateValue("coarse", value); - public crush = (value: Param) => this.updateValue("crush", value); - public shape = (value: Param) => this.updateValue("shape", value); - public vowel = (value: Param) => this.updateValue("vowel", value); + 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: Param) => this.updateValue("delay", value); + public delay = (value: number) => this.updateValue("delay", value); public del = this.delay; - public delayfeedback = (value: Param) => + public delayfeedback = (value: number) => this.updateValue("delayfeedback", value); public delayfb = this.delayfeedback; - public delaytime = (value: Param) => this.updateValue("delaytime", value); + public delaytime = (value: number) => this.updateValue("delaytime", value); public delayt = this.delaytime; // Orbit management - public orbit = (value: Param) => this.updateValue("orbit", value); + public orbit = (value: number) => this.updateValue("orbit", value); public o = this.orbit; // Reverb management - public room = (value: Param) => this.updateValue("room", value); + public room = (value: number) => this.updateValue("room", value); public rm = this.room; - public roomfade = (value: Param) => this.updateValue("roomfade", value); + public roomfade = (value: number) => this.updateValue("roomfade", value); public rfade = this.roomfade; - public roomlp = (value: Param) => this.updateValue("roomlp", value); + public roomlp = (value: number) => this.updateValue("roomlp", value); public rlp = this.roomlp; - public roomdim = (value: Param) => this.updateValue("roomdim", value); + public roomdim = (value: number) => this.updateValue("roomdim", value); public rdim = this.roomdim; - public size = (value: Param) => this.updateValue("roomsize", value); + public size = (value: number) => this.updateValue("roomsize", value); public sz = this.size; - public rev = (room: Param, size: Param, fade?: Param, lp?: Param, dim?: Param) => { + public rev = (room: number, size: number, fade?: number, lp?: number, dim?: number) => { this.updateValue("room", room) this.updateValue("roomsize", size) if (fade) @@ -441,21 +413,21 @@ export class SoundEvent extends AudibleEvent { } // Compressor - public comp = (value: Param) => this.updateValue("compressor", value); + public comp = (value: number) => this.updateValue("compressor", value); public cmp = this.comp; - public ratio = (value: Param) => this.updateValue("compressorRatio", value); + public ratio = (value: number) => this.updateValue("compressorRatio", value); public rt = this.ratio; - public knee = (value: Param) => this.updateValue("compressorKnee", value); + public knee = (value: number) => this.updateValue("compressorKnee", value); public kn = this.knee; - public compAttack = (value: Param) => + public compAttack = (value: number) => this.updateValue("compressorAttack", value); public cmpa = this.compAttack; - public compRelease = (value: Param) => + public compRelease = (value: number) => this.updateValue("compressorRelease", value); public cmpr = this.compRelease; // Unit - public stretch = (beat: Param) => { + public stretch = (beat: number) => { this.updateValue("unit", "c"); this.updateValue("speed", 1 / beat); this.updateValue("cut", beat); @@ -487,16 +459,14 @@ export class SoundEvent extends AudibleEvent { }; out = (): void => { - console.log(this.values) - if (Array.isArray(this.values)) { - this.values.forEach((soundObj) => { - superdough(soundObj, this.nudge, soundObj.dur); + if (this.values.chord) { + this.values.chord.forEach((obj: { [key: string]: number }) => { + const copy = { ...this.values }; + copy.freq = obj.freq; + superdough(copy, this.nudge, this.values.dur); }); } else { superdough(this.values, this.nudge, this.values.dur); } }; - - - } From 35b758e9a7254c1916415a6868b6daae0fe9aa6e Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Wed, 1 Nov 2023 00:48:43 +0100 Subject: [PATCH 03/10] beginning refactoring of soundevent --- src/classes/SoundEvent.ts | 642 +++++++++++++++----------------------- 1 file changed, 251 insertions(+), 391 deletions(-) diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index 80ac9f8..ecb67ad 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -20,9 +20,258 @@ export type SoundParams = { export class SoundEvent extends AudibleEvent { nudge: number; + 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); + }, + 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.resonance(resonance); + } + return this; + }, + lpf: (value: number, resonance?: number) => { + this.updateValue("cutoff", value); + if (resonance) { + this.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)); + }, + db: (value: number) => { + this.updateValue("gain", Math.min(Math.pow(10, value / 20), 10)); + }, + 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"], + size: (value: number) => { + this.updateValue("roomsize", value); + }, + sz: (value: number) => { + this.updateValue("roomsize", value); + }, + comp: ["compressor", "cmp"], + ratio: (value: number) => { + this.updateValue("compressorRatio", value); + }, + knee: (value: number) => { + this.updateValue("compressorKnee", value); + }, + compAttack: (value: number) => { + this.updateValue("compressorAttack", value); + }, + compRelease: (value: number) => { + this.updateValue("compressorRelease", value); + }, + stretch: (beat: number) => { + this.updateValue("unit", "c"); + this.updateValue("speed", 1 / beat); + this.updateValue("cut", beat); + return this; + }, + }; + constructor(sound: string | object, public app: Editor) { super(app); this.nudge = app.dough_nudge / 100; + + 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; + } + } + if (typeof sound === "string") { if (sound.includes(":")) { this.values = { @@ -39,401 +288,12 @@ export class SoundEvent extends AudibleEvent { } } - private updateValue(key: string, value: T): this { + private updateValue(key: string, value: T | T[] | null): this { + if (value == null) return this; this.values[key] = value; return this; } - // ================================================================================ - // ZZFX Sound Parameters - // ================================================================================ - - public volume = (value: number) => this.updateValue("volume", value); - public vol = this.volume; - public zrand = (value: number) => this.updateValue("zrand", value); - public curve = (value: number) => this.updateValue("curve", value); - public slide = (value: number) => this.updateValue("slide", value); - public sld = this.slide; - public deltaSlide = (value: number) => this.updateValue("deltaSlide", value); - 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; - public resonance = (value: number) => { - if (value >= 0 && value <= 1) { - this.updateValue( - "resonance", - 50 * value - ); - } - return this; - } - public lpq = this.resonance; - public lpadsr = ( - depth: number, - a: number, - d: number, - 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; - }; - - - // 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); - value = chord.map((note: number) => { - 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) => { - if (this.values.chord) { - let notes = this.values.chord.map( - (obj: { [key: string]: number }) => obj.note - ); - notes = howMany < 0 ? [...notes].reverse() : notes; - for (let i = 0; i < Math.abs(howMany); i++) { - notes[i % notes.length] += howMany <= 0 ? -12 : 12; - } - const chord = notes.map((note: number) => { - return { note: note, freq: midiToFreq(note) }; - }); - return this.updateValue("chord", chord); - } else { - 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) => { - if (typeof value === "string") { - return this.updateValue("note", noteNameToMidi(value)); - } else if (typeof value == null || value == undefined) { - return this.updateValue("note", 0).updateValue("gain", 0); - } else { - 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 // ================================================================================ From 1620bffe5ca7f0119266ef8830d226486fa48373 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Wed, 1 Nov 2023 01:01:18 +0100 Subject: [PATCH 04/10] trying some weird trick --- src/classes/SoundEvent.ts | 49 +++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index ecb67ad..bad52aa 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -12,6 +12,8 @@ import { // @ts-ignore } from "superdough"; +type EventObj = { [key: string]: number | number[] }; + export type SoundParams = { dur: number; s?: string; @@ -86,14 +88,14 @@ export class SoundEvent extends AudibleEvent { cutoff: (value: number, resonance?: number) => { this.updateValue("cutoff", value); if (resonance) { - this.resonance(resonance); + this.updateValue("resonance", resonance); } return this; }, lpf: (value: number, resonance?: number) => { this.updateValue("cutoff", value); if (resonance) { - this.resonance(resonance); + this.updateValue("resonance", resonance); } return this; }, @@ -318,15 +320,42 @@ export class SoundEvent extends AudibleEvent { this.values.freq = midiToFreq(note); }; + generateEvents = (input: EventObj): EventObj[] => { + const keys = Object.keys(input); + const maxLength = Math.max( + ...keys.map((k) => + Array.isArray(input[k]) ? (input[k] as number[]).length : 1 + ) + ); + + const output: EventObj[] = []; + + for (let i = 0; i < maxLength; i++) { + const event: EventObj = {}; + for (const k of keys) { + if (Array.isArray(input[k])) { + // @ts-ignore + event[k] = input[k][i % (input[k] as number[]).length]; + } else { + event[k] = input[k]; + } + } + output.push(event); + } + + return output; + }; + out = (): void => { - if (this.values.chord) { - this.values.chord.forEach((obj: { [key: string]: number }) => { - const copy = { ...this.values }; - copy.freq = obj.freq; - superdough(copy, this.nudge, this.values.dur); - }); - } else { - superdough(this.values, this.nudge, this.values.dur); + const input = this.values.chord || this.values; + const events = this.generateEvents(input); + console.log(events); + for (const event of events) { + superdough( + { ...event, ...{ freq: event.freq, dur: this.values.dur } }, + this.nudge, + this.values.dur + ); } }; } From 3cde5f220735bb88c6901cbc46c4cdb91e1bc2fa Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Wed, 1 Nov 2023 10:59:06 +0100 Subject: [PATCH 05/10] fixing a few bugs --- src/classes/SoundEvent.ts | 58 +++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index bad52aa..79b3b4a 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -19,6 +19,13 @@ export type SoundParams = { s?: string; }; +type ValuesType = { + s: string | string[]; + n?: string | string[]; + dur: number; + analyze: boolean; +}; + export class SoundEvent extends AudibleEvent { nudge: number; @@ -59,6 +66,7 @@ export class SoundEvent extends AudibleEvent { fmad: (a: number, d: number) => { this.updateValue("fmattack", a); this.updateValue("fmdecay", d); + return this; }, ftype: ["ftype"], fanchor: ["fanchor"], @@ -209,9 +217,11 @@ export class SoundEvent extends AudibleEvent { 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"], @@ -233,22 +243,28 @@ export class SoundEvent extends AudibleEvent { roomdim: ["roomdim", "rdim"], 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"); @@ -258,7 +274,8 @@ export class SoundEvent extends AudibleEvent { }, }; - constructor(sound: string | object, public app: Editor) { + + constructor(sound: string | string[] | object, public app: Editor) { super(app); this.nudge = app.dough_nudge / 100; @@ -273,23 +290,42 @@ export class SoundEvent extends AudibleEvent { this[methodName] = keys; } } + this.values = this.processSound(sound); + } - if (typeof sound === "string") { - if (sound.includes(":")) { - this.values = { - s: sound.split(":")[0], - n: sound.split(":")[1], - dur: app.clock.convertPulseToSecond(app.clock.ppqn), - analyze: true, + private processSound = (sound: string | string[] | object): ValuesType => { + if (Array.isArray(sound)) { + const s: string[] = []; + const n: string[] = []; + sound.forEach(str => { + const parts = str.split(":"); + s.push(parts[0]); + if (parts[1]) { + n.push(parts[1]); + } + }); + return { + s, + n: n.length > 0 ? n : undefined, + dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn), + analyze: true + }; + } else { + if ((sound as string).includes(":")) { + const [s, n] = (sound as string).split(":"); + return { + s, + n, + dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn), + analyze: true }; } else { - this.values = { s: sound, dur: 0.5, analyze: true }; + return { s: sound, dur: 0.5, analyze: true }; } - } else { - this.values = sound; } } + private updateValue(key: string, value: T | T[] | null): this { if (value == null) return this; this.values[key] = value; From 988fba7157d5ef75f42405caa49a488cccc5ea71 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Wed, 1 Nov 2023 11:06:26 +0100 Subject: [PATCH 06/10] restore chords and notes --- src/classes/SoundEvent.ts | 49 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index 79b3b4a..d919f82 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -382,6 +382,55 @@ export class SoundEvent extends AudibleEvent { return output; }; + public chord = ( + value: string | object[] | number[] | number, + ...kwargs: number[] + ) => { + if (typeof value === "string") { + const chord = parseChord(value); + value = chord.map((note: number) => { + 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) => { + if (this.values.chord) { + let notes = this.values.chord.map( + (obj: { [key: string]: number }) => obj.note + ); + notes = howMany < 0 ? [...notes].reverse() : notes; + for (let i = 0; i < Math.abs(howMany); i++) { + notes[i % notes.length] += howMany <= 0 ? -12 : 12; + } + const chord = notes.map((note: number) => { + return { note: note, freq: midiToFreq(note) }; + }); + return this.updateValue("chord", chord); + } else { + return this; + } + }; + public note = (value: number | string | null) => { + if (typeof value === "string") { + return this.updateValue("note", noteNameToMidi(value)); + } else if (typeof value == null || value == undefined) { + return this.updateValue("note", 0).updateValue("gain", 0); + } else { + return this.updateValue("note", value); + } + }; + + + out = (): void => { const input = this.values.chord || this.values; const events = this.generateEvents(input); From 269df3f8995b8326b164cf8cc78395b1ce4ffb51 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Wed, 1 Nov 2023 13:12:02 +0100 Subject: [PATCH 07/10] Fixing Ziffers for monophonic melodic expressions --- src/classes/SoundEvent.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index d919f82..9f19a85 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -241,6 +241,7 @@ export class SoundEvent extends AudibleEvent { roomfade: ["roomfade", "rfade"], roomlp: ["roomlp", "rlp"], roomdim: ["roomdim", "rdim"], + sound: ["sound", "s"], size: (value: number) => { this.updateValue("roomsize", value); return this; @@ -310,9 +311,17 @@ export class SoundEvent extends AudibleEvent { dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn), analyze: true }; + } else if (typeof sound === 'object') { + console.log(sound) + const validatedObj: ValuesType = { + dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn), + analyze: true, + ...sound as Partial + }; + return validatedObj; } else { - if ((sound as string).includes(":")) { - const [s, n] = (sound as string).split(":"); + if (sound.includes(":")) { + const [s, n] = sound.split(":"); return { s, n, From a09e3a76be64d47a825dc6c1982cba7a24e4150b Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Fri, 3 Nov 2023 16:07:39 +0200 Subject: [PATCH 08/10] Refactor object mangling and add midi support --- src/API.ts | 30 +++----- src/Utils/Generic.ts | 70 ++++++++++++++++++ src/classes/AbstractEvents.ts | 46 ++++++++++-- src/classes/MidiEvent.ts | 99 +++++++++++++++----------- src/classes/RestEvent.ts | 4 +- src/classes/SoundEvent.ts | 130 +++++++++++++--------------------- src/classes/ZPlayer.ts | 24 ++++--- 7 files changed, 242 insertions(+), 161 deletions(-) create mode 100644 src/Utils/Generic.ts diff --git a/src/API.ts b/src/API.ts index c6c4008..7854c10 100644 --- a/src/API.ts +++ b/src/API.ts @@ -9,7 +9,7 @@ import { tryEvaluate, evaluateOnce } from "./Evaluator"; import { DrunkWalk } from "./Utils/Drunk"; import { Editor } from "./main"; import { SoundEvent } from "./classes/SoundEvent"; -import { MidiEvent } from "./classes/MidiEvent"; +import { MidiEvent, MidiParams } from "./classes/MidiEvent"; import { LRUCache } from "lru-cache"; import { InputOptions, Player } from "./classes/ZPlayer"; import { @@ -390,9 +390,10 @@ export class UserAPI { }; public midi = ( - value: number | object = 60, - velocity?: number, - channel?: number + value: number | number[] = 60, + velocity?: number | number[], + channel?: number | number[], + port?: number | string | number[] | string[] ): MidiEvent => { /** * Sends a MIDI note to the current MIDI output. @@ -402,24 +403,9 @@ export class UserAPI { * { channel: 0, velocity: 100, duration: 0.5 } */ - if (velocity !== undefined) { - // Check if value is of type number - if (typeof value === "number") { - value = { note: value }; - } - // @ts-ignore - value["velocity"] = velocity; - } + const event = {note: value, velocity, channel, port} as MidiParams - if (channel !== undefined) { - if (typeof value === "number") { - value = { note: value }; - } - // @ts-ignore - value["channel"] = channel; - } - - return new MidiEvent(value, this.app); + return new MidiEvent(event, this.app); }; public sysex = (data: Array): void => { @@ -1893,7 +1879,7 @@ export class UserAPI { // Trivial functions // ============================================================= - sound = (sound: string | object) => { + sound = (sound: string | string[]) => { return new SoundEvent(sound, this.app); }; diff --git a/src/Utils/Generic.ts b/src/Utils/Generic.ts new file mode 100644 index 0000000..15578d1 --- /dev/null +++ b/src/Utils/Generic.ts @@ -0,0 +1,70 @@ +/* + * Transforms object with arrays into array of objects + * + * @param {Record} input - Object with arrays + * @param {string[]} ignoredKeys - Keys to ignore + * @returns {Record[]} Array of objects + * + */ +export function objectWithArraysToArrayOfObjects(input: Record, ignoredKeys: string[]): Record[] { + 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[] = []; + + for (let i = 0; i < maxLength; i++) { + const event: Record = {}; + 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[]} array - Array of objects + * @param {Record} mergeObject - Object that is merged to each object in the array + * @returns {object} Merged object with arrays + * + */ +export function arrayOfObjectsToObjectWithArrays>(array: T[], mergeObject: Record = {}): Record { + 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); + } + + /* + * Filter certain keys from object + * + * @param {Record} obj - Object to filter + * @param {string[]} filter - Keys to filter + * @returns {object} Filtered object + * + */ + export function filterObject(obj: Record, filter: string[]): Record { + return Object.fromEntries(Object.entries(obj).filter(([key]) => filter.includes(key))); + } \ No newline at end of file diff --git a/src/classes/AbstractEvents.ts b/src/classes/AbstractEvents.ts index 6eab352..29d80dd 100644 --- a/src/classes/AbstractEvents.ts +++ b/src/classes/AbstractEvents.ts @@ -204,8 +204,11 @@ export abstract class Event { return this.modify(func); }; - length = (value: number): Event => { - this.values["length"] = value; + noteLength = (value: number): Event => { + /** + * This function is used to set the note length of the Event. + */ + this.values["noteLength"] = value; return this; }; } @@ -215,30 +218,63 @@ export abstract class AudibleEvent extends Event { super(app); } + pitch = (value: number): this => { + /* + * This function is used to set the pitch of the Event. + * @param value - The pitch value + * @returns The Event + */ + this.values["pitch"] = value; + if(this.values.key && this.values.parsedScale) this.update(); + return this; + } + + pc = this.pitch; + octave = (value: number): this => { + /* + * This function is used to set the octave of the Event. + * @param value - The octave value + * @returns The Event + */ this.values["octave"] = value; - this.update(); + if(this.values.key && this.values.pitch && this.values.parsedScale) this.update(); return this; }; key = (value: string): this => { + /* + * This function is used to set the key of the Event. + * @param value - The key value + * @returns The Event + */ this.values["key"] = value; - this.update(); + if(this.values.pitch && this.values.parsedScale) this.update(); return this; }; scale = (value: string): this => { + /* + * This function is used to set the scale of the Event. + * @param value - The scale value + * @returns The Event + */ if (!isScale(value)) { this.values.parsedScale = parseScala(value) as number[]; } else { this.values.scaleName = value; this.values.parsedScale = getScale(value) as number[]; } - this.update(); + if(this.values.key && this.values.pitch) this.update(); return this; }; freq = (value: number): this => { + /* + * This function is used to set the frequency of the Event. + * @param value - The frequency value + * @returns The Event + */ this.values["freq"] = value; const midiNote = freqToMidi(value); if (midiNote % 1 !== 0) { diff --git a/src/classes/MidiEvent.ts b/src/classes/MidiEvent.ts index 1294d57..5fd0c5b 100644 --- a/src/classes/MidiEvent.ts +++ b/src/classes/MidiEvent.ts @@ -1,7 +1,8 @@ import { AudibleEvent } from "./AbstractEvents"; import { type Editor } from "../main"; 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 = { note: number; @@ -9,40 +10,49 @@ export type MidiParams = { channel?: number; port?: number; sustain?: number; + velocity?: number; } export class MidiEvent extends AudibleEvent { midiConnection: MidiConnection; - constructor(input: number | object, public app: Editor) { + constructor(input: MidiParams, public app: Editor) { super(app); - if (typeof input === "number") this.values["note"] = input; - else this.values = input; + this.values = input; this.midiConnection = app.api.MidiConnection; } - chord = (value: MidiParams[]): this => { - this.values["chord"] = value; - return this; + public chord = (value: string) => { + this.values.note = parseChord(value); + return this; }; - note = (value: number): this => { + note = (value: number | number[]): this => { this.values["note"] = value; return this; }; - sustain = (value: number): this => { + sustain = (value: number | number[]): this => { this.values["sustain"] = value; 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; return this; }; - port = (value: number | string): this => { - this.values["port"] = this.midiConnection.getMidiOutputIndex(value); + port = (value: number | string | number[] | string[]): this => { + 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; }; @@ -75,37 +85,46 @@ export class MidiEvent extends AudibleEvent { }; 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; + // Get key, pitch, parsedScale and octave from this.values object + const filteredValues = filterObject(this.values, ["key", "pitch", "parsedScale", "octave"]); + + const events = objectWithArraysToArrayOfObjects(filteredValues,["parsedScale"]); + + events.forEach((event) => { + const [note, bend] = 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; + 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 => { - function play(event: MidiEvent, params?: MidiParams): void { - const paramChannel = params && params.channel ? params.channel : 0; - const channel = event.values.channel ? event.values.channel : paramChannel; - const velocity = event.values.velocity ? event.values.velocity : 100; - const paramNote = params && params.note ? params.note : 60; - const note = event.values.note ? event.values.note : paramNote; + function play(event: MidiEvent, params: MidiParams): void { + const channel = params.channel ? params.channel : 0; + const velocity = params.velocity ? params.velocity : 100; + const note = params.note ? params.note : 60; - const sustain = event.values.sustain - ? event.values.sustain * + const sustain = params.sustain + ? params.sustain * 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 port = event.values.port - ? event.midiConnection.getMidiOutputIndex(event.values.port) - : event.midiConnection.getCurrentMidiPortIndex(); + const bend = params.bend ? params.bend : undefined; + const port = params.port + ? event.midiConnection.getMidiOutputIndex(params.port) + : event.midiConnection.getCurrentMidiPortIndex() || 0; + event.midiConnection.sendMidiNote( note, channel, @@ -116,13 +135,11 @@ export class MidiEvent extends AudibleEvent { ); } - if(this.values.chord) { - this.values.chord.forEach((p: MidiParams) => { - play(this, p); - }); - } else { - play(this); - } + const events = objectWithArraysToArrayOfObjects(this.values,["parsedScale"]) as MidiParams[]; + + events.forEach((p: MidiParams) => { + play(this,p); + }); }; } diff --git a/src/classes/RestEvent.ts b/src/classes/RestEvent.ts index 4f4ec53..862d2d5 100644 --- a/src/classes/RestEvent.ts +++ b/src/classes/RestEvent.ts @@ -4,11 +4,11 @@ import { Event } from "./AbstractEvents"; export class RestEvent extends Event { constructor(length: number, app: Editor) { super(app); - this.values["length"] = length; + this.values["noteLength"] = length; } _fallbackMethod = (): Event => { - return RestEvent.createRestProxy(this.values["length"], this.app); + return RestEvent.createRestProxy(this.values["noteLength"], this.app); }; public static createRestProxy = ( diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index 9f19a85..4c753d3 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -1,5 +1,6 @@ import { type Editor } from "../main"; import { AudibleEvent } from "./AbstractEvents"; +import { filterObject, arrayOfObjectsToObjectWithArrays, objectWithArraysToArrayOfObjects } from "../Utils/Generic"; import { chord as parseChord, midiToFreq, @@ -12,22 +13,23 @@ import { // @ts-ignore } from "superdough"; -type EventObj = { [key: string]: number | number[] }; - export type SoundParams = { - dur: number; - s?: string; -}; - -type ValuesType = { - s: string | string[]; - n?: string | string[]; - dur: number; - analyze: boolean; + dur: number | number[]; + 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 { nudge: number; + sound: any; private methodMap = { volume: ["volume", "vol"], @@ -276,7 +278,7 @@ export class SoundEvent extends AudibleEvent { }; - constructor(sound: string | string[] | object, public app: Editor) { + constructor(sound: string | string[] | SoundParams, public app: Editor) { super(app); this.nudge = app.dough_nudge / 100; @@ -294,15 +296,15 @@ export class SoundEvent extends AudibleEvent { this.values = this.processSound(sound); } - private processSound = (sound: string | string[] | object): ValuesType => { - if (Array.isArray(sound)) { + private processSound = (sound: string | string[] | SoundParams | SoundParams[]): SoundParams => { + if (Array.isArray(sound) && typeof sound[0] === 'string') { const s: string[] = []; - const n: string[] = []; + const n: number[] = []; sound.forEach(str => { - const parts = str.split(":"); + const parts = (str as string).split(":"); s.push(parts[0]); if (parts[1]) { - n.push(parts[1]); + n.push(parseInt(parts[1])); } }); return { @@ -312,16 +314,17 @@ export class SoundEvent extends AudibleEvent { analyze: true }; } else if (typeof sound === 'object') { - console.log(sound) - const validatedObj: ValuesType = { + const validatedObj: SoundParams = { dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn), analyze: true, - ...sound as Partial + ...sound as Partial }; return validatedObj; } else { if (sound.includes(":")) { - const [s, n] = sound.split(":"); + const vals = sound.split(":"); + const s = vals[0]; + const n = parseInt(vals[1]); return { s, n, @@ -334,8 +337,7 @@ export class SoundEvent extends AudibleEvent { } } - - private updateValue(key: string, value: T | T[] | null): this { + private updateValue(key: string, value: T | T[] | SoundParams[] | null): this { if (value == null) return this; this.values[key] = value; return this; @@ -356,61 +358,31 @@ export class SoundEvent extends AudibleEvent { }; 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 filteredValues = filterObject(this.values, ["key", "pitch", "parsedScale", "octave"]); + const events = objectWithArraysToArrayOfObjects(filteredValues,["parsedScale"]); + + events.forEach((event) => { + 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; + event.freq = midiToFreq(note); + }); + + const newArrays = arrayOfObjectsToObjectWithArrays(events) as SoundParams; + + this.values.note = newArrays.note; + this.values.freq = newArrays.freq; }; - generateEvents = (input: EventObj): EventObj[] => { - const keys = Object.keys(input); - const maxLength = Math.max( - ...keys.map((k) => - Array.isArray(input[k]) ? (input[k] as number[]).length : 1 - ) - ); - - const output: EventObj[] = []; - - for (let i = 0; i < maxLength; i++) { - const event: EventObj = {}; - for (const k of keys) { - if (Array.isArray(input[k])) { - // @ts-ignore - event[k] = input[k][i % (input[k] as number[]).length]; - } else { - event[k] = input[k]; - } - } - output.push(event); - } - - return output; - }; - - public chord = ( - value: string | object[] | number[] | number, - ...kwargs: number[] - ) => { - if (typeof value === "string") { + public chord = (value: string) => { const chord = parseChord(value); - value = chord.map((note: number) => { - 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); + return this.updateValue("note", chord); }; + public invert = (howMany: number = 0) => { if (this.values.chord) { let notes = this.values.chord.map( @@ -438,17 +410,13 @@ export class SoundEvent extends AudibleEvent { } }; - - out = (): void => { - const input = this.values.chord || this.values; - const events = this.generateEvents(input); - console.log(events); + const events = objectWithArraysToArrayOfObjects(this.values,["parsedScale"]); for (const event of events) { superdough( - { ...event, ...{ freq: event.freq, dur: this.values.dur } }, + event, this.nudge, - this.values.dur + event.dur ); } }; diff --git a/src/classes/ZPlayer.ts b/src/classes/ZPlayer.ts index 85fb3ba..57a87fb 100644 --- a/src/classes/ZPlayer.ts +++ b/src/classes/ZPlayer.ts @@ -5,6 +5,7 @@ import { SkipEvent } from "./SkipEvent"; import { SoundEvent, SoundParams } from "./SoundEvent"; import { MidiEvent, MidiParams } from "./MidiEvent"; import { RestEvent } from "./RestEvent"; +import { arrayOfObjectsToObjectWithArrays } from "../Utils/Generic"; export type InputOptions = { [key: string]: string | number }; @@ -139,37 +140,39 @@ export class Player extends Event { }; sound(name?: string) { - if (this.areWeThereYet()) { const event = this.next() as Pitch | Chord | ZRest; const noteLengthInSeconds = this.app.clock.convertPulseToSecond(event.duration*4*this.app.clock.ppqn); if (event instanceof Pitch) { const obj = event.getExisting( "freq", + "note", "pitch", "key", "scale", "octave", "parsedScale" - ); + ) as SoundParams; 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; return new SoundEvent(obj, this.app).sound(name || "sine"); } else if (event instanceof Chord) { const pitches = event.pitches.map((p) => { return p.getExisting( "freq", + "note", "pitch", "key", "scale", "octave", "parsedScale" ); - }); - const sound: SoundParams = {dur: noteLengthInSeconds}; - if(name) sound.s = name; - return new SoundEvent(sound, this.app).chord(pitches); + }) as SoundParams[]; + const add = { dur: noteLengthInSeconds } as SoundParams; + if(name) add.s = name; + let sound = arrayOfObjectsToObjectWithArrays(pitches,add) as SoundParams; + return new SoundEvent(sound, this.app); } else if (event instanceof ZRest) { return RestEvent.createRestProxy(event.duration, this.app); } @@ -189,16 +192,17 @@ export class Player extends Event { "scale", "octave", "parsedScale", - ); + ) as MidiParams; 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); return value ? note.note(value) : note; } else if (event instanceof ZRest) { return RestEvent.createRestProxy(event.duration, this.app); } else if (event instanceof Chord) { 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 { return SkipEvent.createSkipProxy(); From f39625a985b9fb2a772264f0071704af2c6917fd Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Fri, 3 Nov 2023 17:09:53 +0200 Subject: [PATCH 09/10] Fix for some keys and added some kwargs --- src/Utils/Generic.ts | 1 + src/classes/AbstractEvents.ts | 84 +++++++++++++++++++++++++---------- 2 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/Utils/Generic.ts b/src/Utils/Generic.ts index 15578d1..d6e91e6 100644 --- a/src/Utils/Generic.ts +++ b/src/Utils/Generic.ts @@ -7,6 +7,7 @@ * */ export function objectWithArraysToArrayOfObjects(input: Record, ignoredKeys: string[]): Record[] { + 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) => diff --git a/src/classes/AbstractEvents.ts b/src/classes/AbstractEvents.ts index 29d80dd..f486ed8 100644 --- a/src/classes/AbstractEvents.ts +++ b/src/classes/AbstractEvents.ts @@ -2,9 +2,7 @@ import { type Editor } from "../main"; import { freqToMidi, resolvePitchBend, - getScale, - isScale, - parseScala, + safeScale } from "zifferjs"; export abstract class Event { @@ -204,11 +202,20 @@ export abstract class Event { return this.modify(func); }; - noteLength = (value: number): Event => { + noteLength = (value: number | number[], ...kwargs: number[]): Event => { /** * This function is used to set the note length of the Event. */ - this.values["noteLength"] = value; + 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; }; } @@ -218,12 +225,15 @@ export abstract class AudibleEvent extends Event { super(app); } - pitch = (value: number): this => { + pitch = (value: number | number[], ...kwargs: number[]): this => { /* * This function is used to set the pitch of the Event. * @param value - The pitch value * @returns The Event */ + if(kwargs.length > 0) { + value = (Array.isArray(value) ? value.concat(kwargs) : [value, ...kwargs]); + } this.values["pitch"] = value; if(this.values.key && this.values.parsedScale) this.update(); return this; @@ -231,57 +241,85 @@ export abstract class AudibleEvent extends Event { pc = this.pitch; - octave = (value: number): this => { + 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.parsedScale) this.update(); + if(this.values.key && (this.values.pitch || this.values.pitch === 0) && this.values.parsedScale) this.update(); return this; }; - key = (value: string): 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.parsedScale) this.update(); + if((this.values.pitch || this.values.pitch === 0) && this.values.parsedScale) this.update(); return this; }; - scale = (value: string): 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 (!isScale(value)) { - this.values.parsedScale = parseScala(value) as number[]; - } else { - this.values.scaleName = value; - this.values.parsedScale = getScale(value) as number[]; + 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(); } - if(this.values.key && this.values.pitch) this.update(); return this; }; - freq = (value: number): 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; - const midiNote = freqToMidi(value); - if (midiNote % 1 !== 0) { - this.values["note"] = Math.floor(midiNote); - this.values["bend"] = resolvePitchBend(midiNote)[1]; + if(Array.isArray(value)) { + this.values["note"] = []; + this.values["bend"] = []; + 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 { - 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; }; From d8230c7cd103cb0c21ef35c6660fdabacfeac797 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Fri, 3 Nov 2023 17:45:32 +0200 Subject: [PATCH 10/10] Added null to sound() --- src/API.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/API.ts b/src/API.ts index 7854c10..62f2cb4 100644 --- a/src/API.ts +++ b/src/API.ts @@ -28,6 +28,7 @@ import { import { Speaker } from "./StringExtensions"; import { getScaleNotes } from "zifferjs"; import { OscilloscopeConfig, blinkScript } from "./AudioVisualisation"; +import { SkipEvent } from './classes/SkipEvent'; interface ControlChange { channel: number; @@ -1879,8 +1880,9 @@ export class UserAPI { // Trivial functions // ============================================================= - sound = (sound: string | string[]) => { - return new SoundEvent(sound, this.app); + sound = (sound: string | string[] | null | undefined) => { + if(sound) return new SoundEvent(sound, this.app); + else return new SkipEvent(); }; snd = this.sound;