Merge branch 'main' of github.com:Bubobubobubobubo/Topos

This commit is contained in:
2023-11-11 13:55:41 +01:00
12 changed files with 470 additions and 293 deletions

View File

@ -1,5 +1,5 @@
import { EditorView } from "@codemirror/view";
import { getAllScaleNotes, seededRandom } from "zifferjs";
import { getAllScaleNotes, nearScales, seededRandom } from "zifferjs";
import {
MidiCCEvent,
MidiConnection,
@ -25,7 +25,7 @@ import {
soundMap,
// @ts-ignore
} from "superdough";
import { Speaker } from "./StringExtensions";
import { Speaker } from "./extensions/StringExtensions";
import { getScaleNotes } from "zifferjs";
import { OscilloscopeConfig, blinkScript } from "./AudioVisualisation";
import { SkipEvent } from "./classes/SkipEvent";
@ -681,8 +681,12 @@ export class UserAPI {
this.patternCache.forEach((player) => (player as Player).reset());
};
public removePatternFromCache = (id: string): void => {
this.patternCache.delete(id);
};
public z = (
input: string,
input: string | Generator<number>,
options: InputOptions = {},
id: number | string = ""
): Player => {
@ -693,7 +697,7 @@ export class UserAPI {
if (this.app.api.patternCache.has(key)) {
player = this.app.api.patternCache.get(key) as Player;
if (player.input !== input) {
if (typeof input === "string" && player.input !== input) {
player = undefined;
}
}
@ -703,6 +707,10 @@ export class UserAPI {
this.app.api.patternCache.set(key, player);
}
if(player.ziffers.generator && player.ziffers.generatorDone) {
this.removePatternFromCache(key);
}
if (typeof id === "number") player.zid = zid;
player.updateLastCallTime();
@ -1832,6 +1840,8 @@ export class UserAPI {
scale = getScaleNotes;
nearScales = nearScales;
rate = (rate: number): void => {
rate = rate;
// TODO: Implement this. This function should change the rate at which the global script

View File

@ -1,230 +0,0 @@
import { noteNameToMidi } from "zifferjs";
import { type UserAPI } from "./API";
import { Player } from "./classes/ZPlayer";
export { };
// Extend String prototype
declare global {
interface String {
speak(): void;
rate(speed: number): string;
pitch(pitch: number): string;
volume(volume: number): string;
voice(voice: number): string;
lang(language: string): string;
options(): SpeechOptions;
z(): Player;
z0(): Player;
z1(): Player;
z2(): Player;
z3(): Player;
z4(): Player;
z5(): Player;
z6(): Player;
z7(): Player;
z8(): Player;
z9(): Player;
z10(): Player;
z11(): Player;
z12(): Player;
z13(): Player;
z14(): Player;
z15(): Player;
z16(): Player;
note(): number;
}
}
const isJsonString = (str: string): boolean => {
return str[0] === '{' && str[str.length - 1] === '}'
}
const stringObject = (str: string, params: object) => {
if (isJsonString(str)) {
const obj = JSON.parse(str);
return JSON.stringify({ ...obj, ...params });
} else {
return JSON.stringify({ ...params, text: str });
}
}
export const makeStringExtensions = (api: UserAPI) => {
String.prototype.speak = function() {
const options = JSON.parse(this.valueOf());
new Speaker({ ...options, text: options.text }).speak().then(() => {
// Done
}).catch((e) => {
console.log("Error speaking:", e);
});
};
String.prototype.rate = function(speed: number) {
return stringObject(this.valueOf(), { rate: speed });
};
String.prototype.pitch = function(pitch: number) {
return stringObject(this.valueOf(), { pitch: pitch });
};
String.prototype.lang = function(language: string) {
return stringObject(this.valueOf(), { lang: language });
};
String.prototype.volume = function(volume: number) {
return stringObject(this.valueOf(), { volume: volume });
};
String.prototype.voice = function(voice: number) {
return stringObject(this.valueOf(), { voice: voice });
};
String.prototype.z = function(options: { [key: string]: any } = {}) {
return api.z(this.valueOf(), options);
};
String.prototype.z0 = function(options: { [key: string]: any } = {}) {
return api.z0(this.valueOf(), options);
};
String.prototype.z1 = function(options: { [key: string]: any } = {}) {
return api.z1(this.valueOf(), options);
};
String.prototype.z2 = function(options: { [key: string]: any } = {}) {
return api.z2(this.valueOf(), options);
};
String.prototype.z3 = function(options: { [key: string]: any } = {}) {
return api.z3(this.valueOf(), options);
};
String.prototype.z4 = function(options: { [key: string]: any } = {}) {
return api.z4(this.valueOf(), options);
};
String.prototype.z5 = function(options: { [key: string]: any } = {}) {
return api.z5(this.valueOf(), options);
};
String.prototype.z6 = function(options: { [key: string]: any } = {}) {
return api.z6(this.valueOf(), options);
};
String.prototype.z7 = function(options: { [key: string]: any } = {}) {
return api.z7(this.valueOf(), options);
};
String.prototype.z8 = function(options: { [key: string]: any } = {}) {
return api.z8(this.valueOf(), options);
};
String.prototype.z9 = function(options: { [key: string]: any } = {}) {
return api.z9(this.valueOf(), options);
};
String.prototype.z10 = function(options: { [key: string]: any } = {}) {
return api.z10(this.valueOf(), options);
};
String.prototype.z11 = function(options: { [key: string]: any } = {}) {
return api.z11(this.valueOf(), options);
};
String.prototype.z12 = function(options: { [key: string]: any } = {}) {
return api.z12(this.valueOf(), options);
};
String.prototype.z13 = function(options: { [key: string]: any } = {}) {
return api.z13(this.valueOf(), options);
};
String.prototype.z14 = function(options: { [key: string]: any } = {}) {
return api.z14(this.valueOf(), options);
};
String.prototype.z15 = function(options: { [key: string]: any } = {}) {
return api.z15(this.valueOf(), options);
};
String.prototype.z16 = function(options: { [key: string]: any } = {}) {
return api.z16(this.valueOf(), options);
};
String.prototype.note = function() {
try {
return parseInt(this.valueOf());
} catch (e) {
return noteNameToMidi(this.valueOf());
}
};
}
type SpeechOptions = {
text?: string;
rate?: number;
pitch?: number;
volume?: number;
voice?: number;
lang?: string;
}
let speakerTimeout: number;
export class Speaker {
constructor(
public options: SpeechOptions
) { }
speak = () => {
return new Promise<void>((resolve, reject) => {
if (this.options.text) {
const synth = window.speechSynthesis;
if (synth.speaking) synth.cancel();
const utterance = new SpeechSynthesisUtterance(this.options.text);
utterance.rate = this.options.rate || 1;
utterance.pitch = this.options.pitch || 1;
utterance.volume = this.options.volume || 1;
if (this.options.voice) {
utterance.voice = synth.getVoices()[this.options.voice];
}
if (this.options.lang) {
// Check if language has country code
if (this.options.lang.length === 2) {
utterance.lang = `${this.options.lang}-${this.options.lang.toUpperCase()}`
} else if (this.options.lang.length === 5) {
utterance.lang = this.options.lang;
} else {
// Fallback to en us
utterance.lang = 'en-US';
}
}
utterance.onend = () => {
resolve();
};
utterance.onerror = (error) => {
reject(error);
};
if (synth.speaking) {
// Cancel again?
synth.cancel();
// Set timeout
if (speakerTimeout) clearTimeout(speakerTimeout);
// @ts-ignore
speakerTimeout = setTimeout(() => {
synth.speak(utterance);
}, 200);
} else {
synth.speak(utterance);
}
} else {
reject("No text provided");
}
});
}
}

View File

@ -10,7 +10,7 @@ import { arrayOfObjectsToObjectWithArrays } from "../Utils/Generic";
export type InputOptions = { [key: string]: string | number };
export class Player extends Event {
input: string;
input: string|number;
ziffers: Ziffers;
initCallTime: number = 0;
startCallTime: number = 0;
@ -25,15 +25,23 @@ export class Player extends Event {
skipIndex = 0;
constructor(
input: string,
input: string|number|Generator<number>,
options: InputOptions,
public app: Editor,
zid: string = ""
) {
super(app);
this.input = input;
this.options = options;
this.ziffers = new Ziffers(input, options);
if (typeof input === "string") {
this.input = input;
this.ziffers = new Ziffers(input, options);
} else if (typeof input === "number") {
this.input = input;
this.ziffers = Ziffers.fromNumber(input,options);
} else {
this.ziffers = Ziffers.fromGenerator(input,options);
this.input = this.ziffers.input;
}
this.zid = zid;
}
@ -236,6 +244,16 @@ export class Player extends Event {
return this;
}
tonnetz(transform: string) {
if (this.atTheBeginning()) this.ziffers.tonnetzTransformation(transform);
return this;
}
tonnetzChord(chord: string) {
if (this.atTheBeginning()) this.ziffers.tonnetzChords(chord);
return this;
}
voiceleading() {
if (this.atTheBeginning()) this.ziffers.lead();
return this;

View File

@ -196,6 +196,19 @@ beat(1)::snd('sine').sustain(0.1).freq([100,100,100,100,200].unique().beat()).ou
true
)}
- <ic>pitch()</ic>: convert a list of integers to pitch classes
${makeExample(
"Converting a list of integers to pitch classes using key and scale",
`
beat(0.25) :: snd('sine')
.pitch([0,1,2,3,4,6,7,8].beat(0.125))
.key(["F4","F3"].beat(2.0))
.scale("minor").out()
`,
true
)}
- <ic>scale(scale: string, base note: number)</ic>: Map each element of the list to the closest note of the slected scale. [0, 2, 3, 5 ].scale("major", 50) returns [50, 52, <ic>54</ic>, 55]. You can use western scale names like (Major, Minor, Minor pentatonic ...) or [zeitler](https://ianring.com/musictheory/scales/traditions/zeitler) scale names. Alternatively you can also use the integers as used by Ian Ring in his [study of scales](https://ianring.com/musictheory/scales/).
${makeExample(

View File

@ -1,4 +1,4 @@
import { type UserAPI } from "./API";
import { type UserAPI } from "../API";
import { safeScale, stepsToScale } from "zifferjs";
export { };

View File

@ -0,0 +1,98 @@
import { type UserAPI } from "../API";
import { Player } from "../classes/ZPlayer";
declare global {
interface Number {
z(): Player;
z0(): Player;
z1(): Player;
z2(): Player;
z3(): Player;
z4(): Player;
z5(): Player;
z6(): Player;
z7(): Player;
z8(): Player;
z9(): Player;
z10(): Player;
z11(): Player;
z12(): Player;
z13(): Player;
z14(): Player;
z15(): Player;
z16(): Player;
note(): number;
}
}
export const makeNumberExtensions = (api: UserAPI) => {
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} = {}) {
return api.z1(this.valueOf().toString().split("").join(), options);
};
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} = {}) {
return api.z3(this.valueOf().toString().split("").join(), options);
};
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} = {}) {
return api.z5(this.valueOf().toString().split("").join(), options);
};
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} = {}) {
return api.z7(this.valueOf().toString().split("").join(), options);
};
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} = {}) {
return api.z9(this.valueOf().toString().split("").join(), options);
};
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} = {}) {
return api.z11(this.valueOf().toString().split("").join(), options);
};
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} = {}) {
return api.z13(this.valueOf().toString().split("").join(), options);
};
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} = {}) {
return api.z15(this.valueOf().toString().split("").join(), options);
};
Number.prototype.z16 = function (options: {[key: string]: any} = {}) {
return api.z16(this.valueOf().toString().split("").join(), options);
};
}

