From d0f62231d804c4fb51c51afb72a2875afb267afc Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 15 Jan 2024 23:49:04 +0100 Subject: [PATCH] LFO: simplifying arguments and minor corrections --- src/API.ts | 82 +++++++++-------------- src/documentation/patterns/functions.ts | 19 ++++++ src/documentation/patterns/lfos.ts | 20 +++--- src/extensions/NumberExtensions.ts | 88 +++++++++++++++++++------ 4 files changed, 128 insertions(+), 81 deletions(-) diff --git a/src/API.ts b/src/API.ts index 3f183a4..f8ccf60 100644 --- a/src/API.ts +++ b/src/API.ts @@ -1655,8 +1655,6 @@ export class UserAPI { // Low Frequency Oscillators // ============================================================= - public range = (v: number, a: number, b: number): number => v * (b - a) + a; - public line = (start: number, end: number, step: number = 1): number[] => { /** * Returns an array of values between start and end, with a given step. @@ -1688,58 +1686,45 @@ export class UserAPI { return result; }; - public sine = ( - freq: number = 1, - times: number = 1, - offset: number = 0, - ): number => { + public sine = (freq: number = 1, phase: number = 0): number => { /** * Returns a sine wave between -1 and 1. * * @param freq - The frequency of the sine wave - * @param offset - The offset of the sine wave + * @param phase - The phase of the sine wave * @returns A sine wave between -1 and 1 */ - return ( - (Math.sin(this.app.clock.ctx.currentTime * Math.PI * 2 * freq) + offset) * - times - ); + return Math.sin(2 * Math.PI * freq * (this.app.clock.ctx.currentTime - phase)); }; - public usine = ( - freq: number = 1, - times: number = 1, - offset: number = 0, - ): number => { + public usine = (freq: number = 1, phase: number = 0): number => { /** * Returns a sine wave between 0 and 1. * * @param freq - The frequency of the sine wave - * @param offset - The offset of the sine wave + * @param phase - The phase of the sine wave * @returns A sine wave between 0 and 1 * @see sine */ - return ((this.sine(freq, times, offset) + 1) / 2) * times; + return ((this.sine(freq, phase) + 1) / 2); }; - saw = (freq: number = 1, times: number = 1, offset: number = 0): number => { + saw = (freq: number = 1, phase: number = 0): number => { /** * Returns a saw wave between -1 and 1. * * @param freq - The frequency of the saw wave - * @param offset - The offset of the saw wave + * @param phase - The phase of the saw wave * @returns A saw wave between -1 and 1 * @see triangle * @see square * @see sine * @see noise */ - return ( - (((this.app.clock.ctx.currentTime * freq) % 1) * 2 - 1 + offset) * times - ); + return (((this.app.clock.ctx.currentTime * freq + phase) % 1) * 2 - 1); }; - usaw = (freq: number = 1, times: number = 1, offset: number = 0): number => { + usaw = (freq: number = 1, phase: number = 0): number => { /** * Returns a saw wave between 0 and 1. * @@ -1748,14 +1733,10 @@ export class UserAPI { * @returns A saw wave between 0 and 1 * @see saw */ - return ((this.saw(freq, times, offset) + 1) / 2) * times; + return ((this.saw(freq, phase) + 1) / 2); }; - triangle = ( - freq: number = 1, - times: number = 1, - offset: number = 0, - ): number => { + triangle = (freq: number = 1, phase: number = 0): number => { /** * Returns a triangle wave between -1 and 1. * @@ -1765,14 +1746,10 @@ export class UserAPI { * @see sine * @see noise */ - return (Math.abs(this.saw(freq, times, offset)) * 2 - 1) * times; + return (Math.abs(this.saw(freq, phase)) * 2 - 1); }; - utriangle = ( - freq: number = 1, - times: number = 1, - offset: number = 0, - ): number => { + utriangle = (freq: number = 1, phase: number = 0): number => { /** * Returns a triangle wave between 0 and 1. * @@ -1781,13 +1758,11 @@ export class UserAPI { * @returns A triangle wave between 0 and 1 * @see triangle */ - return ((this.triangle(freq, times, offset) + 1) / 2) * times; + return ((this.triangle(freq, phase) + 1) / 2); }; square = ( freq: number = 1, - times: number = 1, - offset: number = 0, duty: number = 0.5, ): number => { /** @@ -1800,16 +1775,11 @@ export class UserAPI { * @see noise */ const period = 1 / freq; - const t = (Date.now() / 1000 + offset) % period; - return (t / period < duty ? 1 : -1) * times; + const t = (Date.now() / 1000 ) % period; + return (t / period < duty ? 1 : -1); }; - usquare = ( - freq: number = 1, - times: number = 1, - offset: number = 0, - duty: number = 0.5, - ): number => { + usquare = (freq: number = 1, duty: number = 0.5): number => { /** * Returns a square wave between 0 and 1. * @@ -1818,10 +1788,10 @@ export class UserAPI { * @returns A square wave between 0 and 1 * @see square */ - return ((this.square(freq, times, offset, duty) + 1) / 2) * times; + return ((this.square(freq, duty) + 1) / 2); }; - noise = (times: number = 1): number => { + noise = (): number => { /** * Returns a random value between -1 and 1. * @@ -1832,7 +1802,17 @@ export class UserAPI { * @see sine * @see noise */ - return (this.randomGen() * 2 - 1) * times; + return (this.randomGen() * 2 - 1); + }; + + unoise = (): number => { + /** + * Returns a random value between 0 and 1. + * + * @returns A random value between 0 and 1 + * @see noise + */ + return ((this.noise() + 1) / 2); }; // ============================================================= diff --git a/src/documentation/patterns/functions.ts b/src/documentation/patterns/functions.ts index f93bd33..8f1c133 100644 --- a/src/documentation/patterns/functions.ts +++ b/src/documentation/patterns/functions.ts @@ -37,6 +37,25 @@ beat(1) :: script(1, 3, 5) - mean(...values: number[]): number: returns the arithmetic mean of a list of numbers. - limit(value: number, min: number, max: number): number: Limits a value between a minimum and a maximum. +### Scaling functions + +There are some very useful scaling methods taken from **SuperCollider**. You can call these on any number: + +- .linlin(inMin: number, inMax: number, outMin: number, outMax: number): scale linearly from one range to another. +- .linexp(inMin: number, inMax: number, outMin: number, outMax: number): scale a linear range to an exponential range. +- .explin(inMin: number, inMax: number, outMin: number, outMax: number): scale an exponential range to a linear range. +- .expexp(inMin: number, inMax: number, outMin: number, outMax: number): scale an exponential range to another exponential range. +- .lincurve(inMin: number, inMax: number, outMin: number, outMax: number, curve: number): scale a number from one range to another following a specific curve. + - curve: number: 0 is linear, < 0 is concave, negatively curved, > 0 is convex, positively curved + +${makeExample( + "Scaling an LFO", + `usine(1/2).linlin(0, 1, 0, 100)`, + true, +)} + + + ## Delay functions - delay(ms: number, func: Function): void: Delays the execution of a function by a given number of milliseconds. diff --git a/src/documentation/patterns/lfos.ts b/src/documentation/patterns/lfos.ts index 04910f9..ab29bc7 100644 --- a/src/documentation/patterns/lfos.ts +++ b/src/documentation/patterns/lfos.ts @@ -8,11 +8,10 @@ export const lfos = (application: Editor): string => { Low Frequency Oscillators (_LFOs_) are an important piece in any digital audio workstation or synthesizer. Topos implements some basic waveforms you can play with to automatically modulate your paremeters. -- sine(freq: number = 1, times: number = 1, offset: number= 0): number: returns a sinusoïdal oscillation between -1 and 1. +- sine(freq: number = 1, phase: number = 0): number: returns a sinusoïdal oscillation between -1 and 1. - freq : frequency in hertz. - - times : output value multiplier. - - offset: linear offset. -- usine(freq: number = 1, times: number = 1, offset: number= 0): number: returns a sinusoïdal oscillation between 0 and 1. The u stands for _unipolar_. + - phase : phase amount (adds or substract from current time point). +- usine(freq: number = 1, phase: number = 0): number: returns a sinusoïdal oscillation between 0 and 1. The u stands for _unipolar_. ${makeExample( "Modulating the speed of a sample player using a sine LFO", @@ -20,8 +19,8 @@ ${makeExample( true, )}; -- triangle(freq: number = 1, times: number = 1, offset: number= 0): number: returns a triangle oscillation between -1 and 1. -- utriangle(freq: number = 1, times: number = 1, offset: number= 0): number: returns a triangle oscillation between 0 and 1. The u stands for _unipolar_. +- triangle(freq: number = 1, phase: number = 0): number: returns a triangle oscillation between -1 and 1. +- utriangle(freq: number = 1, phase: number = 0): number: returns a triangle oscillation between 0 and 1. The u stands for _unipolar_. ${makeExample( "Modulating the speed of a sample player using a triangle LFO", @@ -29,8 +28,8 @@ ${makeExample( true, )} -- saw(freq: number = 1, times: number = 1, offset: number= 0): number: returns a sawtooth-like oscillation between -1 and 1. -- usaw(freq: number = 1, times: number = 1, offset: number= 0): number: returns a sawtooth-like oscillation between 0 and 1. The u stands for _unipolar_. +- saw(freq: number = 1, phase: number = 0): number: returns a sawtooth-like oscillation between -1 and 1. +- usaw(freq: number = 1, phase: number = 0): number: returns a sawtooth-like oscillation between 0 and 1. The u stands for _unipolar_. ${makeExample( "Modulating the speed of a sample player using a saw LFO", @@ -38,8 +37,8 @@ ${makeExample( true, )} -- square(freq: number = 1, times: number = 1, offset: number= 0, duty: number = .5): number: returns a square wave oscillation between -1 and 1. You can also control the duty cycle using the duty parameter. -- usquare(freq: number = 1, times: number = 1, offset: number= 0, duty: number = .5): number: returns a square wave oscillation between 0 and 1. The u stands for _unipolar_. You can also control the duty cycle using the duty parameter. +- square(freq: number = 1, duty: number = .5): number: returns a square wave oscillation between -1 and 1. You can also control the duty cycle using the duty parameter. +- usquare(freq: number = 1, duty: number = .5): number: returns a square wave oscillation between 0 and 1. The u stands for _unipolar_. You can also control the duty cycle using the duty parameter. ${makeExample( "Modulating the speed of a sample player using a square LFO", @@ -48,6 +47,7 @@ ${makeExample( )}; - noise(times: number = 1): returns a random value between -1 and 1. +- unoise(times: number = 1): returns a random value between 0 and 1. ${makeExample( "Modulating the speed of a sample player using noise", diff --git a/src/extensions/NumberExtensions.ts b/src/extensions/NumberExtensions.ts index 6c592cd..2a11f94 100644 --- a/src/extensions/NumberExtensions.ts +++ b/src/extensions/NumberExtensions.ts @@ -25,84 +25,132 @@ declare global { z15(): Player; z16(): Player; midi(): MidiEvent; - sound(name: string): SoundEvent | SkipEvent; + sound(name: string): SoundEvent | SkipEvent, + linlin(a: number, b: number, c: number, d: number): number, + linexp(a: number, b: number, c: number, d: number): number, + explin(a: number, b: number, c: number, d: number): number, + expexp(a: number, b: number, c: number, d: number): number, + lincurve(inMin: number, inMax: number, + outMin: number, outMax: number, + curve: number): number; } } export const makeNumberExtensions = (api: UserAPI) => { - Number.prototype.z0 = function (options: { [key: string]: any } = {}) { + + Number.prototype.linlin = function(a: number, b: number, c: number, d: number) { + if (this.valueOf() < a) return c; + if (this.valueOf() > b) return d; + return (this.valueOf() - a) / (b - a) * (d - c) + c; + }; + + Number.prototype.explin = function(a: number, b: number, c: number, d: number) { + if (this.valueOf() <= a) return c; + if (this.valueOf() >= b) return d; + return (Math.log(this.valueOf() / a)) / (Math.log(b / a)) * (d - c) + c; + }; + + Number.prototype.expexp = function(a: number, b: number, c: number, d: number) { + if (this.valueOf() <= a) return c; + if (this.valueOf() >= b) return d; + return Math.pow(d / c, Math.log(this.valueOf() / a) / Math.log(b / a)) * c; + }; + + Number.prototype.lincurve = function( + inMin: number, inMax: number, + outMin: number, outMax: number, + curve: number) { + if(this.valueOf() <= inMin) return outMin; + if(this.valueOf() >= inMax) return outMax; + if(Math.abs(curve) < 0.001) { + return (this.valueOf() - inMin) / (inMax - inMin) * (outMax - outMin) + outMin; + }; + let grow = Math.exp(curve); + let a = outMax - outMin / (1.0 - grow); + let b = outMin + a; + let scaled = (this.valueOf() - inMin) / (inMax - inMin); + return b - (a * Math.pow(grow, scaled)) + } + + Number.prototype.linexp = function(a: number, b: number, c: number, d: number) { + if (this.valueOf() <= a) return c; + if (this.valueOf() >= b) return d; + return Math.pow(d / c, (this.valueOf() - a) / (b - a)) * c; + }; + + Number.prototype.z0 = function(options: { [key: string]: any } = {}) { return api.z0(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z1 = function (options: { [key: string]: any } = {}) { + Number.prototype.z1 = function(options: { [key: string]: any } = {}) { return api.z1(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z2 = function (options: { [key: string]: any } = {}) { + Number.prototype.z2 = function(options: { [key: string]: any } = {}) { return api.z2(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z3 = function (options: { [key: string]: any } = {}) { + Number.prototype.z3 = function(options: { [key: string]: any } = {}) { return api.z3(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z4 = function (options: { [key: string]: any } = {}) { + Number.prototype.z4 = function(options: { [key: string]: any } = {}) { return api.z4(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z5 = function (options: { [key: string]: any } = {}) { + Number.prototype.z5 = function(options: { [key: string]: any } = {}) { return api.z5(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z6 = function (options: { [key: string]: any } = {}) { + Number.prototype.z6 = function(options: { [key: string]: any } = {}) { return api.z6(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z7 = function (options: { [key: string]: any } = {}) { + Number.prototype.z7 = function(options: { [key: string]: any } = {}) { return api.z7(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z8 = function (options: { [key: string]: any } = {}) { + Number.prototype.z8 = function(options: { [key: string]: any } = {}) { return api.z8(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z9 = function (options: { [key: string]: any } = {}) { + Number.prototype.z9 = function(options: { [key: string]: any } = {}) { return api.z9(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z10 = function (options: { [key: string]: any } = {}) { + Number.prototype.z10 = function(options: { [key: string]: any } = {}) { return api.z10(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z11 = function (options: { [key: string]: any } = {}) { + Number.prototype.z11 = function(options: { [key: string]: any } = {}) { return api.z11(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z12 = function (options: { [key: string]: any } = {}) { + Number.prototype.z12 = function(options: { [key: string]: any } = {}) { return api.z12(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z13 = function (options: { [key: string]: any } = {}) { + Number.prototype.z13 = function(options: { [key: string]: any } = {}) { return api.z13(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z14 = function (options: { [key: string]: any } = {}) { + Number.prototype.z14 = function(options: { [key: string]: any } = {}) { return api.z14(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z15 = function (options: { [key: string]: any } = {}) { + Number.prototype.z15 = function(options: { [key: string]: any } = {}) { return api.z15(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.z16 = function (options: { [key: string]: any } = {}) { + Number.prototype.z16 = function(options: { [key: string]: any } = {}) { return api.z16(this.valueOf().toString().split("").join(" "), options); }; - Number.prototype.midi = function (...kwargs: any[]) { + Number.prototype.midi = function(...kwargs: any[]) { return api.midi(this.valueOf(), ...kwargs); }; - Number.prototype.sound = function (name: string): SoundEvent | SkipEvent { + Number.prototype.sound = function(name: string): SoundEvent | SkipEvent { if (Number.isInteger(this.valueOf())) { return (api.sound(name) as SoundEvent).note(this.valueOf()); } else {