diff --git a/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.html b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.html index 81a21e4c2..bf995237f 100644 --- a/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.html +++ b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.html @@ -3,8 +3,10 @@ + --> + +
+ + + + + + + + + + + + + +

diff --git a/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.scss b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.scss index 88b9eea6d..1e9ab1bed 100644 --- a/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.scss +++ b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.scss @@ -10,6 +10,7 @@ //padding-left: 20px; //padding-right: 20px; padding: 40px; + margin: 0 auto; } .storyContainer > * { @@ -57,25 +58,25 @@ .currentWord { background-color: var(--scealai-light-green); + transition: background-color .1s; } // there is a visual bug where the toolbar is not quite stuck to the top of the screen .Toolbar { position: sticky; top: 0; - /*padding: 0; - margin: 0; - width: 100%;*/ + background-color: var(--scealai-green); max-width: 60%; margin: auto; display: flex; flex-direction: row; justify-content: center; - //align-items: center; + + margin: 0 auto; } -.Toolbar > button { +.Toolbar > * { border: none; background-color: white; color: var(--scealai-green); @@ -101,10 +102,26 @@ font-size: 40px; } +button.playing { + background-color: var(--scealai-cream); +} + .pause { font-size: 20px; } +.voiceSelection { + display: flex; + flex-direction: row; + //right: 0; +} + +.voiceSelection mat-form-field { + //margin: auto auto; + max-width: 150px; + //max-width: min-content; +} + /*html { scroll-behavior: smooth; }*/ \ No newline at end of file diff --git a/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.ts b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.ts index 2a3da6036..740a1a600 100644 --- a/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.ts +++ b/ngapp/src/app/dr-story-viewer/dr-story-builder/dr-story-builder.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, ViewChild, ElementRef, Input, HostListener, importProvidersFrom } from "@angular/core"; import { SynthItem } from "app/core/models/synth-item"; -import { SynthesisService, Voice } from "app/core/services/synthesis.service"; +import { SynthesisService, Voice, voices } from "app/core/services/synthesis.service"; import { TranslationService } from "app/core/services/translation.service"; import { SynthesisPlayerComponent } from "app/student/synthesis-player/synthesis-player.component"; import { ActivatedRoute, ChildActivationEnd, Router } from "@angular/router"; @@ -26,18 +26,34 @@ import { ViewEncapsulation } from '@angular/core'; import { DigitalReaderStoryService } from "app/core/services/dr-story.service"; import { firstValueFrom, Observable } from "rxjs"; import { QuillModule } from "ngx-quill"; +import { MatSelectModule } from "@angular/material/select"; +import { MatMenuModule } from "@angular/material/menu"; +import { MatIconModule } from "@angular/material/icon"; +import { MatButtonModule } from "@angular/material/button"; + +const dialectToVoiceIndex = new Map([ + ["Connacht f", 0], + ["Ulster f", 1], + ["Connacht m", 2], + ["Munster f", 3], + ["Munster m", 4], +]); @Component({ standalone: true, //providers: [importProvidersFrom(QuillModule.forRoot())], imports: [ CommonModule, - QuillModule + QuillModule, + MatSelectModule, + MatMenuModule, + MatIconModule, + MatButtonModule ], selector: "app-dr-story-builder", templateUrl: "./dr-story-builder.component.html", styleUrls: ["./dr-story-builder.component.scss"], // Digital Reader Story Styling - encapsulation: ViewEncapsulation.None // Without this line, non-angular html can not be targetted for styling + encapsulation: ViewEncapsulation.None // Without this line, non-angular html cannot be targetted for styling }) export class DigitalReaderStoryBuilderComponent implements OnInit { @@ -52,7 +68,15 @@ export class DigitalReaderStoryBuilderComponent implements OnInit { public forceTrustedHTML:SafeHtml; public currentSentence:Element | null = null; public currentWord:Element | null = null; - public listOfAudios:any[] = [] + + public listOfAudios:any = [ // 5 sub-arrays, 1 for each voice option + [], + [], + [], + [], + [] + ] + public audio:HTMLAudioElement; public timings:any[] = [] public voiceSpeed = 1; @@ -62,6 +86,12 @@ export class DigitalReaderStoryBuilderComponent implements OnInit { // below boolean is needed to meaningfully distinguish audio.ended and audio.paused public audioPaused:Boolean = false; + public audioPlaying:Boolean = false; + + //public voiceDialect:string | null = 'Connacht'; + //public voiceGender:string | null = 'f'; + public voiceIndex:number = 0; // defaults to Sibéal nemo + constructor( private auth: AuthenticationService, @@ -78,35 +108,28 @@ export class DigitalReaderStoryBuilderComponent implements OnInit { } async ngOnInit() { - //this.audioCreationTest() - /*console.log(this.content.textContent) - const audioObservable = firstValueFrom(this.synth.synthesiseText( - this.content.textContent, - { name: "Sibéal", gender: "female", shortCode: "snc", code: "ga_CO_snc_nemo", dialect: "connacht", algorithm: "nemo", }, - false, - 'MP3', - 1) - ).then( (data) => { - console.log(data) - })*/ this.forceTrustedHTML = this.sanitizer.bypassSecurityTrustHtml(this.content.innerHTML) - console.log(this.content) // only for testing const firstSentSpans = this.content?.querySelectorAll('.sentence') for (let i=0;i<3;i++) { const sent = firstSentSpans.item(i) - this.synthRequest(sent?.textContent).then( (data) => { + this.synthRequest(sent?.textContent, voices[this.voiceIndex]).then( (data) => { console.log(data) - this.listOfAudios[i] = data // only for testing + this.listOfAudios[this.voiceIndex][i] = data // only for testing console.log(this.listOfAudios) - //return data }) } + + console.log(dialectToVoiceIndex.get('Connacht f')); } + speakerSelected(dialect:string, gender:string) { + console.log(dialect, gender); + } + getWordPositionIndex(word:Element, childWordSpans:NodeList) { let numNonWords = 0; @@ -173,67 +196,47 @@ export class DigitalReaderStoryBuilderComponent implements OnInit { const childWordSpans = sentenceSpan.querySelectorAll('.word'); - //const childWordSpans = sentenceSpan.querySelectorAll('.word') const wordInd = this.getWordPositionIndex(word, childWordSpans) // for testing as ASR seems to split only on spaces. - const sentenceText:string = this.recreateSentenceFromWord(childWordSpans.item(wordInd)) - const numTimings = sentenceText.split(' ').length - console.log(numTimings) - console.log(wordInd) + //const sentenceText:string = this.recreateSentenceFromWord(childWordSpans.item(wordInd)) + //const numTimings = sentenceText.split(' ').length + //console.log(numTimings) const timings = sentAudioObj.timing; - const tmp = []; const timingsArr = [] - console.log(timings) - - //const reSyncOffset = .03 let reSyncBuffer = 0; - //for (let i=wordInd; iwordInd) { // if it is not the first word of the sentence - if (i>0) { - //start = timings[i-1].end - //console.log(timingsArr.length, timingsArr[i-1]); - //start = timings[i-1].end; - start = tmp[i-1].end; - } - - const length = end-start; - //reSyncBuffer+=length*.1; + for (let i=0; i0) { + start = timingsArr[i-1].end; + } - timing['start'] = start; - timing['end'] = end-reSyncBuffer; // testing - //timing['end'] = end; // testing + const length = end-start; - // there may be something wrong with the timing alignment in the actual array - may have fixed it also - //if (i>=wordInd) - tmp.push(timing) + timing['start'] = start; + timing['end'] = end-reSyncBuffer; - //reSyncBuffer+=length*.09; // should not apply to the first word - //const modifier = .033*(1 + (1-this.voiceSpeed))*(this.speakerBaseSpeed); // should not apply to the first word - //console.log(modifier) - //reSyncBuffer+=length*Math.min(modifier, .9); - reSyncBuffer+=length*this.speakerAlignmentConstant; + timingsArr.push(timing) + reSyncBuffer+=length*this.speakerAlignmentConstant; } - console.log(tmp) - return tmp.slice(wordInd, timings.length); + return timingsArr.slice(wordInd, timings.length); } // TODO : relocate to the story creation page - synthRequest(text: string) { + synthRequest(text: string, speaker:Voice) { const audioObservable = firstValueFrom(this.synth.synthesiseText( text, - { name: "Áine", gender: "female", shortCode: "anb", code: "ga_UL_anb_nemo", dialect: "ulster", algorithm: "nemo", }, + speaker, + //{ name: "Áine", gender: "female", shortCode: "anb", code: "ga_UL_anb_nemo", dialect: "ulster", algorithm: "nemo", }, //{ name: "Síbéal", gender: "female", shortCode: "snc", code: "ga_CO_snc_nemo", dialect: "connacht", algorithm: "nemo", }, false, 'MP3', @@ -254,14 +257,14 @@ export class DigitalReaderStoryBuilderComponent implements OnInit { for (let k=i;k