View File

@ -0,0 +1,230 @@
import { noteNameToMidi } from "zifferjs";
import { type UserAPI } from "../API";
import { Player } from "../classes/ZPlayer";
export {};
// Extend String prototype
declare global {
interface String {
speak(): void;
rate(speed: number): string;
pitch(pitch: number): string;
volume(volume: number): string;
voice(voice: number): string;
lang(language: string): string;
options(): SpeechOptions;
z(): Player;
z0(): Player;
z1(): Player;
z2(): Player;
z3(): Player;
z4(): Player;
z5(): Player;
z6(): Player;
z7(): Player;
z8(): Player;
z9(): Player;
z10(): Player;
z11(): Player;
z12(): Player;
z13(): Player;
z14(): Player;
z15(): Player;
z16(): Player;
note(): number;
}
}
const isJsonString = (str: string):boolean => {
return str[0] === '{' && str[str.length - 1] === '}'
}
const stringObject = (str: string, params: object) => {
if(isJsonString(str)) {
const obj = JSON.parse(str);
return JSON.stringify({...obj, ...params});
} else {
return JSON.stringify({...params, text: str});
}
}
export const makeStringExtensions = (api: UserAPI) => {
String.prototype.speak = function () {
const options = JSON.parse(this.valueOf());
new Speaker({ ...options, text: options.text }).speak().then(() => {
// Done
}).catch((e) => {
console.log("Error speaking:", e);
});
};
String.prototype.rate = function (speed: number) {
return stringObject(this.valueOf(), {rate: speed});
};
String.prototype.pitch = function (pitch: number) {
return stringObject(this.valueOf(), {pitch: pitch});
};
String.prototype.lang = function (language: string) {
return stringObject(this.valueOf(),{lang: language});
};
String.prototype.volume = function (volume: number) {
return stringObject(this.valueOf(), {volume: volume});
};
String.prototype.voice = function (voice: number) {
return stringObject(this.valueOf(), {voice: voice});
};
String.prototype.z = function (options: {[key: string]: any} = {}) {
return api.z(this.valueOf(), options);
};
String.prototype.z0 = function (options: {[key: string]: any} = {}) {
return api.z0(this.valueOf(), options);
};
String.prototype.z1 = function (options: {[key: string]: any} = {}) {
return api.z1(this.valueOf(), options);
};
String.prototype.z2 = function (options: {[key: string]: any} = {}) {
return api.z2(this.valueOf(), options);
};
String.prototype.z3 = function (options: {[key: string]: any} = {}) {
return api.z3(this.valueOf(), options);
};
String.prototype.z4 = function (options: {[key: string]: any} = {}) {
return api.z4(this.valueOf(), options);
};
String.prototype.z5 = function (options: {[key: string]: any} = {}) {
return api.z5(this.valueOf(), options);
};
String.prototype.z6 = function (options: {[key: string]: any} = {}) {
return api.z6(this.valueOf(), options);
};
String.prototype.z7 = function (options: {[key: string]: any} = {}) {
return api.z7(this.valueOf(), options);
};
String.prototype.z8 = function (options: {[key: string]: any} = {}) {
return api.z8(this.valueOf(), options);
};
String.prototype.z9 = function (options: {[key: string]: any} = {}) {
return api.z9(this.valueOf(), options);
};
String.prototype.z10 = function (options: {[key: string]: any} = {}) {
return api.z10(this.valueOf(), options);
};
String.prototype.z11 = function (options: {[key: string]: any} = {}) {
return api.z11(this.valueOf(), options);
};
String.prototype.z12 = function (options: {[key: string]: any} = {}) {
return api.z12(this.valueOf(), options);
};
String.prototype.z13 = function (options: {[key: string]: any} = {}) {
return api.z13(this.valueOf(), options);
};
String.prototype.z14 = function (options: {[key: string]: any} = {}) {
return api.z14(this.valueOf(), options);
};
String.prototype.z15 = function (options: {[key: string]: any} = {}) {
return api.z15(this.valueOf(), options);
};
String.prototype.z16 = function (options: {[key: string]: any} = {}) {
return api.z16(this.valueOf(), options);
};
String.prototype.note = function () {
try {
return parseInt(this.valueOf());
} catch (e) {
return noteNameToMidi(this.valueOf());
}
};
}
type SpeechOptions = {
text?: string;
rate?: number;
pitch?: number;
volume?: number;
voice?: number;
lang?: string;
}
let speakerTimeout: number;
export class Speaker {
constructor(
public options: SpeechOptions
) {}
speak = () => {
return new Promise<void>((resolve, reject) => {
if (this.options.text) {
const synth = window.speechSynthesis;
if(synth.speaking) synth.cancel();
const utterance = new SpeechSynthesisUtterance(this.options.text);
utterance.rate = this.options.rate || 1;
utterance.pitch = this.options.pitch || 1;
utterance.volume = this.options.volume || 1;
if (this.options.voice) {
utterance.voice = synth.getVoices()[this.options.voice];
}
if(this.options.lang) {
// Check if language has country code
if (this.options.lang.length === 2) {
utterance.lang = `${this.options.lang}-${this.options.lang.toUpperCase()}`
} else if (this.options.lang.length === 5) {
utterance.lang = this.options.lang;
} else {
// Fallback to en us
utterance.lang = 'en-US';
}
}
utterance.onend = () => {
resolve();
};
utterance.onerror = (error) => {
reject(error);
};
if(synth.speaking) {
// Cancel again?
synth.cancel();
// Set timeout
if(speakerTimeout) clearTimeout(speakerTimeout);
// @ts-ignore
speakerTimeout = setTimeout(() => {
synth.speak(utterance);
}, 200);
} else {
synth.speak(utterance);
}
} else {
reject("No text provided");
}
});
}
}

