Skip to content

Commit

Permalink
Documented generators and fixed some bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
amiika committed Dec 16, 2023
1 parent 6ccd493 commit 8f46309
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 31 deletions.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@
<p rel="noopener noreferrer" id="docs_probabilities" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Probabilities</p>
<p rel="noopener noreferrer" id="docs_chaining" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Chaining</p>
<p rel="noopener noreferrer" id="docs_functions" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Functions</p>

<p rel="noopener noreferrer" id="docs_generators" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Generators</p>
<!--
<p rel="noopener noreferrer" id="docs_reference" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Reference</p>
-->
Expand Down
9 changes: 4 additions & 5 deletions src/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ export class UserAPI {
public randomGen = Math.random;
public currentSeed: string | undefined = undefined;
public localSeeds = new Map<string, Function>();
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;
Expand Down Expand Up @@ -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;
Expand All @@ -744,7 +743,7 @@ export class UserAPI {
if(isGenerator(value)) {
if(this.patternCache.has(key)) {
const cachedValue = (this.patternCache.get(key) as Generator<any>).next().value
if(!cachedValue) {
if(cachedValue!==0 && !cachedValue) {
const generator = value as unknown as Generator<any>
this.patternCache.set(key, generator);
return this.maybeToNumber(generator.next().value);
Expand All @@ -758,7 +757,7 @@ export class UserAPI {
} else if(isGeneratorFunction(value)) {
if(this.patternCache.has(key)) {
const cachedValue = (this.patternCache.get(key) as Generator<any>).next().value;
if(cachedValue) {
if(cachedValue || cachedValue===0 || cachedValue===0n) {
return this.maybeToNumber(cachedValue);
} else {
const generator = value();
Expand Down
2 changes: 2 additions & 0 deletions src/Documentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions src/InterfaceLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ export const installInterfaceLogic = (app: Editor) => {
"midi",
"osc",
"functions",
"generators",
"lfos",
"probabilities",
"variables",
Expand Down
41 changes: 23 additions & 18 deletions src/classes/AbstractEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = (instance: T, ...args: any[]) => void;

Expand Down Expand Up @@ -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[]
Expand Down Expand Up @@ -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;
Expand All @@ -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;
};

Expand All @@ -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;
};

Expand Down Expand Up @@ -364,40 +373,35 @@ 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;

cents(values: number|number[], ...rest: number[]) {
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<T>(key: string, value: T | T[] | null): this {
Expand Down Expand Up @@ -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 => {
Expand Down
6 changes: 3 additions & 3 deletions src/classes/MidiEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ export class MidiEvent extends AudibleEvent {
return funcResult;
} else {
func(this.values);
this.update();
return this;
return this.update();
}
};

Expand All @@ -83,7 +82,7 @@ export class MidiEvent extends AudibleEvent {
return this;
};

update = (): void => {
update = (): this => {
const filteredValues = filterObject(this.values, [
"key",
"pitch",
Expand Down Expand Up @@ -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 => {
Expand Down
7 changes: 3 additions & 4 deletions src/classes/SoundEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 => {
Expand Down
3 changes: 3 additions & 0 deletions src/documentation/basics/keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
119 changes: 119 additions & 0 deletions src/documentation/patterns/generators.ts
Original file line number Diff line number Diff line change
@@ -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 <ic>cache(key, function)</ic> 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 <ic>pitch()</ic> or <ic>freq()</ic> or as Ziffers patterns. When playing the values using <ic>pitch()</ic> different scales and chained methods can be used to alter the result, for example <ic>mod(value: number)</ic> to limit the integer range or <ic>scale(name: string)</ic> 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,
)};
`
};

0 comments on commit 8f46309

Please sign in to comment.