import { AudioQuery , AccentPhrase , Speaker , SpeakerInfo } from "@/openapi" ; import path from "path" ; import { v4 as uuidv4 } from "uuid" ; import { AudioItem , EngineState , SaveResultObject , State , commandMutationsCreator , AudioActions , AudioGetters , AudioMutations , AudioStoreState , AudioCommandActions , AudioCommandGetters , AudioCommandMutations , AudioCommandStoreState , VoiceVoxStoreOptions , IEngineConnectorFactoryActions , } from "./type" ; import { createUILockAction } from "./ui" ; import { CharacterInfo , DefaultStyleId , Encoding as EncodingType , MoraDataType , StyleInfo , } from "@/type/preload" ; import Encoding from "encoding-japanese" ; import { PromiseType } from "./vuex" ; import { buildProjectFileName , sanitizeFileName } from "./utility" ; async function generateUniqueIdAndQuery ( state , audioItem ) { audioItem = JSON . parse ( JSON . stringify ( audioItem ) ) as AudioItem ; const audioQuery = audioItem . query ; if ( audioQuery != undefined ) { audioQuery . outputSamplingRate = state . savingSetting . outputSamplingRate ; audioQuery . outputStereo = state . savingSetting . outputStereo ; } const data = new TextEncoder ( ) . encode ( JSON . stringify ( [ audioItem . text , audioQuery , audioItem . styleId ] ) ) ; const digest = await crypto . subtle . digest ( "SHA-256" , data ) ; const id = Array . from ( new Uint8Array ( digest ) ) . map ( ( v ) => v . toString ( 16 ) . padStart ( 2 , "0" ) ) . join ( "" ) ; return [ id , audioQuery ] ; } function parseTextFile ( body , defaultStyleIds , characterInfos ? ) { const characters = new Map < string , number > ( ) ; { const uuid2StyleIds = new Map < string , number > ( ) ; for ( const defaultStyleId of defaultStyleIds || [ ] ) { const speakerUuid = defaultStyleId . speakerUuid ; const styleId = defaultStyleId . defaultStyleId ; uuid2StyleIds . set ( speakerUuid , styleId ) ; } for ( const characterInfo of characterInfos || [ ] ) { const uuid = characterInfo . metas . speakerUuid ; const styleId = uuid2StyleIds . get ( uuid ) ?? characterInfo . metas . styles [ 0 ] . styleId ; const speakerName = characterInfo . metas . speakerName ; characters . set ( speakerName , styleId ) ; } } if ( ! characters . size ) return [ ] ; const audioItems = [ ] ; const seps = [ "," , "\r\n" , "\n" ] ; let lastStyleId = 0 ; for ( const splittedText of body . split ( new RegExp ( `${ seps . join ( "|" ) }` , "g" ) ) ) { const styleId = characters . get ( splittedText ) ; if ( styleId !== undefined ) { lastStyleId = styleId ; continue ; } audioItems . push ( { text : splittedText , styleId : lastStyleId } ) ; } return audioItems ; } function buildFileName ( state , audioKey ) { const index = state . audioKeys . indexOf ( audioKey ) ; const audioItem = state . audioItems [ audioKey ] ; let styleName = "" ; const character = state . characterInfos ?. find ( ( info ) => { const result = info . metas . styles . findIndex ( ( style ) => style . styleId === audioItem . styleId ) ; if ( result > - 1 ) { styleName = info . metas . styles [ result ] . styleName ; } return result > - 1 ; } ) ; if ( character === undefined ) { throw new Error ( ) ; } const characterName = sanitizeFileName ( character . metas . speakerName ) ; let text = sanitizeFileName ( audioItem . text ) ; if ( text . length > 10 ) { text = text . substring ( 0 , 9 ) + "…" ; } const preFileName = ( index + 1 ) . toString ( ) . padStart ( 3 , "0" ) ; if ( styleName === undefined ) { return preFileName + `_${ characterName }_${ text }.wav` ; } const sanitizedStyleName = sanitizeFileName ( styleName ) ; return preFileName + `_${ characterName }（${ sanitizedStyleName }）_${ text }.wav` ; } const audioBlobCache = { } ; const audioElements = { } ; export const audioStoreState = { engineState : "STARTING" , audioItems : { } , audioKeys : [ ] , audioStates : { } , nowPlayingContinuously : false , } ; export const audioStore = { getters : { ACTIVE_AUDIO_KEY ( state ) { return state . _activeAudioKey !== undefined && state . audioKeys . includes ( state . _activeAudioKey ) ? state . _activeAudioKey : undefined ; } , HAVE_AUDIO_QUERY : ( state ) => ( audioKey ) => { return state . audioItems [ audioKey ] ?. query != undefined ; } , IS_ACTIVE : ( state ) => ( audioKey ) => { return state . _activeAudioKey === audioKey ; } , IS_ENGINE_READY : ( state ) => { return state . engineState === "READY" ; } , } , mutations : { SET_ENGINE_STATE ( state , { engineState } ) { state . engineState = engineState ; } , SET_CHARACTER_INFOS ( state , { characterInfos } ) { state . characterInfos = characterInfos ; } , SET_ACTIVE_AUDIO_KEY ( state , { audioKey } ) { state . _activeAudioKey = audioKey ; } , SET_AUDIO_NOW_PLAYING ( state , { audioKey , nowPlaying } ) { state . audioStates [ audioKey ] . nowPlaying = nowPlaying ; } , SET_AUDIO_NOW_GENERATING ( state , { audioKey , nowGenerating } ) { state . audioStates [ audioKey ] . nowGenerating = nowGenerating ; } , SET_NOW_PLAYING_CONTINUOUSLY ( state , { nowPlaying } ) { state . nowPlayingContinuously = nowPlaying ; } , INSERT_AUDIO_ITEM ( state , { audioItem , audioKey , prevAudioKey , } ) { const index = prevAudioKey !== undefined ? state . audioKeys . indexOf ( prevAudioKey ) + 1 : state . audioKeys . length ; state . audioKeys . splice ( index , 0 , audioKey ) ; state . audioItems [ audioKey ] = audioItem ; state . audioStates [ audioKey ] = { nowPlaying : false , nowGenerating : false , } ; } , INSERT_AUDIO_ITEMS ( state , { prevAudioKey , audioKeyItemPairs , } ) { const index = prevAudioKey !== undefined ? state . audioKeys . indexOf ( prevAudioKey ) + 1 : state . audioKeys . length ; const audioKeys = audioKeyItemPairs . map ( ( pair ) => pair . audioKey ) ; state . audioKeys . splice ( index , 0 , ... audioKeys ) ; for ( const { audioKey , audioItem } of audioKeyItemPairs ) { state . audioItems [ audioKey ] = audioItem ; state . audioStates [ audioKey ] = { nowPlaying : false , nowGenerating : false , } ; } } , REMOVE_AUDIO_ITEM ( state , { audioKey } ) { state . audioKeys . splice ( state . audioKeys . indexOf ( audioKey ) , 1 ) ; delete state . audioItems [ audioKey ] ; delete state . audioStates [ audioKey ] ; } , SET_AUDIO_KEYS ( state , { audioKeys } ) { state . audioKeys = audioKeys ; } , SET_AUDIO_TEXT ( state , { audioKey , text } ) { state . audioItems [ audioKey ] . text = text ; } , SET_AUDIO_SPEED_SCALE ( state , { audioKey , speedScale } ) { const query = state . audioItems [ audioKey ] . query ; if ( query == undefined ) throw new Error ( "query == undefined" ) ; query . speedScale = speedScale ; } , SET_AUDIO_PITCH_SCALE ( state , { audioKey , pitchScale } ) { const query = state . audioItems [ audioKey ] . query ; if ( query == undefined ) throw new Error ( "query == undefined" ) ; query . pitchScale = pitchScale ; } , SET_AUDIO_INTONATION_SCALE ( state , { audioKey , intonationScale , } ) { const query = state . audioItems [ audioKey ] . query ; if ( query == undefined ) throw new Error ( "query == undefined" ) ; query . intonationScale = intonationScale ; } , SET_AUDIO_VOLUME_SCALE ( state , { audioKey , volumeScale } ) { const query = state . audioItems [ audioKey ] . query ; if ( query == undefined ) throw new Error ( "query == undefined" ) ; query . volumeScale = volumeScale ; } , SET_AUDIO_PRE_PHONEME_LENGTH ( state , { audioKey , prePhonemeLength , } ) { const query = state . audioItems [ audioKey ] . query ; if ( query == undefined ) throw new Error ( "query == undefined" ) ; query . prePhonemeLength = prePhonemeLength ; } , SET_AUDIO_POST_PHONEME_LENGTH ( state , { audioKey , postPhonemeLength , } ) { const query = state . audioItems [ audioKey ] . query ; if ( query == undefined ) throw new Error ( "query == undefined" ) ; query . postPhonemeLength = postPhonemeLength ; } , SET_AUDIO_PRESET ( state , { audioKey , presetKey , } ) { if ( presetKey === undefined ) { delete state . audioItems [ audioKey ] . presetKey ; } else { state . audioItems [ audioKey ] . presetKey = presetKey ; } } , APPLY_AUDIO_PRESET ( state , { audioKey } ) { const audioItem = state . audioItems [ audioKey ] ; if ( audioItem == undefined || audioItem . presetKey == undefined || audioItem . query == undefined ) return ; const presetItem = state . presetItems [ audioItem . presetKey ] ; if ( presetItem == undefined ) return ; const { name : _ , ... presetAudioInfos } = presetItem ; const audioInfos = presetAudioInfos ; audioItem . query = { ... audioItem . query , ... audioInfos } ; } , SET_AUDIO_QUERY ( state , { audioKey , audioQuery } ) { state . audioItems [ audioKey ] . query = audioQuery ; } , SET_AUDIO_STYLE_ID ( state , { audioKey , styleId } ) { state . audioItems [ audioKey ] . styleId = styleId ; } , SET_ACCENT_PHRASES ( state , { audioKey , accentPhrases , } ) { const query = state . audioItems [ audioKey ] . query ; if ( query == undefined ) throw new Error ( "query == undefined" ) ; query . accentPhrases = accentPhrases ; } , SET_SINGLE_ACCENT_PHRASE ( state , { audioKey , accentPhraseIndex , accentPhrases , } ) { const query = state . audioItems [ audioKey ] . query ; if ( query == undefined ) throw new Error ( "query == undefined" ) ; query . accentPhrases . splice ( accentPhraseIndex , 1 , ... accentPhrases ) ; } , SET_AUDIO_MORA_DATA ( state , { audioKey , accentPhraseIndex , moraIndex , data , type , } ) { const query = state . audioItems [ audioKey ] . query ; if ( query == undefined ) throw new Error ( "query == undefined" ) ; switch ( type ) { case "pitch" : query . accentPhrases [ accentPhraseIndex ] . moras [ moraIndex ] . pitch = data ; break ; case "consonant" : query . accentPhrases [ accentPhraseIndex ] . moras [ moraIndex ] . consonantLength = data ; break ; case "vowel" : query . accentPhrases [ accentPhraseIndex ] . moras [ moraIndex ] . vowelLength = data ; break ; case "pause" : { const pauseMora = query . accentPhrases [ accentPhraseIndex ] . pauseMora ; if ( pauseMora !== undefined && pauseMora !== null ) { pauseMora . vowelLength = data ; } break ; } case "voicing" : { query . accentPhrases [ accentPhraseIndex ] . moras [ moraIndex ] . pitch = data ; if ( data == 0 ) { query . accentPhrases [ accentPhraseIndex ] . moras [ moraIndex ] . vowel = query . accentPhrases [ accentPhraseIndex ] . moras [ moraIndex ] . vowel . toUpperCase ( ) ; } else { query . accentPhrases [ accentPhraseIndex ] . moras [ moraIndex ] . vowel = query . accentPhrases [ accentPhraseIndex ] . moras [ moraIndex ] . vowel . toLowerCase ( ) ; } } } } , } , actions : { START_WAITING_ENGINE : createUILockAction ( async ( { state , commit , dispatch } ) => { let engineState = state . engineState ; for ( let i = 0 ; i < 100 ; i ++ ) { engineState = state . engineState ; if ( engineState === "FAILED_STARTING" ) { break ; } try { await dispatch ( "INVOKE_ENGINE_CONNECTOR" , { action : "versionVersionGet" , payload : [ ] , } ) . then ( toDispatchResponse ( "versionVersionGet" ) ) ; } catch { await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) ) ; window . electron . logInfo ( "waiting engine..." ) ; continue ; } engineState = "READY" ; commit ( "SET_ENGINE_STATE" , { engineState } ) ; break ; } if ( engineState !== "READY" ) { commit ( "SET_ENGINE_STATE" , { engineState : "FAILED_STARTING" } ) ; } } ) , LOAD_CHARACTER : createUILockAction ( async ( { commit , dispatch } ) => { const speakers = await dispatch ( "INVOKE_ENGINE_CONNECTOR" , { action : "speakersSpeakersGet" , payload : [ ] , } ) . then ( toDispatchResponse ( "speakersSpeakersGet" ) ) . catch ( ( error ) => { window . electron . logError ( error , `Failed to get speakers.` ) ; throw error ; } ) ; const base64ToUrl = function ( base64 , type ) { const buffer = Buffer . from ( base64 , "base64" ) ; const iconBlob = new Blob ( [ buffer . buffer ] , { type : type } ) ; return URL . createObjectURL ( iconBlob ) ; } ; const getStyles = function ( speaker , speakerInfo ) { const styles = new Array ( speaker . styles . length ) ; speaker . styles . forEach ( ( style , i ) => { const styleInfo = speakerInfo . styleInfos . find ( ( styleInfo ) => style . id === styleInfo . id ) ; if ( ! styleInfo ) throw new Error ( `Not found the style id "${ style . id }" of "${ speaker . name }". ` ) ; const voiceSamples = styleInfo . voiceSamples . map ( ( voiceSample ) => { return base64ToUrl ( voiceSample , "audio/wav" ) ; } ) ; styles [ i ] = { styleName : style . name , styleId : style . id , iconPath : base64ToUrl ( styleInfo . icon , "image/png" ) , voiceSamplePaths : voiceSamples , } ; } ) ; return styles ; } ; const getSpeakerInfo = async function ( speaker ) { const speakerInfo = await dispatch ( "INVOKE_ENGINE_CONNECTOR" , { action : "speakerInfoSpeakerInfoGet" , payload : [ { speakerUuid : speaker . speakerUuid } ] , } ) . then ( toDispatchResponse ( "speakerInfoSpeakerInfoGet" ) ) . catch ( ( error ) => { window . electron . logError ( error , `Failed to get speakers.` ) ; throw error ; } ) ; const styles = getStyles ( speaker , speakerInfo ) ; const characterInfo = { portraitPath : base64ToUrl ( speakerInfo . portrait , "image/png" ) , metas : { speakerUuid : speaker . speakerUuid , speakerName : speaker . name , styles : styles , policy : speakerInfo . policy , } , } ; return characterInfo ; } ; const characterInfos = await Promise . all ( speakers . map ( async ( speaker ) => { return await getSpeakerInfo ( speaker ) ; } ) ) ; commit ( "SET_CHARACTER_INFOS" , { characterInfos } ) ; } ) , GENERATE_AUDIO_KEY ( ) { const audioKey = uuidv4 ( ) ; audioElements [ audioKey ] = new Audio ( ) ; return audioKey ; } , REMOVE_ALL_AUDIO_ITEM ( { commit , state } ) { for ( const audioKey of [ ... state . audioKeys ] ) { commit ( "REMOVE_AUDIO_ITEM" , { audioKey } ) ; } } , async GENERATE_AUDIO_ITEM ( { state , getters , dispatch } , payload ) { if ( state . defaultStyleIds == undefined ) throw new Error ( "state.defaultStyleIds == undefined" ) ; if ( state . characterInfos == undefined ) throw new Error ( "state.characterInfos == undefined" ) ; const characterInfos = state . characterInfos ; const text = payload . text ?? "" ; const styleId = payload . styleId ?? state . defaultStyleIds [ state . defaultStyleIds . findIndex ( ( x ) => x . speakerUuid === characterInfos [ 0 ] . metas . speakerUuid ) ] . defaultStyleId ; const baseAudioItem = payload . baseAudioItem ; const query = getters . IS_ENGINE_READY ? await dispatch ( "FETCH_AUDIO_QUERY" , { text , styleId , } ) . catch ( ( ) => undefined ) : undefined ; const audioItem = { text , styleId , } ; if ( query != undefined ) { audioItem . query = query ; } if ( payload . presetKey != undefined ) audioItem . presetKey = payload . presetKey ; if ( baseAudioItem && baseAudioItem . query && audioItem . query ) { audioItem . query . speedScale = baseAudioItem . query . speedScale ; audioItem . query . pitchScale = baseAudioItem . query . pitchScale ; audioItem . query . intonationScale = baseAudioItem . query . intonationScale ; audioItem . query . volumeScale = baseAudioItem . query . volumeScale ; audioItem . query . prePhonemeLength = baseAudioItem . query . prePhonemeLength ; audioItem . query . postPhonemeLength = baseAudioItem . query . postPhonemeLength ; audioItem . query . outputSamplingRate = baseAudioItem . query . outputSamplingRate ; audioItem . query . outputStereo = baseAudioItem . query . outputStereo ; } return audioItem ; } , async REGISTER_AUDIO_ITEM ( { dispatch , commit } , { audioItem , prevAudioKey , } ) { const audioKey = await dispatch ( "GENERATE_AUDIO_KEY" ) ; commit ( "INSERT_AUDIO_ITEM" , { audioItem , audioKey , prevAudioKey } ) ; return audioKey ; } , SET_ACTIVE_AUDIO_KEY ( { commit } , { audioKey } ) { commit ( "SET_ACTIVE_AUDIO_KEY" , { audioKey } ) ; } , async GET_AUDIO_CACHE ( { state } , { audioKey } ) { const audioItem = state . audioItems [ audioKey ] ; const [ id ] = await generateUniqueIdAndQuery ( state , audioItem ) ; if ( Object . prototype . hasOwnProperty . call ( audioBlobCache , id ) ) { return audioBlobCache [ id ] ; } else { return null ; } } , SET_AUDIO_QUERY ( { commit } , payload ) { commit ( "SET_AUDIO_QUERY" , payload ) ; } , FETCH_ACCENT_PHRASES ( { dispatch , state } , { text , styleId , isKana , } ) { return dispatch ( "INVOKE_ENGINE_CONNECTOR" , { action : "accentPhrasesAccentPhrasesPost" , payload : [ { text , speaker : styleId , isKana , enableInterrogative : state . experimentalSetting . enableInterrogative , } , ] , } ) . then ( toDispatchResponse ( "accentPhrasesAccentPhrasesPost" ) ) . catch ( ( error ) => { window . electron . logError ( error , `Failed to fetch AccentPhrases for the text "${ text }".` ) ; throw error ; } ) ; } , FETCH_MORA_DATA ( { dispatch } , { accentPhrases , styleId , } ) { return dispatch ( "INVOKE_ENGINE_CONNECTOR" , { action : "moraDataMoraDataPost" , payload : [ { accentPhrase : accentPhrases , speaker : styleId } ] , } ) . then ( toDispatchResponse ( "moraDataMoraDataPost" ) ) . catch ( ( error ) => { window . electron . logError ( error , `Failed to fetch MoraData for the accentPhrases "${ JSON . stringify ( accentPhrases ) }".` ) ; throw error ; } ) ; } , async FETCH_AND_COPY_MORA_DATA ( { dispatch } , { accentPhrases , styleId , copyIndexes , } ) { const fetchedAccentPhrases = await dispatch ( "FETCH_MORA_DATA" , { accentPhrases , styleId , } ) ; for ( const index of copyIndexes ) { accentPhrases [ index ] = fetchedAccentPhrases [ index ] ; } return accentPhrases ; } , FETCH_AUDIO_QUERY ( { dispatch , state } , { text , styleId } ) { return dispatch ( "INVOKE_ENGINE_CONNECTOR" , { action : "audioQueryAudioQueryPost" , payload : [ { text , speaker : styleId , enableInterrogative : state . experimentalSetting . enableInterrogative , } , ] , } ) . then ( toDispatchResponse ( "audioQueryAudioQueryPost" ) ) . catch ( ( error ) => { window . electron . logError ( error , `Failed to fetch AudioQuery for the text "${ text }".` ) ; throw error ; } ) ; } , GENERATE_LAB : createUILockAction ( async ( { state } , { audioKey , offset } ) => { const query = state . audioItems [ audioKey ] . query ; if ( query == undefined ) return ; const speedScale = query . speedScale ; let labString = "" ; let timestamp = offset !== undefined ? offset : 0 ; labString += timestamp . toFixed ( ) + " " ; timestamp += ( query . prePhonemeLength * 10000000 ) / speedScale ; labString += timestamp . toFixed ( ) + " " ; labString += "pau" + "\n" ; query . accentPhrases . forEach ( ( accentPhrase ) => { accentPhrase . moras . forEach ( ( mora ) => { if ( mora . consonantLength !== undefined && mora . consonant !== undefined ) { labString += timestamp . toFixed ( ) + " " ; timestamp += ( mora . consonantLength * 10000000 ) / speedScale ; labString += timestamp . toFixed ( ) + " " ; labString += mora . consonant + "\n" ; } labString += timestamp . toFixed ( ) + " " ; timestamp += ( mora . vowelLength * 10000000 ) / speedScale ; labString += timestamp . toFixed ( ) + " " ; if ( mora . vowel != "N" ) { labString += mora . vowel . toLowerCase ( ) + "\n" ; } else { labString += mora . vowel + "\n" ; } } ) ; if ( accentPhrase . pauseMora !== undefined && accentPhrase . pauseMora !== null ) { labString += timestamp . toFixed ( ) + " " ; timestamp += ( accentPhrase . pauseMora . vowelLength * 10000000 ) / speedScale ; labString += timestamp . toFixed ( ) + " " ; labString += accentPhrase . pauseMora . vowel + "\n" ; } } ) ; labString += timestamp . toFixed ( ) + " " ; timestamp += ( query . postPhonemeLength * 10000000 ) / speedScale ; labString += timestamp . toFixed ( ) + " " ; labString += "pau" + "\n" ; return labString ; } ) , CONNECT_AUDIO : createUILockAction ( async ( { dispatch } , { encodedBlobs } ) => { return dispatch ( "INVOKE_ENGINE_CONNECTOR" , { action : "connectWavesConnectWavesPost" , payload : [ { requestBody : encodedBlobs , } , ] , } ) . then ( toDispatchResponse ( "connectWavesConnectWavesPost" ) ) . then ( async ( blob ) => { return blob ; } ) . catch ( ( e ) => { window . electron . logError ( e ) ; return null ; } ) ; } ) , GENERATE_AUDIO : createUILockAction ( async ( { dispatch , state } , { audioKey } ) => { const audioItem = JSON . parse ( JSON . stringify ( state . audioItems [ audioKey ] ) ) ; const [ id , audioQuery ] = await generateUniqueIdAndQuery ( state , audioItem ) ; const speaker = audioItem . styleId ; if ( audioQuery == undefined || speaker == undefined ) { return null ; } return dispatch ( "INVOKE_ENGINE_CONNECTOR" , { action : "synthesisSynthesisPost" , payload : [ { audioQuery , speaker , } , ] , } ) . then ( toDispatchResponse ( "synthesisSynthesisPost" ) ) . then ( async ( blob ) => { audioBlobCache [ id ] = blob ; return blob ; } ) . catch ( ( e ) => { window . electron . logError ( e ) ; return null ; } ) ; } ) , GENERATE_AND_SAVE_AUDIO : createUILockAction ( async ( { state , dispatch } , { audioKey , filePath , encoding , } ) : Promise < SaveResultObject > => { if ( state . savingSetting . fixedExportEnabled ) { filePath = path . join ( state . savingSetting . fixedExportDir , buildFileName ( state , audioKey ) ) ; } else { filePath ??= await window . electron . showAudioSaveDialog ( { title : "音声を保存" , defaultPath : buildFileName ( state , audioKey ) , } ) ; } if ( ! filePath ) { return { result : "CANCELED" , path : "" } ; } if ( state . savingSetting . avoidOverwrite ) { let tail = 1 ; const name = filePath . slice ( 0 , filePath . length - 4 ) ; while ( await dispatch ( "CHECK_FILE_EXISTS" , { file : filePath } ) ) { filePath = name + "[" + tail . toString ( ) + "]" + ".wav" ; tail += 1 ; } } let blob = await dispatch ( "GET_AUDIO_CACHE" , { audioKey } ) ; if ( ! blob ) { blob = await dispatch ( "GENERATE_AUDIO" , { audioKey } ) ; if ( ! blob ) { return { result : "ENGINE_ERROR" , path : filePath } ; } } try { window . electron . writeFile ( { filePath , buffer : await blob . arrayBuffer ( ) , } ) ; } catch ( e ) { window . electron . logError ( e ) ; return { result : "WRITE_ERROR" , path : filePath } ; } if ( state . savingSetting . exportLab ) { const labString = await dispatch ( "GENERATE_LAB" , { audioKey } ) ; if ( labString === undefined ) return { result : "WRITE_ERROR" , path : filePath } ; const bom = new Uint8Array ( [ 0xef , 0xbb , 0xbf ] ) ; const labBlob = new Blob ( [ bom , labString ] , { type : "text/plain;charset=UTF-8" , } ) ; try { window . electron . writeFile ( { filePath : filePath . replace ( /\.wav$/ , ".lab" ) , buffer : await labBlob . arrayBuffer ( ) , } ) ; } catch ( e ) { window . electron . logError ( e ) ; return { result : "WRITE_ERROR" , path : filePath } ; } } if ( state . savingSetting . exportText ) { const textBlob = ( ( ) : Blob => { if ( ! encoding || encoding === "UTF-8" ) { const bom = new Uint8Array ( [ 0xef , 0xbb , 0xbf ] ) ; return new Blob ( [ bom , state . audioItems [ audioKey ] . text ] , { type : "text/plain;charset=UTF-8" , } ) ; } const sjisArray = Encoding . convert ( Encoding . stringToCode ( state . audioItems [ audioKey ] . text ) , { to : "SJIS" , type : "arraybuffer" } ) ; return new Blob ( [ new Uint8Array ( sjisArray ) ] , { type : "text/plain;charset=Shift_JIS" , } ) ; } ) ( ) ; try { window . electron . writeFile ( { filePath : filePath . replace ( /\.wav$/ , ".txt" ) , buffer : await textBlob . arrayBuffer ( ) , } ) ; } catch ( e ) { window . electron . logError ( e ) ; return { result : "WRITE_ERROR" , path : filePath } ; } } return { result : "SUCCESS" , path : filePath } ; } ) , GENERATE_AND_SAVE_ALL_AUDIO : createUILockAction ( async ( { state , dispatch } , { dirPath , encoding } ) => { if ( state . savingSetting . fixedExportEnabled ) { dirPath = state . savingSetting . fixedExportDir ; } else { dirPath ??= await window . electron . showOpenDirectoryDialog ( { title : "音声を全て保存" , } ) ; } if ( dirPath ) { const _dirPath = dirPath ; const promises = state . audioKeys . map ( ( audioKey ) => { const name = buildFileName ( state , audioKey ) ; return dispatch ( "GENERATE_AND_SAVE_AUDIO" , { audioKey , filePath : path . join ( _dirPath , name ) , encoding , } ) ; } ) ; return Promise . all ( promises ) ; } } ) , GENERATE_AND_CONNECT_AND_SAVE_AUDIO : createUILockAction ( async ( { state , dispatch } , { filePath , encoding } ) : Promise < SaveResultObject > => { const defaultFileName = buildProjectFileName ( state , "wav" ) ; if ( state . savingSetting . fixedExportEnabled ) { filePath = path . join ( state . savingSetting . fixedExportDir , defaultFileName ) ; } else { filePath ??= await window . electron . showAudioSaveDialog ( { title : "音声を全て繋げて保存" , defaultPath : defaultFileName , } ) ; } if ( ! filePath ) { return { result : "CANCELED" , path : "" } ; } if ( state . savingSetting . avoidOverwrite ) { let tail = 1 ; const name = filePath . slice ( 0 , filePath . length - 4 ) ; while ( await dispatch ( "CHECK_FILE_EXISTS" , { file : filePath } ) ) { filePath = name + "[" + tail . toString ( ) + "]" + ".wav" ; tail += 1 ; } } const encodedBlobs = [ ] ; const labs = [ ] ; const texts = [ ] ; let labOffset = 0 ; const base64Encoder = ( blob ) : Promise < string | undefined > => { return new Promise ( ( resolve , reject ) => { const reader = new FileReader ( ) ; reader . onload = ( ) => { const result = reader . result ?. toString ( ) ; if ( result ) { resolve ( result . slice ( result . indexOf ( "," ) + 1 ) ) ; } else { reject ( ) ; } } ; reader . readAsDataURL ( blob ) ; } ) ; } ; for ( const audioKey of state . audioKeys ) { let blob = await dispatch ( "GET_AUDIO_CACHE" , { audioKey } ) ; if ( ! blob ) { blob = await dispatch ( "GENERATE_AUDIO" , { audioKey } ) ; } if ( blob === null ) { return { result : "ENGINE_ERROR" , path : filePath } ; } const encodedBlob = await base64Encoder ( blob ) ; if ( encodedBlob === undefined ) { return { result : "WRITE_ERROR" , path : filePath } ; } encodedBlobs . push ( encodedBlob ) ; const lab = await dispatch ( "GENERATE_LAB" , { audioKey , offset : labOffset , } ) ; if ( lab === undefined ) { return { result : "WRITE_ERROR" , path : filePath } ; } labs . push ( lab ) ; texts . push ( state . audioItems [ audioKey ] . text ) ; const splitLab = lab . split ( " " ) ; labOffset = Number ( splitLab [ splitLab . length - 2 ] ) ; } const connectedWav = await dispatch ( "CONNECT_AUDIO" , { encodedBlobs , } ) ; if ( ! connectedWav ) { return { result : "ENGINE_ERROR" , path : filePath } ; } try { window . electron . writeFile ( { filePath , buffer : await connectedWav . arrayBuffer ( ) , } ) ; } catch ( e ) { window . electron . logError ( e ) ; return { result : "WRITE_ERROR" , path : filePath } ; } if ( state . savingSetting . exportLab ) { const labString = labs . join ( "" ) ; const bom = new Uint8Array ( [ 0xef , 0xbb , 0xbf ] ) ; const labBlob = new Blob ( [ bom , labString ] , { type : "text/plain;charset=UTF-8" , } ) ; try { window . electron . writeFile ( { filePath : filePath . replace ( /\.wav$/ , ".lab" ) , buffer : await labBlob . arrayBuffer ( ) , } ) ; } catch ( e ) { window . electron . logError ( e ) ; return { result : "WRITE_ERROR" , path : filePath } ; } } if ( state . savingSetting . exportText ) { const textBlob = ( ( ) : Blob => { const text = texts . join ( "\n" ) ; if ( ! encoding || encoding === "UTF-8" ) { const bom = new Uint8Array ( [ 0xef , 0xbb , 0xbf ] ) ; return new Blob ( [ bom , text ] , { type : "text/plain;charset=UTF-8" , } ) ; } const sjisArray = Encoding . convert ( Encoding . stringToCode ( text ) , { to : "SJIS" , type : "arraybuffer" , } ) ; return new Blob ( [ new Uint8Array ( sjisArray ) ] , { type : "text/plain;charset=Shift_JIS" , } ) ; } ) ( ) ; try { window . electron . writeFile ( { filePath : filePath . replace ( /\.wav$/ , ".txt" ) , buffer : await textBlob . arrayBuffer ( ) , } ) ; } catch ( e ) { window . electron . logError ( e ) ; return { result : "WRITE_ERROR" , path : filePath } ; } } return { result : "SUCCESS" , path : filePath } ; } ) , PLAY_AUDIO : createUILockAction ( async ( { state , commit , dispatch } , { audioKey } ) => { const audioElem = audioElements [ audioKey ] as HTMLAudioElement & { setSinkId ( deviceID ) : Promise < undefined > ; } ; audioElem . pause ( ) ; let blob = await dispatch ( "GET_AUDIO_CACHE" , { audioKey } ) ; if ( ! blob ) { commit ( "SET_AUDIO_NOW_GENERATING" , { audioKey , nowGenerating : true , } ) ; blob = await dispatch ( "GENERATE_AUDIO" , { audioKey } ) ; commit ( "SET_AUDIO_NOW_GENERATING" , { audioKey , nowGenerating : false , } ) ; if ( ! blob ) { throw new Error ( ) ; } } audioElem . src = URL . createObjectURL ( blob ) ; audioElem . setSinkId ( state . savingSetting . audioOutputDevice ) . catch ( ( err ) => { const stop = ( ) => { audioElem . pause ( ) ; audioElem . removeEventListener ( "canplay" , stop ) ; } ; audioElem . addEventListener ( "canplay" , stop ) ; window . electron . showErrorDialog ( { title : "エラー" , message : "再生デバイスが見つかりません" , } ) ; throw new Error ( err ) ; } ) ; const played = async ( ) => { commit ( "SET_AUDIO_NOW_PLAYING" , { audioKey , nowPlaying : true } ) ; } ; audioElem . addEventListener ( "play" , played ) ; let paused ; const audioPlayPromise = new Promise < boolean > ( ( resolve ) => { paused = ( ) => { resolve ( audioElem . ended ) ; } ; audioElem . addEventListener ( "pause" , paused ) ; } ) . finally ( async ( ) => { audioElem . removeEventListener ( "play" , played ) ; audioElem . removeEventListener ( "pause" , paused ) ; commit ( "SET_AUDIO_NOW_PLAYING" , { audioKey , nowPlaying : false } ) ; } ) ; audioElem . play ( ) ; return audioPlayPromise ; } ) , STOP_AUDIO ( _ , { audioKey } ) { const audioElem = audioElements [ audioKey ] ; audioElem . pause ( ) ; } , PLAY_CONTINUOUSLY_AUDIO : createUILockAction ( async ( { state , commit , dispatch } ) => { const currentAudioKey = state . _activeAudioKey ; let index = 0 ; if ( currentAudioKey !== undefined ) { index = state . audioKeys . findIndex ( ( v ) => v === currentAudioKey ) ; } commit ( "SET_NOW_PLAYING_CONTINUOUSLY" , { nowPlaying : true } ) ; try { for ( let i = index ; i < state . audioKeys . length ; ++ i ) { const audioKey = state . audioKeys [ i ] ; commit ( "SET_ACTIVE_AUDIO_KEY" , { audioKey } ) ; const isEnded = await dispatch ( "PLAY_AUDIO" , { audioKey } ) ; if ( ! isEnded ) { break ; } } } finally { commit ( "SET_ACTIVE_AUDIO_KEY" , { audioKey : currentAudioKey } ) ; commit ( "SET_NOW_PLAYING_CONTINUOUSLY" , { nowPlaying : false } ) ; } } ) , STOP_CONTINUOUSLY_AUDIO ( { state , dispatch } ) { for ( const audioKey of state . audioKeys ) { if ( state . audioStates [ audioKey ] . nowPlaying ) { dispatch ( "STOP_AUDIO" , { audioKey } ) ; } } } , OPEN_TEXT_EDIT_CONTEXT_MENU ( ) { window . electron . openTextEditContextMenu ( ) ; } , DETECTED_ENGINE_ERROR ( { state , commit } ) { switch ( state . engineState ) { case "STARTING" : commit ( "SET_ENGINE_STATE" , { engineState : "FAILED_STARTING" } ) ; break ; case "READY" : commit ( "SET_ENGINE_STATE" , { engineState : "ERROR" } ) ; break ; default : commit ( "SET_ENGINE_STATE" , { engineState : "ERROR" } ) ; } } , async RESTART_ENGINE ( { dispatch , commit } ) { await commit ( "SET_ENGINE_STATE" , { engineState : "STARTING" } ) ; window . electron . restartEngine ( ) . then ( ( ) => dispatch ( "START_WAITING_ENGINE" ) ) . catch ( ( ) => dispatch ( "DETECTED_ENGINE_ERROR" ) ) ; } , CHECK_FILE_EXISTS ( _ , { file } ) { return window . electron . checkFileExists ( file ) ; } , } , } ; export const audioCommandStoreState = { } ; export const audioCommandStore = { getters : { } , actions : { async COMMAND_REGISTER_AUDIO_ITEM ( { dispatch , commit } , { audioItem , prevAudioKey , } ) { const audioKey = await dispatch ( "GENERATE_AUDIO_KEY" ) ; commit ( "COMMAND_REGISTER_AUDIO_ITEM" , { audioItem , audioKey , prevAudioKey , } ) ; return audioKey ; } , COMMAND_REMOVE_AUDIO_ITEM ( { commit } , payload ) { commit ( "COMMAND_REMOVE_AUDIO_ITEM" , payload ) ; } , COMMAND_SET_AUDIO_KEYS ( { commit } , payload ) { commit ( "COMMAND_SET_AUDIO_KEYS" , payload ) ; } , async COMMAND_CHANGE_AUDIO_TEXT ( { state , commit , dispatch } , { audioKey , text } ) { const styleId = state . audioItems [ audioKey ] . styleId ; if ( styleId == undefined ) throw new Error ( "styleId != undefined" ) ; const query = state . audioItems [ audioKey ] . query ; try { if ( query !== undefined ) { const accentPhrases = await dispatch ( "FETCH_ACCENT_PHRASES" , { text , styleId , } ) ; commit ( "COMMAND_CHANGE_AUDIO_TEXT" , { audioKey , text , update : "AccentPhrases" , accentPhrases , } ) ; } else { const newAudioQuery = await dispatch ( "FETCH_AUDIO_QUERY" , { text , styleId , } ) ; commit ( "COMMAND_CHANGE_AUDIO_TEXT" , { audioKey , text , update : "AudioQuery" , query : newAudioQuery , } ) ; } } catch ( error ) { commit ( "COMMAND_CHANGE_AUDIO_TEXT" , { audioKey , text , update : "Text" , } ) ; throw error ; } } , async COMMAND_CHANGE_STYLE_ID ( { state , dispatch , commit } , { audioKey , styleId } ) { const query = state . audioItems [ audioKey ] . query ; try { if ( query !== undefined ) { const accentPhrases = query . accentPhrases ; const newAccentPhrases = await dispatch ( "FETCH_MORA_DATA" , { accentPhrases , styleId , } ) ; commit ( "COMMAND_CHANGE_STYLE_ID" , { styleId , audioKey : audioKey , update : "AccentPhrases" , accentPhrases : newAccentPhrases , } ) ; } else { const text = state . audioItems [ audioKey ] . text ; const query = await dispatch ( "FETCH_AUDIO_QUERY" , { text : text , styleId , } ) ; commit ( "COMMAND_CHANGE_STYLE_ID" , { styleId , audioKey , update : "AudioQuery" , query , } ) ; } } catch ( error ) { commit ( "COMMAND_CHANGE_STYLE_ID" , { styleId , audioKey , update : "StyleId" , } ) ; throw error ; } } , async COMMAND_CHANGE_ACCENT ( { state , dispatch , commit } , { audioKey , accentPhraseIndex , accent , } ) { const query = state . audioItems [ audioKey ] . query ; if ( query !== undefined ) { const newAccentPhrases = JSON . parse ( JSON . stringify ( query . accentPhrases ) ) ; newAccentPhrases [ accentPhraseIndex ] . accent = accent ; try { const styleId = state . audioItems [ audioKey ] . styleId ; if ( styleId == undefined ) throw new Error ( "styleId != undefined" ) ; const resultAccentPhrases = await dispatch ( "FETCH_AND_COPY_MORA_DATA" , { accentPhrases : newAccentPhrases , styleId , copyIndexes : [ accentPhraseIndex ] , } ) ; commit ( "COMMAND_CHANGE_ACCENT" , { audioKey , accentPhrases : resultAccentPhrases , } ) ; } catch ( error ) { commit ( "COMMAND_CHANGE_ACCENT" , { audioKey , accentPhrases : newAccentPhrases , } ) ; throw error ; } } } , async COMMAND_CHANGE_ACCENT_PHRASE_SPLIT ( { state , dispatch , commit } , payload ) { const { audioKey , accentPhraseIndex } = payload ; const query = state . audioItems [ audioKey ] . query ; const styleId = state . audioItems [ audioKey ] . styleId ; if ( styleId == undefined ) throw new Error ( "styleId != undefined" ) ; if ( query === undefined ) { throw Error ( "`COMMAND_CHANGE_ACCENT_PHRASE_SPLIT` should not be called if the query does not exist." ) ; } const newAccentPhrases = JSON . parse ( JSON . stringify ( query . accentPhrases ) ) ; const changeIndexes = [ accentPhraseIndex ] ; { const mergeAccent = ( accentPhrases , accentPhraseIndex ) => { const newAccentPhrase = { moras : [ ... accentPhrases [ accentPhraseIndex ] . moras , ... accentPhrases [ accentPhraseIndex + 1 ] . moras , ] , accent : accentPhrases [ accentPhraseIndex ] . accent , pauseMora : accentPhrases [ accentPhraseIndex + 1 ] . pauseMora , } ; accentPhrases . splice ( accentPhraseIndex , 2 , newAccentPhrase ) ; } ; const splitAccent = ( accentPhrases , accentPhraseIndex , moraIndex ) => { const newAccentPhrase1 = { moras : accentPhrases [ accentPhraseIndex ] . moras . slice ( 0 , moraIndex + 1 ) , accent : accentPhrases [ accentPhraseIndex ] . accent > moraIndex ? moraIndex + 1 : accentPhrases [ accentPhraseIndex ] . accent , pauseMora : undefined , } ; const newAccentPhrase2 = { moras : accentPhrases [ accentPhraseIndex ] . moras . slice ( moraIndex + 1 ) , accent : accentPhrases [ accentPhraseIndex ] . accent > moraIndex + 1 ? accentPhrases [ accentPhraseIndex ] . accent - moraIndex - 1 : 1 , pauseMora : accentPhrases [ accentPhraseIndex ] . pauseMora , } ; accentPhrases . splice ( accentPhraseIndex , 1 , newAccentPhrase1 , newAccentPhrase2 ) ; } ; if ( payload . isPause ) { mergeAccent ( newAccentPhrases , accentPhraseIndex ) ; } else { const moraIndex = payload . moraIndex ; if ( moraIndex === newAccentPhrases [ accentPhraseIndex ] . moras . length - 1 ) { mergeAccent ( newAccentPhrases , accentPhraseIndex ) ; } else { splitAccent ( newAccentPhrases , accentPhraseIndex , moraIndex ) ; changeIndexes . push ( accentPhraseIndex + 1 ) ; } } } try { const resultAccentPhrases = await dispatch ( "FETCH_AND_COPY_MORA_DATA" , { accentPhrases : newAccentPhrases , styleId , copyIndexes : changeIndexes , } ) ; commit ( "COMMAND_CHANGE_ACCENT_PHRASE_SPLIT" , { audioKey , accentPhrases : resultAccentPhrases , } ) ; } catch ( error ) { commit ( "COMMAND_CHANGE_ACCENT_PHRASE_SPLIT" , { audioKey , accentPhrases : newAccentPhrases , } ) ; throw error ; } } , async COMMAND_CHANGE_SINGLE_ACCENT_PHRASE ( { state , dispatch , commit } , { audioKey , newPronunciation , accentPhraseIndex , popUntilPause , } ) { const styleId = state . audioItems [ audioKey ] . styleId ; if ( styleId == undefined ) throw new Error ( "styleId != undefined" ) ; let newAccentPhrasesSegment = undefined ; const kanaRegex = /^[\u3041-\u3094\u30A1-\u30F4\u30FC]+$/ ; if ( kanaRegex . test ( newPronunciation ) ) { const katakana = newPronunciation . replace ( /[\u3041-\u3094]/g , ( s ) => { return String . fromCharCode ( s . charCodeAt ( 0 ) + 0x60 ) ; } ) ; const pureKatakana = katakana . replace ( /(?<=[アカサタナハマヤラワャァガザダバパ]ー*)ー/g , "ア" ) . replace ( /(?<=[イキシチニヒミリィギジヂビピ]ー*)ー/g , "イ" ) . replace ( /(?<=[ウクスツヌフムユルュゥヴグズヅブプ]ー*)ー/g , "ウ" ) . replace ( /(?<=[エケセテネヘメレェゲゼデベペ]ー*)ー/g , "エ" ) . replace ( /(?<=[オコソトノホモヨロヲョォゴゾドボポ]ー*)ー/g , "オ" ) . replace ( /(?<=[ン]ー*)ー/g , "ン" ) . replace ( /(?<=[ッ]ー*)ー/g , "ッ" ) ; newAccentPhrasesSegment = await dispatch ( "FETCH_ACCENT_PHRASES" , { text : pureKatakana + "'" , styleId , isKana : true , } ) . catch ( ( ) => dispatch ( "FETCH_ACCENT_PHRASES" , { text : newPronunciation , styleId , isKana : false , } ) ) ; } else { newAccentPhrasesSegment = await dispatch ( "FETCH_ACCENT_PHRASES" , { text : newPronunciation , styleId , } ) ; } if ( popUntilPause ) { while ( newAccentPhrasesSegment [ newAccentPhrasesSegment . length - 1 ] . pauseMora === undefined ) { newAccentPhrasesSegment . pop ( ) ; } } const query = state . audioItems [ audioKey ] . query ; if ( query == undefined ) throw new Error ( "query == undefined" ) ; const originAccentPhrases = query . accentPhrases ; const newAccentPhrases = [ ... originAccentPhrases . slice ( 0 , accentPhraseIndex ) , ... newAccentPhrasesSegment , ... originAccentPhrases . slice ( accentPhraseIndex + 1 ) , ] ; const copyIndexes = newAccentPhrasesSegment . map ( ( _ , i ) => accentPhraseIndex + i ) ; try { const resultAccentPhrases = await dispatch ( "FETCH_AND_COPY_MORA_DATA" , { accentPhrases : newAccentPhrases , styleId , copyIndexes , } ) ; commit ( "COMMAND_CHANGE_SINGLE_ACCENT_PHRASE" , { audioKey , accentPhrases : resultAccentPhrases , } ) ; } catch ( error ) { commit ( "COMMAND_CHANGE_SINGLE_ACCENT_PHRASE" , { audioKey , accentPhrases : newAccentPhrases , } ) ; } } , COMMAND_SET_AUDIO_MORA_DATA ( { commit } , payload ) { commit ( "COMMAND_SET_AUDIO_MORA_DATA" , payload ) ; } , COMMAND_SET_AUDIO_MORA_DATA_ACCENT_PHRASE ( { commit } , payload ) { commit ( "COMMAND_SET_AUDIO_MORA_DATA_ACCENT_PHRASE" , payload ) ; } , COMMAND_SET_AUDIO_SPEED_SCALE ( { commit } , payload ) { commit ( "COMMAND_SET_AUDIO_SPEED_SCALE" , payload ) ; } , COMMAND_SET_AUDIO_PITCH_SCALE ( { commit } , payload ) { commit ( "COMMAND_SET_AUDIO_PITCH_SCALE" , payload ) ; } , COMMAND_SET_AUDIO_INTONATION_SCALE ( { commit } , payload ) { commit ( "COMMAND_SET_AUDIO_INTONATION_SCALE" , payload ) ; } , COMMAND_SET_AUDIO_VOLUME_SCALE ( { commit } , payload ) { commit ( "COMMAND_SET_AUDIO_VOLUME_SCALE" , payload ) ; } , COMMAND_SET_AUDIO_PRE_PHONEME_LENGTH ( { commit } , payload ) { commit ( "COMMAND_SET_AUDIO_PRE_PHONEME_LENGTH" , payload ) ; } , COMMAND_SET_AUDIO_POST_PHONEME_LENGTH ( { commit } , payload ) { commit ( "COMMAND_SET_AUDIO_POST_PHONEME_LENGTH" , payload ) ; } , COMMAND_SET_AUDIO_PRESET : ( { commit } , { audioKey , presetKey , } ) => { commit ( "COMMAND_SET_AUDIO_PRESET" , { audioKey , presetKey } ) ; } , COMMAND_APPLY_AUDIO_PRESET : ( { commit } , payload ) => { commit ( "COMMAND_APPLY_AUDIO_PRESET" , payload ) ; } , COMMAND_FULLY_APPLY_AUDIO_PRESET : ( { commit } , payload ) => { commit ( "COMMAND_FULLY_APPLY_AUDIO_PRESET" , payload ) ; } , COMMAND_IMPORT_FROM_FILE : createUILockAction ( async ( { state , commit , dispatch } , { filePath } ) => { if ( ! filePath ) { filePath = await window . electron . showImportFileDialog ( { title : "セリフ読み込み" , } ) ; if ( ! filePath ) return ; } let body = new TextDecoder ( "utf-8" ) . decode ( await window . electron . readFile ( { filePath } ) ) ; if ( body . indexOf ( "\ufffd" ) > - 1 ) { body = new TextDecoder ( "shift-jis" ) . decode ( await window . electron . readFile ( { filePath } ) ) ; } const audioItems = [ ] ; let baseAudioItem = undefined ; if ( state . inheritAudioInfo ) { baseAudioItem = state . _activeAudioKey ? state . audioItems [ state . _activeAudioKey ] : undefined ; } for ( const { text , styleId } of parseTextFile ( body , state . defaultStyleIds , state . characterInfos ) ) { audioItems . push ( await dispatch ( "GENERATE_AUDIO_ITEM" , { text , styleId , baseAudioItem , } ) ) ; } const audioKeys = await Promise . all ( audioItems . map ( ( ) => dispatch ( "GENERATE_AUDIO_KEY" ) ) ) ; const audioKeyItemPairs = audioItems . map ( ( audioItem , index ) => ( { audioItem , audioKey : audioKeys [ index ] , } ) ) ; commit ( "COMMAND_IMPORT_FROM_FILE" , { audioKeyItemPairs , } ) ; return audioKeys ; } ) , COMMAND_PUT_TEXTS : createUILockAction ( async ( { state , commit , dispatch } , { prevAudioKey , texts , styleId , } ) => { const audioKeyItemPairs = [ ] ; let baseAudioItem = undefined ; if ( state . inheritAudioInfo ) { baseAudioItem = state . _activeAudioKey ? state . audioItems [ state . _activeAudioKey ] : undefined ; } for ( const text of texts . filter ( ( value ) => value != "" ) ) { const audioKey = await dispatch ( "GENERATE_AUDIO_KEY" ) ; const audioItem = await dispatch ( "GENERATE_AUDIO_ITEM" , { text , styleId , baseAudioItem , } ) ; audioKeyItemPairs . push ( { audioKey , audioItem , } ) ; } const audioKeys = audioKeyItemPairs . map ( ( value ) => value . audioKey ) ; commit ( "COMMAND_PUT_TEXTS" , { prevAudioKey , audioKeyItemPairs , } ) ; return audioKeys ; } ) , } , mutations : commandMutationsCreator ( { COMMAND_REGISTER_AUDIO_ITEM ( draft , payload ) { audioStore . mutations . INSERT_AUDIO_ITEM ( draft , payload ) ; audioStore . mutations . APPLY_AUDIO_PRESET ( draft , { audioKey : payload . audioKey , } ) ; } , COMMAND_REMOVE_AUDIO_ITEM ( draft , payload ) { audioStore . mutations . REMOVE_AUDIO_ITEM ( draft , payload ) ; } , COMMAND_SET_AUDIO_KEYS ( draft , payload ) { audioStore . mutations . SET_AUDIO_KEYS ( draft , payload ) ; } , COMMAND_CHANGE_AUDIO_TEXT ( draft , payload ) { audioStore . mutations . SET_AUDIO_TEXT ( draft , { audioKey : payload . audioKey , text : payload . text , } ) ; if ( payload . update == "AccentPhrases" ) { audioStore . mutations . SET_ACCENT_PHRASES ( draft , { audioKey : payload . audioKey , accentPhrases : payload . accentPhrases , } ) ; } else if ( payload . update == "AudioQuery" ) { audioStore . mutations . SET_AUDIO_QUERY ( draft , { audioKey : payload . audioKey , audioQuery : payload . query , } ) ; audioStore . mutations . APPLY_AUDIO_PRESET ( draft , { audioKey : payload . audioKey , } ) ; } } , COMMAND_CHANGE_STYLE_ID ( draft , payload ) { audioStore . mutations . SET_AUDIO_STYLE_ID ( draft , { audioKey : payload . audioKey , styleId : payload . styleId , } ) ; if ( payload . update == "AccentPhrases" ) { audioStore . mutations . SET_ACCENT_PHRASES ( draft , { audioKey : payload . audioKey , accentPhrases : payload . accentPhrases , } ) ; } else if ( payload . update == "AudioQuery" ) { audioStore . mutations . SET_AUDIO_QUERY ( draft , { audioKey : payload . audioKey , audioQuery : payload . query , } ) ; audioStore . mutations . APPLY_AUDIO_PRESET ( draft , { audioKey : payload . audioKey , } ) ; } } , COMMAND_CHANGE_ACCENT ( draft , { audioKey , accentPhrases , } ) { audioStore . mutations . SET_ACCENT_PHRASES ( draft , { audioKey , accentPhrases , } ) ; } , COMMAND_CHANGE_ACCENT_PHRASE_SPLIT ( draft , payload ) { audioStore . mutations . SET_ACCENT_PHRASES ( draft , payload ) ; } , COMMAND_CHANGE_SINGLE_ACCENT_PHRASE ( draft , payload ) { audioStore . mutations . SET_ACCENT_PHRASES ( draft , payload ) ; } , COMMAND_SET_AUDIO_MORA_DATA ( draft , payload ) { audioStore . mutations . SET_AUDIO_MORA_DATA ( draft , payload ) ; } , COMMAND_SET_AUDIO_MORA_DATA_ACCENT_PHRASE ( draft , payload ) { const maxPitch = 6.5 ; const minPitch = 3 ; const maxMoraLength = 0.3 ; const minMoraLength = 0 ; const { audioKey , accentPhraseIndex , moraIndex , data , type } = payload ; const audioItem = draft . audioItems [ audioKey ] ; if ( audioItem . query === undefined ) { throw Error ( "draft.audioItems[audioKey].query === undefined" ) ; } const accentPhrase = audioItem . query . accentPhrases [ accentPhraseIndex ] ; const targetMora = accentPhrase . moras [ moraIndex ] ; let diffData = data ; switch ( type ) { case "pitch" : diffData -= targetMora . pitch ; break ; case "consonant" : if ( targetMora . consonantLength !== undefined ) { diffData -= targetMora . consonantLength ; } break ; case "vowel" : diffData -= targetMora . vowelLength ; break ; } accentPhrase . moras . forEach ( ( mora , moraIndex ) => { switch ( type ) { case "pitch" : if ( mora . pitch > 0 ) { const newData = Math . max ( minPitch , Math . min ( maxPitch , mora . pitch + diffData ) ) ; audioStore . mutations . SET_AUDIO_MORA_DATA ( draft , { audioKey , accentPhraseIndex , moraIndex , data : newData , type , } ) ; } break ; case "consonant" : case "vowel" : if ( mora . consonantLength !== undefined ) { audioStore . mutations . SET_AUDIO_MORA_DATA ( draft , { audioKey , accentPhraseIndex , moraIndex , data : Math . max ( minMoraLength , Math . min ( maxMoraLength , mora . consonantLength + diffData ) ) , type : "consonant" , } ) ; } audioStore . mutations . SET_AUDIO_MORA_DATA ( draft , { audioKey , accentPhraseIndex , moraIndex , data : Math . max ( minMoraLength , Math . min ( maxMoraLength , mora . vowelLength + diffData ) ) , type : "vowel" , } ) ; break ; } } ) ; } , COMMAND_SET_AUDIO_SPEED_SCALE ( draft , payload ) { audioStore . mutations . SET_AUDIO_SPEED_SCALE ( draft , payload ) ; } , COMMAND_SET_AUDIO_PITCH_SCALE ( draft , payload ) { audioStore . mutations . SET_AUDIO_PITCH_SCALE ( draft , payload ) ; } , COMMAND_SET_AUDIO_INTONATION_SCALE ( draft , payload ) { audioStore . mutations . SET_AUDIO_INTONATION_SCALE ( draft , payload ) ; } , COMMAND_SET_AUDIO_VOLUME_SCALE ( draft , payload ) { audioStore . mutations . SET_AUDIO_VOLUME_SCALE ( draft , payload ) ; } , COMMAND_SET_AUDIO_PRE_PHONEME_LENGTH ( draft , payload ) { audioStore . mutations . SET_AUDIO_PRE_PHONEME_LENGTH ( draft , payload ) ; } , COMMAND_SET_AUDIO_POST_PHONEME_LENGTH ( draft , payload ) { audioStore . mutations . SET_AUDIO_POST_PHONEME_LENGTH ( draft , payload ) ; } , COMMAND_SET_AUDIO_PRESET : ( draft , { audioKey , presetKey , } ) => { audioStore . mutations . SET_AUDIO_PRESET ( draft , { audioKey , presetKey } ) ; audioStore . mutations . APPLY_AUDIO_PRESET ( draft , { audioKey } ) ; } , COMMAND_APPLY_AUDIO_PRESET ( draft , payload ) { audioStore . mutations . APPLY_AUDIO_PRESET ( draft , payload ) ; } , COMMAND_FULLY_APPLY_AUDIO_PRESET ( draft , { presetKey } ) { const targetAudioKeys = draft . audioKeys . filter ( ( audioKey ) => draft . audioItems [ audioKey ] . presetKey === presetKey ) ; for ( const audioKey of targetAudioKeys ) { audioStore . mutations . APPLY_AUDIO_PRESET ( draft , { audioKey } ) ; } } , COMMAND_IMPORT_FROM_FILE ( draft , { audioKeyItemPairs , } ) { audioStore . mutations . INSERT_AUDIO_ITEMS ( draft , { audioKeyItemPairs , prevAudioKey : undefined , } ) ; } , COMMAND_PUT_TEXTS ( draft , { audioKeyItemPairs , prevAudioKey , } ) { audioStore . mutations . INSERT_AUDIO_ITEMS ( draft , { audioKeyItemPairs , prevAudioKey , } ) ; } , } ) , } ; const toDispatchResponse = < T extends keyof IEngineConnectorFactoryActions > ( _ ) => ( response ) : PromiseType < ReturnType < IEngineConnectorFactoryActions [ T ] > > => { _ ; return response ; } ;