Skip to content

Commit

Permalink
feat: 支持设置语速
Browse files Browse the repository at this point in the history
  • Loading branch information
liou666 committed Apr 14, 2023
1 parent 52d85a7 commit d54a12e
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 3 deletions.
11 changes: 8 additions & 3 deletions src/components/Content.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ const { el, scrollToBottom } = useScroll()
const {
language,
voiceName,
rate,
isRecognizing,
recognizeSpeech,
textToSpeak,
ssmlToSpeak,
isSynthesizing,
} = useSpeechService(getOpenAzureKey(), getOpenAzureRegion(), store.allLanguage as any)
Expand All @@ -33,21 +35,23 @@ const speakIndex = ref(0) // record speak
const translateIndex = ref(0) // record translate
const messageLength = computed(() => store.getConversationsByCurrentProps('chatMessages').length)
const chatMessages = computed(() => store.getConversationsByCurrentProps('chatMessages').slice(1))
const chatMessages = computed(() => store.getConversationsByCurrentProps('chatMessages').slice(1))// 除去第一条系统设置的消息
const currentChatMessages = computed(() => store.getConversationsByCurrentProps('chatMessages'))
const currentKey = computed(() => store.currentKey)
const currentName = computed(() => store.getConversationsByCurrentProps('name'))
const currentAvatar = computed(() => store.getConversationsByCurrentProps('avatar'))
const currentLanguage = computed(() => store.getConversationsByCurrentProps('language'))
const currentVoice = computed(() => store.getConversationsByCurrentProps('voice'))
const currentRate = computed(() => store.getConversationsByCurrentProps('rate'))
useTitle(currentName)
// effects
watch(messageLength, () => nextTick(() => scrollToBottom()))
watch(currentKey, () => {
language.value = currentLanguage.value as any
voiceName.value = currentVoice.value as any
voiceName.value = currentVoice.value
rate.value = currentRate.value
})
// methods
Expand All @@ -66,6 +70,7 @@ const fetchResponse = async (key: string) => {
const onSubmit = async () => {
const key = getOpenKey()
if (!verifyOpenKey(key)) return alert('请输入正确的API-KEY')
if (!message.value) return
Expand Down Expand Up @@ -96,7 +101,7 @@ const onSubmit = async () => {
function speak(content: string, index: number) {
speakIndex.value = index
text.value = content
textToSpeak(content)
ssmlToSpeak(content)
}
const recognize = async () => {
Expand Down
9 changes: 9 additions & 0 deletions src/components/NewChat.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const filterVoices = ref<VoiceInfo[]>([])
const selectVoiceName = ref('')
const desc = ref('')
const name = ref('')
const rate = ref('1.0')
const canAdd = computed(() => !!(selectLanguage.value && selectVoiceName.value && desc.value && name.value))
Expand All @@ -43,6 +44,7 @@ const addChat = (event: any) => {
name: name.value,
key: uuid(),
avatar: getAvatarUrl(avatarList.value[currentAvatarIndex.value]),
rate: +rate.value,
})
emits('close')
}
Expand Down Expand Up @@ -84,6 +86,13 @@ const changeAvatar = () => {
</option>
</select>
</div>
<div flex>
<label center-y justify-end mr-2 for="">语速</label>
<div w-55 flex>
<input v-model="rate" flex-1 type="range" step="0.1" min="0.1" max="2.0">
<span w-4 ml-1>{{ Number(rate).toFixed(1) }}</span>
</div>
</div>
<!-- todo -->
<!-- <div flex>
<label center-y justify-end mr-2 for="">预设</label>
Expand Down
25 changes: 25 additions & 0 deletions src/hooks/useSpeechService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const useSpeechService = (subscriptionKey: string, region: string, langs
const isRecognizing = ref(false) // 语音识别中
const isSynthesizing = ref(false) // 语音合成中
const isFetchAllVoices = ref(false) // 是否在请求所有语音列表
const rate = ref(1) // 语速 (0,2]

const allVoices = ref<VoiceInfo[]>([])

Expand All @@ -28,6 +29,7 @@ export const useSpeechService = (subscriptionKey: string, region: string, langs
speechConfig.value.speechRecognitionLanguage = lang
speechConfig.value.speechSynthesisLanguage = lang
speechConfig.value.speechSynthesisVoiceName = voice

recognizer.value = new SpeechRecognizer(speechConfig.value, audioConfig)
synthesizer.value = new SpeechSynthesizer(speechConfig.value)
}, {
Expand Down Expand Up @@ -85,6 +87,27 @@ export const useSpeechService = (subscriptionKey: string, region: string, langs
})
}

const ssmlToSpeak = async (text: string) => {
isSynthesizing.value = true

const lang = speechConfig.value.speechSynthesisLanguage
const voice = speechConfig.value.speechSynthesisVoiceName

const ssml = `
<speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="${lang}">
<voice name="${voice}">
<prosody rate="${rate.value}">
${text}
</prosody>
</voice>
</speak>`

synthesizer.value.speakSsmlAsync(ssml, () => {
isSynthesizing.value = false
},
)
}

// 停止语音合成
const stopTextToSpeak = () => {
synthesizer.value.close()
Expand Down Expand Up @@ -122,10 +145,12 @@ export const useSpeechService = (subscriptionKey: string, region: string, langs
stopRecognizeSpeech,
recognizeSpeech,
textToSpeak,
ssmlToSpeak,
stopTextToSpeak,
getVoices,
allVoices,
isSynthesizing,
isFetchAllVoices,
rate,
}
}
2 changes: 2 additions & 0 deletions src/stores/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const defaultConversations = [{
language: 'en-US',
voice: 'en-US-JennyMultilingualNeural',
avatar: getAvatarUrl('en.jpg'),
rate: 1,
chatMessages: [{
role: 'system',
content: generatePrompt('English'),
Expand All @@ -26,6 +27,7 @@ export interface Conversation {
language: string // tts stt
voice: string // 参考 https://aka.ms/speech/tts-languages
avatar: string // 用户头像
rate: number // 语速
}

export interface State{
Expand Down

0 comments on commit d54a12e

Please sign in to comment.