Skip to content

Commit

Permalink
Added speaker as promise
Browse files Browse the repository at this point in the history
  • Loading branch information
amiika committed Aug 30, 2023
1 parent 0260174 commit 3c335a5
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 34 deletions.
16 changes: 8 additions & 8 deletions src/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
soundMap,
// @ts-ignore
} from "superdough";
import { Speaker } from "./StringExtensions";

interface ControlChange {
channel: number;
Expand Down Expand Up @@ -1292,7 +1293,7 @@ export class UserAPI {
// Speech synthesis
// =============================================================

speak = (text: string, voice: number, rate: number = 1, pitch: number = 1): void => {
speak = (text: string, lang: string = "en-US", voice: number = 0, rate: number = 1, pitch: number = 1): void => {
/*
* Speaks the given text using the browser's speech synthesis API.
* @param text - The text to speak
Expand All @@ -1301,13 +1302,12 @@ export class UserAPI {
* @param pitch - The pitch at which to speak the text
*
*/
const synth = window.speechSynthesis;
synth.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utterance.voice = speechSynthesis.getVoices()[voice];
utterance.rate = rate;
utterance.pitch = pitch;
synth.speak(utterance);
const speaker = new Speaker({text: text, lang: lang, voice: voice, rate: rate, pitch: pitch});
speaker.speak().then(() => {
// Done speaking
}).catch((err) => {
console.log(err);
});
};

// =============================================================
Expand Down
31 changes: 26 additions & 5 deletions src/Documentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1579,16 +1579,21 @@ mod(0.25) :: sound('sine')
# Speech synthesis
Topos can also speak using [Web Speec API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API)!
Topos can also speak using the Web Speech API. Speech synthesis can be used in two ways:
Speech synthesis can be used in two ways:
- <icode>speak(text: string, voice: number, rate: number, pitch: number)</icode>: speak the given text.
- <icode>speak(text: string, lang: string, voice: number, rate: number, pitch: number, volume: number)</icode>: speak the given text.
Or by using string and chaining:
- <icode>"Hello".rate(1.5).pitch(0.5).speak()</icode>.
Value ranges for the different parameters are:
- <icode>lang(string)</icode>: language code, for example <icode>en</icode> for English, <icode>fr</icode> for French or with the country code for example British English <icode>en-GB</icode>. See supported values from the [list](https://cloud.google.com/speech-to-text/docs/speech-to-text-supported-languages).
- <icode>voice(number)</icode>: voice index, for example <icode>0</icode> for the first voice, <icode>1</icode> for the second voice, etc.
- <icode>rate(number)</icode>: speaking rate, from <icode>0.0</icode> to <icode>10</icode>.
- <icode>pitch(number)</icode>: speaking pitch, from <icode>0.0</icode> to <icode>2</icode>.
- <icode>volume(number)</icode>: speaking volume, from <icode>0.0</icode> to <icode>1.0</icode>.
Examples:
${makeExample(
Expand All @@ -1602,7 +1607,7 @@ mod(4) :: speak("Hello world!")
${makeExample(
"Different voices",
`
mod(2) :: speak("Topos!",irand(0,25))
mod(2) :: speak("Topos!","fr",irand(0,5))
`,
false
)}
Expand All @@ -1628,6 +1633,22 @@ ${makeExample(
false
)}
${makeExample(
"String chaining with array chaining",
`
const croissant = ["Croissant!", "Volant", "Arc-en-ciel", "Chocolat", "Dansant", "Nuage", "Tournant", "Galaxie", "Chatoyant", "Flamboyant", "Cosmique"];
onbeat(1) :: croissant.bar()
.lang("fr")
.volume(rand(0.2,2.0))
.rate(rand(.4,.6))
.speak();
`,
false
)}
Expand Down
78 changes: 57 additions & 21 deletions src/StringExtensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,53 @@ declare global {
pitch(pitch: number): string;
volume(volume: number): string;
voice(voice: number): string;
lang(language: string): string;
options(): SpeechOptions;
}
}

const speechOptionsMap = new Map<string, SpeechOptions>();
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 = speechOptionsMap.get(this.valueOf()) || {};
new Speech({ ...options, text: this.valueOf() }).say();
const options = JSON.parse(this.valueOf());
console.log("SPEAKING:", options);
new Speaker({ ...options, text: options.text }).speak().then(() => {
// Done
}).catch((e) => {
console.log("Error speaking:", e);
});
};

String.prototype.rate = function (speed: number) {
const options = speechOptionsMap.get(this.valueOf()) || {};
speechOptionsMap.set(this.valueOf(), { ...options, rate: speed });
return this.valueOf();
return stringObject(this.valueOf(), {rate: speed});
};

String.prototype.pitch = function (pitch: number) {
const options = speechOptionsMap.get(this.valueOf()) || {};
speechOptionsMap.set(this.valueOf(), { ...options, pitch: pitch });
return this.valueOf();
return stringObject(this.valueOf(), {pitch: pitch});
};

String.prototype.volume = function (volume: number) {
const options = speechOptionsMap.get(this.valueOf()) || {};
speechOptionsMap.set(this.valueOf(), { ...options, volume: volume });
return this.valueOf();
String.prototype.lang = function (language: string) {
return stringObject(this.valueOf(),{lang: language});
};

String.prototype.voice = function (voice: number) {
const options = speechOptionsMap.get(this.valueOf()) || {};
speechOptionsMap.set(this.valueOf(), { ...options, voice: voice });
return this.valueOf();
String.prototype.volume = function (volume: number) {
return stringObject(this.valueOf(), {volume: volume});
};

String.prototype.options = function (): SpeechOptions {
return speechOptionsMap.get(this.valueOf()) || {};
String.prototype.voice = function (voice: number) {
return stringObject(this.valueOf(), {voice: voice});
};

String.prototype.z = function () {
Expand All @@ -62,14 +71,16 @@ type SpeechOptions = {
pitch?: number;
volume?: number;
voice?: number;
lang?: string;
}

class Speech {
export class Speaker {
constructor(
public options: SpeechOptions
) {}

say = () => {
speak = () => {
return new Promise<void>((resolve, reject) => {
if (this.options.text) {
const synth = window.speechSynthesis;
synth.cancel();
Expand All @@ -80,7 +91,32 @@ class Speech {
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);
};

synth.speak(utterance);

} else {
reject("No text provided");
}

});
}
}

0 comments on commit 3c335a5

Please sign in to comment.