From 6ccd4936f38864ae4f63f086b8ef911a0cc9c206 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Fri, 15 Dec 2023 22:30:21 +0200 Subject: [PATCH 1/3] Fix for docs --- src/API.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/API.ts b/src/API.ts index 3e36155..b070d28 100644 --- a/src/API.ts +++ b/src/API.ts @@ -147,8 +147,8 @@ export class UserAPI { ? code : (this.app.selectedExample as string); } + this.clearPatternCache(); this.stop(); - this.resetAllFromCache(); this.play(); }; @@ -159,6 +159,7 @@ export class UserAPI { current_universe.example.candidate! = ""; current_universe.example.committed! = ""; } + this.clearPatternCache(); this.stop(); }; @@ -168,10 +169,10 @@ export class UserAPI { current_universe.example.candidate! = ""; current_universe.example.committed! = ""; } + this.clearPatternCache(); this.stop(); this.play(); this.app.exampleIsPlaying = true; - this.resetAllFromCache(); evaluateOnce(this.app, code as string); }; From 8f463097bc1616c18b2ed6c6a34ca35c09f7a7cb Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Sat, 16 Dec 2023 03:05:47 +0200 Subject: [PATCH 2/3] Documented generators and fixed some bugs --- index.html | 2 +- src/API.ts | 9 +- src/Documentation.ts | 2 + src/InterfaceLogic.ts | 1 + src/classes/AbstractEvents.ts | 41 ++++---- src/classes/MidiEvent.ts | 6 +- src/classes/SoundEvent.ts | 7 +- src/documentation/basics/keyboard.ts | 3 + src/documentation/patterns/generators.ts | 119 +++++++++++++++++++++++ 9 files changed, 159 insertions(+), 31 deletions(-) create mode 100644 src/documentation/patterns/generators.ts diff --git a/index.html b/index.html index 0449411..5254f88 100644 --- a/index.html +++ b/index.html @@ -205,7 +205,7 @@

Probabilities

Chaining

Functions

- +

Generators