View File

@ -16,16 +16,19 @@ import { documentation_factory } from "./Documentation";
import { EditorView } from "codemirror";
import { Clock } from "./Clock";
import { loadSamples, UserAPI } from "./API";
import { makeArrayExtensions } from "./ArrayExtensions";
import * as oeis from 'jisg'
import * as zpatterns from 'zifferjs/src/patterns.ts'
import { makeArrayExtensions } from "./extensions/ArrayExtensions";
import "./style.css";
import { Universes, File, template_universes } from "./FileManagement";
import { tryEvaluate } from "./Evaluator";
// @ts-ignore
import showdown from "showdown";
import { makeStringExtensions } from "./StringExtensions";
import { makeStringExtensions } from "./extensions/StringExtensions";
import { installInterfaceLogic } from "./InterfaceLogic";
import { installWindowBehaviors, saveBeforeExit } from "./WindowBehavior";
import { drawEmptyBlinkers } from "./AudioVisualisation";
import { makeNumberExtensions } from "./extensions/NumberExtensions";
// @ts-ignore
import { registerSW } from "virtual:pwa-register";
@ -130,12 +133,23 @@ export class Editor {
this.api = new UserAPI(this);
makeArrayExtensions(this.api);
makeStringExtensions(this.api);
makeNumberExtensions(this.api);
// Passing the API to the User
Object.entries(this.api).forEach(([name, value]) => {
(globalThis as Record<string, any>)[name] = value;
});
// Passing OEIS generators to the User
Object.entries(oeis).forEach(([name, value]) => {
(globalThis as Record<string, any>)[name] = value;
});
// Passing ziffers sequences to the User
Object.entries(zpatterns).forEach(([name, value]) => {
(globalThis as Record<string, any>)[name] = value;
});
// ================================================================================
// Building Documentation
// ================================================================================