From 5ab7e80db6da4dd5d6c188613b824d859b109ad6 Mon Sep 17 00:00:00 2001 From: Thomas Kemmer Date: Mon, 25 Mar 2024 23:17:09 +0100 Subject: [PATCH] Add voice rate and pitch selection. --- src/app/app-settings.ts | 2 ++ src/app/app.component.ts | 2 ++ src/app/services/speech.service.ts | 8 +++++ src/app/settings/index.ts | 3 +- src/app/settings/settings.module.ts | 6 ++++ src/app/settings/settings.page.html | 9 ++--- src/app/settings/settings.page.ts | 32 +++-------------- src/app/settings/voice.page.html | 48 +++++++++++++++++++++++++ src/app/settings/voice.page.ts | 55 +++++++++++++++++++++++++++++ src/assets/i18n/de.json | 2 ++ src/assets/i18n/en.json | 2 ++ src/assets/i18n/es.json | 2 ++ src/assets/i18n/fr.json | 2 ++ src/assets/i18n/it.json | 2 ++ src/assets/i18n/sk.json | 2 ++ 15 files changed, 143 insertions(+), 34 deletions(-) create mode 100644 src/app/settings/voice.page.html create mode 100644 src/app/settings/voice.page.ts diff --git a/src/app/app-settings.ts b/src/app/app-settings.ts index 61be614c..ca1cccdd 100644 --- a/src/app/app-settings.ts +++ b/src/app/app-settings.ts @@ -69,6 +69,8 @@ export class Options { speech = true; sectors = false; voice = ''; + rate = 1000; + pitch = 1000; } export interface Notification { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index a6dddf0a..dbd71c12 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -66,6 +66,8 @@ export class AppComponent implements AfterViewInit, OnInit, OnDestroy { this.logger.setDebugEnabled(options.debug); this.setLanguage(options.language); this.speech.setVoice(options.voice); + this.speech.setRate(options.rate / 1000.0); + this.speech.setPitch(options.pitch / 1000.0); }); } diff --git a/src/app/services/speech.service.ts b/src/app/services/speech.service.ts index 47746172..9d72f1cf 100644 --- a/src/app/services/speech.service.ts +++ b/src/app/services/speech.service.ts @@ -33,6 +33,7 @@ class WebSpeech { utterance.text = textOrOptions.text; utterance.lang = textOrOptions.locale; utterance.rate = textOrOptions.rate; + utterance.pitch = textOrOptions.pitch; utterance.voice = this.getVoiceMap().get(textOrOptions.identifier); } utterance.onend = () => { @@ -100,6 +101,8 @@ export class SpeechService { private rate = 1.0; + private pitch = 1.0; + private voice: string; private lastMessage: string; @@ -123,6 +126,10 @@ export class SpeechService { this.rate = rate; } + setPitch(pitch: number) { + this.pitch = pitch; + } + setVoice(voice: string) { this.voice = voice; } @@ -138,6 +145,7 @@ export class SpeechService { text: message, locale: this.locale || 'en-us', rate: this.rate, + pitch: this.pitch, identifier: this.voice || null }).then(() => { if (this.pending === 0) { diff --git a/src/app/settings/index.ts b/src/app/settings/index.ts index bd7eacb6..87806fda 100644 --- a/src/app/settings/index.ts +++ b/src/app/settings/index.ts @@ -4,4 +4,5 @@ export * from './about.page'; export * from './logging.page'; export * from './licenses.page'; export * from './connection.page'; -export * from './notifications.page'; \ No newline at end of file +export * from './notifications.page'; +export * from './voice.page'; \ No newline at end of file diff --git a/src/app/settings/settings.module.ts b/src/app/settings/settings.module.ts index 29117fe2..18c7205c 100644 --- a/src/app/settings/settings.module.ts +++ b/src/app/settings/settings.module.ts @@ -14,6 +14,7 @@ import { LoggingMenu } from './logging.menu'; import { LoggingPage } from './logging.page'; import { NotificationsPage } from './notifications.page'; import { SettingsPage } from './settings.page'; +import { VoicePage } from './voice.page'; const routes: Routes = [ { @@ -39,6 +40,10 @@ const routes: Routes = [ { path: 'notifications', component: NotificationsPage + }, + { + path: 'voice', + component: VoicePage } ]; @@ -50,6 +55,7 @@ const routes: Routes = [ LoggingMenu, LoggingPage, NotificationsPage, + VoicePage, SettingsPage ], exports: [ diff --git a/src/app/settings/settings.page.html b/src/app/settings/settings.page.html index 2a493bbf..092412d0 100644 --- a/src/app/settings/settings.page.html +++ b/src/app/settings/settings.page.html @@ -12,7 +12,7 @@ - + @@ -25,11 +25,8 @@ Slovak - - - Default - {{v.name}} - + + Voice Connection diff --git a/src/app/settings/settings.page.ts b/src/app/settings/settings.page.ts index b3c76bdd..8112a583 100644 --- a/src/app/settings/settings.page.ts +++ b/src/app/settings/settings.page.ts @@ -1,7 +1,5 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; - import { AboutPage } from './about.page'; import { ConnectionPage } from './connection.page'; import { LicensesPage } from './licenses.page'; @@ -15,24 +13,16 @@ import { I18nAlertService, SpeechService } from '../services'; templateUrl: 'settings.page.html' }) export class SettingsPage implements OnDestroy, OnInit { - aboutPage = AboutPage; - connectionPage = ConnectionPage; - licensesPage = LicensesPage; - loggingPage = LoggingPage; - notificationsPage = NotificationsPage; options = new Options(); - voices = []; - private subscription: any; - constructor(private alert: I18nAlertService, private settings: AppSettings, private speech: SpeechService, private translate: TranslateService) {} + constructor(private alert: I18nAlertService, private settings: AppSettings, private speech: SpeechService) {} ngOnInit() { this.subscription = this.settings.getOptions().subscribe(options => { this.options = options; - this.updateVoices(); }); } @@ -53,27 +43,15 @@ export class SettingsPage implements OnDestroy, OnInit { }); } - async update() { - await this.updateVoices(); - return this.settings.setOptions(this.options); - } - - async updateVoices() { + async updateLanguage() { if (this.options.language) { - this.voices = await this.speech.getVoices(this.options.language); - if (!this.voices.find(v => v.identifier == this.options.voice)) { + let voices = await this.speech.getVoices(this.options.language); + if (!voices.find(v => v.identifier == this.options.voice)) { this.options.voice = ""; } } else { - this.voices = []; this.options.voice = ""; } - } - - async updateAndGreet() { - await this.update(); - // TODO: trigger when voice is selected - const greeting = this.translate.instant("notifications.greeting"); - this.speech.speak(greeting); + return this.settings.setOptions(this.options); } } diff --git a/src/app/settings/voice.page.html b/src/app/settings/voice.page.html new file mode 100644 index 00000000..4e384df9 --- /dev/null +++ b/src/app/settings/voice.page.html @@ -0,0 +1,48 @@ + + + + + + + Voice + + + + + + + + + Default + {{v.name}} + + + + + Rate + + + + + + + + + + + + Pitch + + + + + + + + + + + Test + + + diff --git a/src/app/settings/voice.page.ts b/src/app/settings/voice.page.ts new file mode 100644 index 00000000..f50f13e4 --- /dev/null +++ b/src/app/settings/voice.page.ts @@ -0,0 +1,55 @@ +import { Component, OnDestroy } from '@angular/core'; + +import { TranslateService } from '@ngx-translate/core'; + +import { AppSettings, Options } from '../app-settings'; +import { SpeechService } from '../services'; + + +@Component({ + templateUrl: 'voice.page.html' +}) +export class VoicePage implements OnDestroy { + + options = new Options(); + + voices = []; + + private subscription: any; + + constructor(private settings: AppSettings, private speech: SpeechService, private translate: TranslateService) {} + + ngOnInit() { + this.subscription = this.settings.getOptions().subscribe(options => { + this.options = options; + this.updateVoices(); + }); + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } + + resetRate() { + this.options.rate = 1000; + this.update(); + } + + resetPitch() { + this.options.pitch = 1000; + this.update(); + } + + async update() { + return this.settings.setOptions(this.options); + } + + async test() { + const greeting = this.translate.instant("notifications.greeting"); + this.speech.speak(greeting); + } + + async updateVoices() { + this.voices = await this.speech.getVoices(this.options.language); + } +} diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index d86d1ee2..e3ce5a0c 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -71,10 +71,12 @@ "Open source licenses": "Open-Source-Lizenzen", "Order by number": "Nach Nummer ordnen", "Pace Car": "Pace Car", + "Pitch": "Tonhöhe", "Privacy policy": "Datenschutz-Bestimmungen", "Qualifying": "Qualifying", "Race finished": "Rennen beendet", "Race": "Rennen", + "Rate": "Geschwindigkeit", "Reconnect": "Erneut verbinden", "Reconnect delay": "Verzögerung beim erneuten Verbinden", "Request timeout": "Zeitlimit für Anfragen", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 2865e50c..72e2dfe9 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -71,10 +71,12 @@ "Open source licenses": "Open source licenses", "Order by number": "Order by number", "Pace Car": "Pace Car", + "Pitch": "Pitch", "Privacy policy": "Privacy policy", "Qualifying": "Qualifying", "Race finished": "Race finished", "Race": "Race", + "Rate": "Rate", "Reconnect": "Reconnect", "Reconnect delay": "Reconnect delay", "Request timeout": "Request timeout", diff --git a/src/assets/i18n/es.json b/src/assets/i18n/es.json index cee62919..14080252 100644 --- a/src/assets/i18n/es.json +++ b/src/assets/i18n/es.json @@ -71,10 +71,12 @@ "Open source licenses": "Licencia open source", "Order by number": "Ordenar por número", "Pace Car": "Coche de seguridad", + "Pitch": "Paso", "Privacy policy": "Política de privacidad", "Qualifying": "Clasificación", "Race finished": "Carrera terminada", "Race": "Carrera", + "Rate": "Velocidad", "Reconnect": "Reconectar", "Reconnect delay": "Retardo para reconexión", "Request timeout": "Solicitud de tiempo de espera", diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json index 18a57175..1503b8e3 100644 --- a/src/assets/i18n/fr.json +++ b/src/assets/i18n/fr.json @@ -71,10 +71,12 @@ "Open source licenses": "Licence Open source", "Order by number": "Ordre par numéro", "Pace Car": "Pace Car", + "Pitch": "Pas", "Privacy policy": "Politique de confidentialité", "Qualifying": "Qualifications", "Race finished": "Course terminée", "Race": "Course", + "Rate": "Vitesse", "Reconnect": "Reconnexion", "Reconnect delay": "Délai de reconnexion", "Request timeout": "Demande de délai d'attente", diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json index 4039fdbb..53c7f5c1 100644 --- a/src/assets/i18n/it.json +++ b/src/assets/i18n/it.json @@ -71,10 +71,12 @@ "Open source licenses": "Licenze open source", "Order by number": "In ordine di numero", "Pace Car": "Pace Car", + "Pitch": "Pece", "Privacy policy": "Politica sulla riservatezza", "Qualifying": "Qualificazioni", "Race finished": "Corsa finita", "Race": "Corsa", + "Rate": "Velocità", "Reconnect": "Ricollegamento", "Reconnect delay": "Attesa per il ricollegamento", "Request timeout": "Tempo massimo per la richiesta", diff --git a/src/assets/i18n/sk.json b/src/assets/i18n/sk.json index a2e426a0..2987d218 100644 --- a/src/assets/i18n/sk.json +++ b/src/assets/i18n/sk.json @@ -71,10 +71,12 @@ "Open source licenses": "Licencie otvoreného zdrojového kódu", "Order by number": "Zoradiť podľa čísla", "Pace Car": "Pace Car", + "Pitch": "Ihrisko", "Privacy policy": "Ochrana súkromia", "Qualifying": "Kvalifikácia", "Race finished": "Preteky ukončené", "Race": "Preteky", + "Rate": "Rýchlosť", "Reconnect": "Opätovne pripojiť", "Reconnect delay": "Oneskorenie pri opätovnom pripojení", "Request timeout": "Vypršanie časového limitu požiadavky",