diff --git a/src/API.ts b/src/API.ts index b070d28..4aa0aa2 100644 --- a/src/API.ts +++ b/src/API.ts @@ -87,8 +87,7 @@ export class UserAPI { public randomGen = Math.random; public currentSeed: string | undefined = undefined; public localSeeds = new Map(); - public patternCache = new LRUCache({ max: 1000, ttl: 1000 * 60 * 5 }); - public tempCache = new LRUCache({ max: 1000, ttl: 1000 * 60 * 5 }); + public patternCache = new LRUCache({ max: 10000, ttl: 10000 * 60 * 5 }); public invalidPatterns: {[key: string]: boolean} = {}; public cueTimes: { [key: string]: number } = {}; private errorTimeoutID: number = 0; @@ -725,7 +724,7 @@ export class UserAPI { maybeToNumber = (something: any): number|any => { // If something is BigInt - if(something && typeof something === "bigint") { + if(typeof something === "bigint") { return Number(something); } else { return something; @@ -744,7 +743,7 @@ export class UserAPI { if(isGenerator(value)) { if(this.patternCache.has(key)) { const cachedValue = (this.patternCache.get(key) as Generator).next().value - if(!cachedValue) { + if(cachedValue!==0 && !cachedValue) { const generator = value as unknown as Generator this.patternCache.set(key, generator); return this.maybeToNumber(generator.next().value); @@ -758,7 +757,7 @@ export class UserAPI { } else if(isGeneratorFunction(value)) { if(this.patternCache.has(key)) { const cachedValue = (this.patternCache.get(key) as Generator).next().value; - if(cachedValue) { + if(cachedValue || cachedValue===0 || cachedValue===0n) { return this.maybeToNumber(cachedValue); } else { const generator = value(); diff --git a/src/Documentation.ts b/src/Documentation.ts index 8cf9a61..ab96079 100644 --- a/src/Documentation.ts +++ b/src/Documentation.ts @@ -27,6 +27,7 @@ import { midi } from "./documentation/learning/midi"; import { osc } from "./documentation/learning/osc"; import { patterns } from "./documentation/patterns/patterns"; import { functions } from "./documentation/patterns/functions"; +import { generators } from "./documentation/patterns/generators"; import { variables } from "./documentation/patterns/variables"; import { probabilities } from "./documentation/patterns/probabilities"; import { lfos } from "./documentation/patterns/lfos"; @@ -109,6 +110,7 @@ export const documentation_factory = (application: Editor) => { variables: variables(application), probabilities: probabilities(application), functions: functions(application), + generators: generators(application), shortcuts: shortcuts(application), amplitude: amplitude(application), effects: effects(application), diff --git a/src/InterfaceLogic.ts b/src/InterfaceLogic.ts index e2e39fa..fed0345 100644 --- a/src/InterfaceLogic.ts +++ b/src/InterfaceLogic.ts @@ -512,6 +512,7 @@ export const installInterfaceLogic = (app: Editor) => { "midi", "osc", "functions", + "generators", "lfos", "probabilities", "variables", diff --git a/src/classes/AbstractEvents.ts b/src/classes/AbstractEvents.ts index efbe666..2fa67c7 100644 --- a/src/classes/AbstractEvents.ts +++ b/src/classes/AbstractEvents.ts @@ -9,6 +9,7 @@ import { import { SkipEvent } from "./SkipEvent"; import { SoundParams } from "./SoundEvent"; import { centsToSemitones, edoToSemitones, ratiosToSemitones } from "zifferjs/src/scale"; +import { safeMod } from "zifferjs/src/utils"; export type EventOperation = (instance: T, ...args: any[]) => void; @@ -210,9 +211,14 @@ export class AbstractEvent { * @param func - The function to be applied to the Event * @returns The transformed Event */ - return this.modify(func); + return this.modify(func).update(); }; + mod = (value: number): AbstractEvent => { + this.values.originalPitch = safeMod(this.values.originalPitch, value); + return this.update(); + } + noteLength = ( value: number | number[], ...kwargs: number[] @@ -297,8 +303,7 @@ export abstract class AudibleEvent extends AbstractEvent { this.values["pitch"] = value; this.values["originalPitch"] = value; this.defaultPitchKeyScale(); - this.update(); - return this; + return this.update(); }; pc = this.pitch; @@ -317,8 +322,10 @@ export abstract class AudibleEvent extends AbstractEvent { this.values.key && (this.values.pitch || this.values.pitch === 0) && this.values.parsedScale - ) - this.update(); + ) { + return this.update(); + } + return this; }; @@ -335,8 +342,10 @@ export abstract class AudibleEvent extends AbstractEvent { if ( (this.values.pitch || this.values.pitch === 0) && this.values.parsedScale - ) - this.update(); + ) { + return this.update(); + } + return this; }; @@ -364,16 +373,14 @@ export abstract class AudibleEvent extends AbstractEvent { this.values.parsedScale = value.map((v) => safeScale(v)); } this.defaultPitchKeyScale(); - this.update(); - return this; + return this.update(); }; semitones(values: number|number[], ...rest: number[]) { const scaleValues = typeof values === "number" ? [values, ...rest] : values; this.values.parsedScale = safeScale(scaleValues); this.defaultPitchKeyScale(); - this.update(); - return this; + return this.update(); } steps = this.semitones; @@ -381,23 +388,20 @@ export abstract class AudibleEvent extends AbstractEvent { const scaleValues = typeof values === "number" ? [values, ...rest] : values; this.values.parsedScale = safeScale(centsToSemitones(scaleValues)); this.defaultPitchKeyScale(); - this.update(); - return this; + return this.update(); } ratios(values: number|number[], ...rest: number[]) { const scaleValues = typeof values === "number" ? [values, ...rest] : values; this.values.parsedScale = safeScale(ratiosToSemitones(scaleValues)); this.defaultPitchKeyScale(); - this.update(); - return this; + return this.update(); } edo(value: number, intervals: string|number[] = new Array(value).fill(1)) { this.values.parsedScale = edoToSemitones(value, intervals); this.defaultPitchKeyScale(); - this.update(); - return this; + return this.update(); } protected updateValue(key: string, value: T | T[] | null): this { @@ -498,8 +502,9 @@ export abstract class AudibleEvent extends AbstractEvent { return this; }; - update = (): void => { + update = (): this => { // Overwrite in subclasses + return this; }; cue = (functionName: string|Function): this => { diff --git a/src/classes/MidiEvent.ts b/src/classes/MidiEvent.ts index b59c8c2..5335eff 100644 --- a/src/classes/MidiEvent.ts +++ b/src/classes/MidiEvent.ts @@ -66,8 +66,7 @@ export class MidiEvent extends AudibleEvent { return funcResult; } else { func(this.values); - this.update(); - return this; + return this.update(); } }; @@ -83,7 +82,7 @@ export class MidiEvent extends AudibleEvent { return this; }; - update = (): void => { + update = (): this => { const filteredValues = filterObject(this.values, [ "key", "pitch", @@ -112,6 +111,7 @@ export class MidiEvent extends AudibleEvent { this.values.note = newArrays.note; if (newArrays.bend) this.values.bend = newArrays.bend; + return this; }; out = (): void => { diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index 664aea9..51aadad 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -381,12 +381,11 @@ export class SoundEvent extends AudibleEvent { if (funcResult instanceof Object) return funcResult; else { func(this.values); - this.update(); - return this; + return this.update(); } }; - update = (): void => { + update = (): this => { const filteredValues = filterObject(this.values, [ "key", "pitch", @@ -419,7 +418,7 @@ export class SoundEvent extends AudibleEvent { this.values.pitch = newArrays.pitch; this.values.octave = newArrays.octave; this.values.pitchOctave = newArrays.pitchOctave; - + return this; }; out = (orbit?: number | number[]): void => { diff --git a/src/documentation/basics/keyboard.ts b/src/documentation/basics/keyboard.ts index 8c08219..675b3e9 100644 --- a/src/documentation/basics/keyboard.ts +++ b/src/documentation/basics/keyboard.ts @@ -49,6 +49,9 @@ Topos is made to be controlled entirely with a keyboard. It is recommanded to st |Force Eval|${key_shortcut( "Ctrl + Shift + Enter", )}|Force evaluation of the current script| + |Clear cache & Eval|${key_shortcut( + "Ctrl + Shift + Backspace (Delete)", + )}|Clears cache and forces evaluation to update cached scripts| ### Special diff --git a/src/documentation/patterns/generators.ts b/src/documentation/patterns/generators.ts new file mode 100644 index 0000000..efd9b1c --- /dev/null +++ b/src/documentation/patterns/generators.ts @@ -0,0 +1,119 @@ +import { type Editor } from "../../main"; +import { makeExampleFactory } from "../../Documentation"; + +export const generators = (application: Editor): string => { + const makeExample = makeExampleFactory(application); + return ` +# Generator functions + +JavaScript [generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) are powerful functions for generating value sequences. They can be used to generate melodies, rhythms or control parameters. + +In Topos generator functions should be called using the cache(key, function) function to store the current state of the generator. This function takes two arguments: the name for the cache and the generator instance. + +Once the generator is cached the values will be returned from the named cache even if the generator function is modified. To clear the current cache and to re-evaluate the modified generator use the **Shift+Ctrl+Backspace** shortcut. Alternatively you can cache the modified generator using a different name. + +The resulted values can be played using either pitch() or freq() or as Ziffers patterns. When playing the values using pitch() different scales and chained methods can be used to alter the result, for example mod(value: number) to limit the integer range or scale(name: string) etc. to change the resulting note. + +${makeExample( +"Simple looping generator function", +` +function* simple() { + let x = 0; + while (x < 12) { + yield x+x; + x+=1; + } +} + +beat(.25) && sound("triangle").pitch(cache("simple",simple())).scale("minor").out() +`, +true, +)}; + +${makeExample( +"Infinite frequency generator", +` + function* poly(x=0) { + while (true) { + const s = Math.tan(x/10)+Math.sin(x/20); + yield 2 * Math.pow(s, 3) - 6 * Math.pow(s, 2) + 5 * s + 200; + x++; + } + } + + beat(.125) && sound("triangle").freq(cache("mathyshit",poly())).out() +`, +true, +)}; + +${makeExample( + "Truly scale free chaos inspired by Lorentz attractor", + ` + function* strange(x = 0.1, y = 0, z = 0, rho = 28, beta = 8 / 3, zeta = 10) { + while (true) { + const dx = 10 * (y - x); + const dy = x * (rho - z) - y; + const dz = x * y - beta * z; + + x += dx * 0.01; + y += dy * 0.01; + z += dz * 0.01; + + const value = 300 + 30 * (Math.sin(x) + Math.tan(y) + Math.cos(z)) + yield value; + } + } + + beat(0.25) :: sound("triangle") + .freq(cache("stranger",strange(3,5,2))) + .adsr(.15,.1,.1,.1) + .log("freq").out() + `, + true, +)}; + +## OEIS integer sequences + +To find some inspiration or to enter into a void one can visit [OEIS](https://oeis.org/) to find some interesting integer sequences. Many of the sequences are also included in Topos from [JISG](https://github.com/acerix/jisg/tree/main/src/oeis) (Javascript Integer Sequence Generators) project. + +One of these implemented generators is the Inventory sequence [A342585](https://github.com/acerix/jisg/blob/main/src/oeis/A342585.ts) made famous by [Neil Sloane](https://www.youtube.com/watch?v=rBU9E-ZOZAI). + +Those OEIS sequences implemented by the **JISG** can be referenced directly with the identifiers in the cache function. For example: + +${makeExample( + "Inventory sequence", + ` + rhythm(0.5,[8,7,5,6].bar(4),9) :: sound("triangle") + .pitch(cache("Inventory",A342585)) + .mod(11).scale("minor") + .adsr(.25,.05,.5,.5) + .room(2.0).size(0.5) + .gain(1).out() + `, + true, + )}; + +## Using generators with Ziffers + +Alternatively generators can be used with Ziffers to generate longer patterns. In this case the generator function should be passed as an argument to the ziffers function. Ziffers patterns are cached separately so there is no need for using **cache()** function. Ziffers expects values from the generators to be integers or strings in ziffers syntax. + +${makeExample( + "Ziffers patterns using a generator functions", +` +function* poly(x) { + while (true) { + yield 64 * Math.pow(x, 6) - 480 * Math.pow(x, 4) + 720 * Math.pow(x, 2); + x++; + } + } + +z0(poly(1)).noteLength(0.5).semitones(2,2,3,2,2,2).sound("sine").out() +z1(poly(8)).noteLength(0.25).semitones(2,1,2,1,2,2).sound("sine").out() +z2(poly(-3)).noteLength(1.0).semitones(2,2,2,1,3,2).sound("sine").out() +`, + true, + )}; + + +` +}; From 02d8863039ed1e3e48793277ae90bf5bae0d8ff8 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Sat, 16 Dec 2023 03:35:24 +0200 Subject: [PATCH 3/3] Updated generator docs --- src/documentation/patterns/generators.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/documentation/patterns/generators.ts b/src/documentation/patterns/generators.ts index efd9b1c..ba80105 100644 --- a/src/documentation/patterns/generators.ts +++ b/src/documentation/patterns/generators.ts @@ -6,7 +6,7 @@ export const generators = (application: Editor): string => { return ` # Generator functions -JavaScript [generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) are powerful functions for generating value sequences. They can be used to generate melodies, rhythms or control parameters. +JavaScript generators are powerful functions for generating value sequences. They can be used to generate melodies, rhythms or control parameters. In Topos generator functions should be called using the cache(key, function) function to store the current state of the generator. This function takes two arguments: the name for the cache and the generator instance. @@ -74,11 +74,11 @@ ${makeExample( ## OEIS integer sequences -To find some inspiration or to enter into a void one can visit [OEIS](https://oeis.org/) to find some interesting integer sequences. Many of the sequences are also included in Topos from [JISG](https://github.com/acerix/jisg/tree/main/src/oeis) (Javascript Integer Sequence Generators) project. +To find some inspiration - or to enter into the void - one can visit The On-Line Encyclopedia of Integer Sequences (OEIS) to find some interesting integer sequences. -One of these implemented generators is the Inventory sequence [A342585](https://github.com/acerix/jisg/blob/main/src/oeis/A342585.ts) made famous by [Neil Sloane](https://www.youtube.com/watch?v=rBU9E-ZOZAI). +Many of the sequences are implemented by JISG (Javascript Integer Sequence Generators) project. Those sequences can be referenced directly with the identifiers using the cache function. -Those OEIS sequences implemented by the **JISG** can be referenced directly with the identifiers in the cache function. For example: +One of these implemented generators is the Inventory sequence A342585 made famous by Neil Sloane. ${makeExample( "Inventory sequence",