Merge branch 'main' of github.com:Bubobubobubobubo/Topos
This commit is contained in:
110
dev-dist/sw.js
110
dev-dist/sw.js
@ -21,22 +21,20 @@ if (!self.define) {
|
||||
|
||||
const singleRequire = (uri, parentUri) => {
|
||||
uri = new URL(uri + ".js", parentUri).href;
|
||||
return registry[uri] || (
|
||||
|
||||
new Promise(resolve => {
|
||||
if ("document" in self) {
|
||||
const script = document.createElement("script");
|
||||
script.src = uri;
|
||||
script.onload = resolve;
|
||||
document.head.appendChild(script);
|
||||
} else {
|
||||
nextDefineUri = uri;
|
||||
importScripts(uri);
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
|
||||
.then(() => {
|
||||
return (
|
||||
registry[uri] ||
|
||||
new Promise((resolve) => {
|
||||
if ("document" in self) {
|
||||
const script = document.createElement("script");
|
||||
script.src = uri;
|
||||
script.onload = resolve;
|
||||
document.head.appendChild(script);
|
||||
} else {
|
||||
nextDefineUri = uri;
|
||||
importScripts(uri);
|
||||
resolve();
|
||||
}
|
||||
}).then(() => {
|
||||
let promise = registry[uri];
|
||||
if (!promise) {
|
||||
throw new Error(`Module ${uri} didn’t register its module`);
|
||||
@ -47,27 +45,31 @@ if (!self.define) {
|
||||
};
|
||||
|
||||
self.define = (depsNames, factory) => {
|
||||
const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
|
||||
const uri =
|
||||
nextDefineUri ||
|
||||
("document" in self ? document.currentScript.src : "") ||
|
||||
location.href;
|
||||
if (registry[uri]) {
|
||||
// Module is already loading or loaded.
|
||||
return;
|
||||
}
|
||||
let exports = {};
|
||||
const require = depUri => singleRequire(depUri, uri);
|
||||
const require = (depUri) => singleRequire(depUri, uri);
|
||||
const specialDeps = {
|
||||
module: { uri },
|
||||
exports,
|
||||
require
|
||||
require,
|
||||
};
|
||||
registry[uri] = Promise.all(depsNames.map(
|
||||
depName => specialDeps[depName] || require(depName)
|
||||
)).then(deps => {
|
||||
registry[uri] = Promise.all(
|
||||
depsNames.map((depName) => specialDeps[depName] || require(depName))
|
||||
).then((deps) => {
|
||||
factory(...deps);
|
||||
return exports;
|
||||
});
|
||||
};
|
||||
}
|
||||
define(['./workbox-b7fccfec'], (function (workbox) { 'use strict';
|
||||
define(["./workbox-b7fccfec"], function (workbox) {
|
||||
"use strict";
|
||||
|
||||
self.skipWaiting();
|
||||
workbox.clientsClaim();
|
||||
@ -77,28 +79,44 @@ define(['./workbox-b7fccfec'], (function (workbox) { 'use strict';
|
||||
* requests for URLs in the manifest.
|
||||
* See https://goo.gl/S9QRab
|
||||
*/
|
||||
workbox.precacheAndRoute([{
|
||||
"url": "registerSW.js",
|
||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||
}, {
|
||||
"url": "index.html",
|
||||
"revision": "0.djkmkn674mo"
|
||||
}], {});
|
||||
workbox.precacheAndRoute(
|
||||
[
|
||||
{
|
||||
url: "registerSW.js",
|
||||
revision: "3ca0b8505b4bec776b69afdba2768812",
|
||||
},
|
||||
{
|
||||
url: "index.html",
|
||||
revision: "0.djkmkn674mo",
|
||||
},
|
||||
],
|
||||
{}
|
||||
);
|
||||
workbox.cleanupOutdatedCaches();
|
||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||
allowlist: [/^\/$/]
|
||||
}));
|
||||
workbox.registerRoute(({
|
||||
url
|
||||
}) => [/^https:\/\/raw\.githubusercontent\.com\/.*/i, /^https:\/\/shabda\.ndre\.gr\/.*/i].some(regex => regex.test(url)), new workbox.CacheFirst({
|
||||
"cacheName": "external-samples",
|
||||
plugins: [new workbox.ExpirationPlugin({
|
||||
maxEntries: 5000,
|
||||
maxAgeSeconds: 2592000
|
||||
}), new workbox.CacheableResponsePlugin({
|
||||
statuses: [0, 200]
|
||||
})]
|
||||
}), 'GET');
|
||||
|
||||
}));
|
||||
workbox.registerRoute(
|
||||
new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||
allowlist: [/^\/$/],
|
||||
})
|
||||
);
|
||||
workbox.registerRoute(
|
||||
({ url }) =>
|
||||
[
|
||||
/^https:\/\/raw\.githubusercontent\.com\/.*/i,
|
||||
/^https:\/\/shabda\.ndre\.gr\/.*/i,
|
||||
].some((regex) => regex.test(url)),
|
||||
new workbox.CacheFirst({
|
||||
cacheName: "external-samples",
|
||||
plugins: [
|
||||
new workbox.ExpirationPlugin({
|
||||
maxEntries: 5000,
|
||||
maxAgeSeconds: 2592000,
|
||||
}),
|
||||
new workbox.CacheableResponsePlugin({
|
||||
statuses: [0, 200],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
"GET"
|
||||
);
|
||||
});
|
||||
//# sourceMappingURL=sw.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -30,6 +30,7 @@
|
||||
"autoprefixer": "^10.4.14",
|
||||
"codemirror": "^6.0.1",
|
||||
"fflate": "^0.8.0",
|
||||
"jisg": "^0.9.7",
|
||||
"lru-cache": "^10.0.1",
|
||||
"marked": "^7.0.3",
|
||||
"postcss": "^8.4.27",
|
||||
@ -41,7 +42,7 @@
|
||||
"tone": "^14.8.49",
|
||||
"unique-names-generator": "^4.7.1",
|
||||
"vite-plugin-markdown": "^2.1.0",
|
||||
"zifferjs": "^0.0.34",
|
||||
"zifferjs": "^0.0.35",
|
||||
"zzfx": "^1.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
18
src/API.ts
18
src/API.ts
@ -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
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { type UserAPI } from "./API";
|
||||
import { type UserAPI } from "../API";
|
||||
import { safeScale, stepsToScale } from "zifferjs";
|
||||
export { };
|
||||
|
||||
98
src/extensions/NumberExtensions.ts
Normal file
98
src/extensions/NumberExtensions.ts
Normal 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);
|
||||
};
|
||||
|
||||
}
|
||||
230
src/extensions/StringExtensions.ts
Normal file
230
src/extensions/StringExtensions.ts
Normal 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");
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
18
src/main.ts
18
src/main.ts
@ -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
|
||||
// ================================================================================
|
||||
|
||||
13
yarn.lock
13
yarn.lock
@ -2595,6 +2595,11 @@ iterate-object@^1.3.2:
|
||||
resolved "https://registry.yarnpkg.com/iterate-object/-/iterate-object-1.3.4.tgz#fa50b1d9e58e340a7dd6b4c98c8a5e182e790096"
|
||||
integrity sha512-4dG1D1x/7g8PwHS9aK6QV5V94+ZvyP4+d19qDv43EzImmrndysIl4prmJ1hWWIGCqrZHyaHBm6BSEWHOLnpoNw==
|
||||
|
||||
jisg@^0.9.7:
|
||||
version "0.9.7"
|
||||
resolved "https://registry.yarnpkg.com/jisg/-/jisg-0.9.7.tgz#d080655808d1f30ec22eb2be070e8a10d8ee097f"
|
||||
integrity sha512-JWoGHGgU3xxJnPCNm6FpgMl0791xYFZq2PsejV8guCbhNJGsMvImAENx9pMhp7HbqhJgkM4ZV5lRbh5zbmF9xw==
|
||||
|
||||
jake@^10.8.5:
|
||||
version "10.8.7"
|
||||
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f"
|
||||
@ -3877,10 +3882,10 @@ yaml@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"
|
||||
integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
|
||||
|
||||
zifferjs@^0.0.34:
|
||||
version "0.0.34"
|
||||
resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.34.tgz#887fb2db1ec2aff21ff1742cbbbbc4621838588f"
|
||||
integrity sha512-q2eFi+j+yXkPTXU53at3Agrh67JmBJ5zloZ13kc5ObT9R8R9L/if21mbnFxpDJC6Sjugql40aM0Ko4p/zBTs4w==
|
||||
zifferjs@^0.0.35:
|
||||
version "0.0.35"
|
||||
resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.35.tgz#0518a84d031d2ef19417bc1084c21b08666df067"
|
||||
integrity sha512-vtvyEO/hIPRboGinkb1IhqJu4iU5DdbkzrQX8Xg7n096fDi/PU72b5Nwxt0xt29D37CfOI99sjaLbtYPab1NoA==
|
||||
|
||||
zzfx@^1.2.0:
|
||||
version "1.2.0"
|
||||
|
||||
Reference in New Issue
Block a user