Code: Select all
;TTS - Sapi 5 (XP et plus) et Speech SDK 11 (Vista et plus)
;from https://www.purebasic.fr/french/viewtopic.php?f=6&t=15716
;[old code by nico at 12/23/15-13:00]添加了 SkipStream (...) 功能, 允许您绕过当前回放, 向前或向后。
;by gurj add Pitch and getSpeakPosition() and speakFromCursor, but has question,has better way? thanks.
#CLSCTX_INPROC_SERVER=1
;// Valeur pour SpeechVoiceSpeakFlags
#SVSFDefault = 0
#SVSFlagsAsync = 1
#SVSFPurgeBeforeSpeak = 2
#SVSFIsFilename = 4
#SVSFIsXML = 8
#SVSFIsNotXML = 16
#SVSFPersistXML = 32
#SVSFNLPSpeakPunc = 64
#SVSFNLPMask = 64
#SVSFVoiceMask = 127
#SVSFUnusedFlags = -128
Enumeration ; SPSTREAMFORMAT
#SPSF_Default
#SPSF_NoAssignedFormat
#SPSF_Text
#SPSF_NonStandardFormat
#SPSF_ExtendedAudioFormat
;// Standard PCM wave formats
#SPSF_8kHz8BitMono
#SPSF_8kHz8BitStereo
#SPSF_8kHz16BitMono
#SPSF_8kHz16BitStereo
#SPSF_11kHz8BitMono
#SPSF_11kHz8BitStereo
#SPSF_11kHz16BitMono
#SPSF_11kHz16BitStereo
#SPSF_12kHz8BitMono
#SPSF_12kHz8BitStereo
#SPSF_12kHz16BitMono
#SPSF_12kHz16BitStereo
#SPSF_16kHz8BitMono
#SPSF_16kHz8BitStereo
#SPSF_16kHz16BitMono
#SPSF_16kHz16BitStereo
#SPSF_22kHz8BitMono
#SPSF_22kHz8BitStereo
#SPSF_22kHz16BitMono
#SPSF_22kHz16BitStereo
#SPSF_24kHz8BitMono
#SPSF_24kHz8BitStereo
#SPSF_24kHz16BitMono
#SPSF_24kHz16BitStereo
#SPSF_32kHz8BitMono
#SPSF_32kHz8BitStereo
#SPSF_32kHz16BitMono
#SPSF_32kHz16BitStereo
#SPSF_44kHz8BitMono
#SPSF_44kHz8BitStereo
#SPSF_44kHz16BitMono
#SPSF_44kHz16BitStereo
#SPSF_48kHz8BitMono
#SPSF_48kHz8BitStereo
#SPSF_48kHz16BitMono
#SPSF_48kHz16BitStereo
;// TrueSpeech format
#SPSF_TrueSpeech_8kHz1BitMono
;// A-Law formats
#SPSF_CCITT_ALaw_8kHzMono
#SPSF_CCITT_ALaw_8kHzStereo
#SPSF_CCITT_ALaw_11kHzMono
#SPSF_CCITT_ALaw_11kHzStereo
#SPSF_CCITT_ALaw_22kHzMono
#SPSF_CCITT_ALaw_22kHzStereo
#SPSF_CCITT_ALaw_44kHzMono
#SPSF_CCITT_ALaw_44kHzStereo
;// u-Law formats
#SPSF_CCITT_uLaw_8kHzMono
#SPSF_CCITT_uLaw_8kHzStereo
#SPSF_CCITT_uLaw_11kHzMono
#SPSF_CCITT_uLaw_11kHzStereo
#SPSF_CCITT_uLaw_22kHzMono
#SPSF_CCITT_uLaw_22kHzStereo
#SPSF_CCITT_uLaw_44kHzMono
#SPSF_CCITT_uLaw_44kHzStereo
;// ADPCM formats
#SPSF_ADPCM_8kHzMono
#SPSF_ADPCM_8kHzStereo
#SPSF_ADPCM_11kHzMono
#SPSF_ADPCM_11kHzStereo
#SPSF_ADPCM_22kHzMono
#SPSF_ADPCM_22kHzStereo
#SPSF_ADPCM_44kHzMono
#SPSF_ADPCM_44kHzStereo
;// GSM 6.10 formats
#SPSF_GSM610_8kHzMono
#SPSF_GSM610_11kHzMono
#SPSF_GSM610_22kHzMono
#SPSF_GSM610_44kHzMono
#SPSF_NUM_FORMATS
EndEnumeration
Interface ISpeechObjectTokenCategory Extends IDispatch
get_Id(Id.i)
set_Default(TokenId.i)
get_Default(TokenId.i)
SetId(Id.p-bstr, CreateIfNotExist.i)
GetDataKey(SpeechDataKeyLocation.i, ISpeechDataKey.i)
EnumerateTokens(RequiredAttributes.i, OptionalAttributes.i, ISpeechObjectTokens.i)
EndInterface
Interface ISpeechObjectToken Extends IDispatch
get_Id(ObjectId.i)
get_DataKey(ISpeechDataKey.i)
get_Category(ISpeechObjectTokenCategory.i)
GetDescription(Locale.i, Description.i);区域设置.
SetId(Id.i, CategoryID.i, CreateIfNotExist.l)
GetAttribute(AttributeName.p-bstr, AttributeValue.i)
CreateInstance(pUnkOuter.i, SpeechTokenContext.l, Object.i)
Remove(ObjectStorageCLSID.i)
GetStorageFileName(ObjectStorageCLSID.i, KeyName,FileName.i, SpeechTokenShellFolder.l, FilePath.i)
RemoveStorageFileName(ObjectStorageCLSID.i, KeyName.i, DeleteFile.l)
IsUISupported(TypeOfUI.i, ExtraData.variant, Object.i, Supported.i)
DisplayUI(hWnd.i, Title.i, TypeOfUI.i, ExtraData.variant, Object.i)
MatchesAttributes(Attributes.i, Matches.i)
EndInterface
Interface ISpeechObjectTokens Extends IDispatch
get_Count(a.l)
Item(Index.l, Token.ISpeechObjectToken)
get__NewEnum(ppEnumVARIANT.IUnknown)
EndInterface
Interface ISpeechAudioFormat Extends IDispatch
get_Type(SpeechAudioFormatType.i)
set_Type(SpeechAudioFormatType.i)
get_Guid(Guid.i)
set_Guid(Guid.i)
GetWaveFormatEx(ISpeechWaveFormatEx.i)
SetWaveFormatEx(ISpeechWaveFormatEx.i)
EndInterface
Interface ISpeechFileStream Extends IDispatch
get_Format(ISpeechAudioFormat.i)
set_putref_Format(ISpeechAudioFormat.i)
Read(Buffer.i, NumberOfBytes.i, BytesRead.i, BytesWritten.i)
Write(Buffer.i, BytesWritten.i)
Seek(Position.i, SpeechStreamSeekPositionType.i, NewPosition.i)
Open(FileName.p-bstr, SpeechStreamFileMode.i, DoEvents.i)
Close()
EndInterface
Interface ISpeechVoiceStatus Extends IDispatch
get_CurrentStreamNumber(StreamNumber.i)
get_LastStreamNumberQueued(StreamNumber.i)
get_LastHResult(Hresult.i)
get_RunningState(State.i)
get_InputWordPosition(Position.i)
get_InputWordLength(Length.i)
get_InputSentencePosition(Position.i)
get_InputSentenceLength(Length.i)
get_LastBookmark(Bookmark.i)
get_LastBookmarkId(BookmarkId.i)
get_PhonemeId(PhoneId.i)
get_VisemeId(VisemeId.i)
EndInterface
Interface ISpeechVoice Extends IDispatch
get_Status(ISpeechVoiceStatus.i)
get_Voice(ISpeechObjectToken.i)
put_Voice(ISpeechObjectToken.i)
get_AudioOutput(ISpeechObjectToken.i)
put_AudioOutput(ISpeechObjectToken.i)
get_AudioOutputStream(ISpeechBaseStream.i)
put_AudioOutputStream(ISpeechBaseStream.i)
get_Rate(long.i)
put_Rate(long.i)
get_Volume(long.i)
put_Volume(long.i)
put_AllowAudioOutputFormatChangesOnNextSet(VARIANT_BOOL.i)
get_AllowAudioOutputFormatChangesOnNextSet(VARIANT_BOOL.i)
get_EventInterests(SpeechVoiceEvents.i)
put_EventInterests(SpeechVoiceEvents.i)
put_Priority(SpeechVoicePriority.i)
get_Priority(SpeechVoicePriority.i)
put_AlertBoundary(SpeechVoiceEvents.i)
get_AlertBoundary(SpeechVoiceEvents.i)
put_SynchronousSpeakTimeout(long.i)
get_SynchronousSpeakTimeout(long.i)
Speak(Text.p-bstr, SpeechVoiceSpeakFlags.l, IDVoice.i)
SpeakStream(ISpeechBaseStream.i, SpeechVoiceSpeakFlags.l, StreamNumber.i)
Pause()
Resume()
Skip(Type.i, NumItems.l, NumSkipped.i);跳过
GetVoices(RequiredAttributes.i, OptionalAttributes.i, ISpeechObjectTokens.i)
GetAudioOutputs(RequiredAttributes.i, OptionalAttributes.i, ISpeechObjectTokens.i)
WaitUntilDone(msTimeout.l, VARIANT_BOOL.i)
SpeakCompleteEvent(Handle.i)
IsUISupported(TypeOfUI.i, ExtraData.VARIANT, VARIANT_BOOL.i)
DisplayUI(hWndParent.i, Title.i, TypeOfUI.i, ExtraData.VARIANT)
EndInterface
Structure AttributeVoice
Description.s
LangID.s
Item.l
Rate.l
Volume.l
SpeechVoice.i
EndStructure
Structure AttributeAudio
Description.s
Item.l
DeviceId.s
EndStructure
Procedure.i ConvertToUnicode(AsciiOrUnicode.s)
Static *MemoryUnicode
Protected LenMemoryUnicode.l
If *MemoryUnicode <> 0
FreeMemory(*MemoryUnicode)
EndIf
LenMemoryUnicode = StringByteLength(AsciiOrUnicode , #PB_Unicode)
*MemoryUnicode = AllocateMemory(LenMemoryUnicode + 2)
If *MemoryUnicode <> 0
PokeS(*MemoryUnicode, AsciiOrUnicode, Len(AsciiOrUnicode), #PB_Unicode)
ProcedureReturn *MemoryUnicode
EndIf
ProcedureReturn 0
EndProcedure
Procedure.i InterfaceData(Option.l, Donnees.i)
Static NameInterface.l
If Option = 1
NameInterface = Donnees
ProcedureReturn 1
ElseIf Option = -1
ProcedureReturn NameInterface
EndIf
ProcedureReturn 0
EndProcedure
Procedure SetInterfaceData(Option.l, Donnees.i)
ProcedureReturn InterfaceData(Option.l, Donnees.i)
EndProcedure
Procedure.i GetInterfaceData(Option.l)
ProcedureReturn InterfaceData(-1, 0)
EndProcedure
Procedure.l SaveTextToWav(Array VoicesSapi.AttributeVoice(1), VoixID.l, Texte.s, File.s, Format.l)
Protected SpeechFileStream.ISpeechFileStream, SpeechAudioFormat.ISpeechAudioFormat
Protected SpeechFileStreamDefault.i, IDVoice.l, NameInterface$, NameInterface.l, Ret.l=-10
Protected SSFMCreateForWrite = 3
If VoixID > -1 And VoixID <= ArraySize(VoicesSapi())
NameInterface = GetInterfaceData(-1)
If NameInterface = 2 ; Speech 11
NameInterface$ = "SAPI.SpFileStream"
ElseIf NameInterface = 1 ; Sapi 5
NameInterface$ = "Speech.SpFileStream"
Else
Ret = -9
Goto Error2
EndIf
If CLSIDFromProgID_(ConvertToUnicode(NameInterface$), @CLSID.CLSID) < #S_OK
Ret = -8
Goto Error2
EndIf
If CLSIDFromString_(ConvertToUnicode("{AF67F125-AB39-4E93-B4A2-CC2E66E182A7}"), @Refiid.CLSID) < #S_OK
Ret = -7
Goto Error2
EndIf
If CoCreateInstance_(Clsid, #Null, #CLSCTX_INPROC_SERVER, Refiid, @SpeechFileStream.ISpeechFileStream) < #S_OK
Ret = -6
Goto Error2
EndIf
If SpeechFileStream\get_Format(@SpeechAudioFormat.ISpeechAudioFormat) < #S_OK
Ret = -5
Goto Error2
EndIf
If SpeechAudioFormat\set_Type(Format) < #S_OK
Ret = -4
Goto Error2
EndIf
If SpeechFileStream\Open(File, SSFMCreateForWrite, 0) < #S_OK
Ret = -3
Goto Error2
EndIf
SpeechVoice.ISpeechVoice = VoicesSapi(VoixID)\SpeechVoice
If SpeechVoice = 0
Ret = -2
Goto Error2
EndIf
If SpeechVoice\get_AudioOutputStream(@SpeechFileStreamDefault) < #S_OK
Ret = -1
Goto Error2
EndIf
If SpeechVoice\put_AudioOutputStream(SpeechFileStream) < #S_OK
Ret = 0
Goto Error2
EndIf
If SpeechVoice\Speak(Texte, 0, @IDVoice) < #S_OK
Ret = 1
Goto Error2
EndIf
error2:
If SpeechFileStream <> 0
SpeechFileStream\Close()
SpeechFileStream\Release()
EndIf
If SpeechAudioFormat <> 0
SpeechAudioFormat\Release()
EndIf
If SpeechFileStreamDefault <> 0
SpeechVoice\put_AudioOutputStream(SpeechFileStreamDefault)
EndIf
EndIf
;如果成功的函数返回1
ProcedureReturn Ret
EndProcedure
Procedure GetAudioOutput(Array VoicesAudio.AttributeAudio(1), *DefaultAudio.long)
Protected SpeechObjectTokenCategory.ISpeechObjectTokenCategory
Protected SpeechObjectTokens.ISpeechObjectTokens
Protected SpeechObjectToken.ISpeechObjectToken
Protected Clsid.CLSID, Refiid.CLSID, NameInterface$
Protected Description.i, DescriptionAudio.s, ID.s, DefaultID.s
Protected a.l, Ret.l=0
#SpeechCategoryAudioOut = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\AudioOutput"
NameInterface = GetInterfaceData(-1)
If NameInterface = 2 ; Speech 11
NameInterface$ = "Speech.SpObjectTokenCategory"
ElseIf NameInterface = 1 ; Sapi 5
NameInterface$ = "Sapi.SpObjectTokenCategory"
Else
Goto Error3
EndIf
If CLSIDFromProgID_(ConvertToUnicode(NameInterface$), @Clsid.CLSID) < #S_OK
Goto Error3
EndIf
If CLSIDFromString_(ConvertToUnicode("{CA7EAC50-2D01-4145-86D4-5AE7D70F4469}"), @Refiid.CLSID) < #S_OK
Goto Error3
EndIf
If CoCreateInstance_(Clsid, #Null, #CLSCTX_INPROC_SERVER, Refiid, @SpeechObjectTokenCategory.ISpeechObjectTokenCategory) >= #S_OK
If SpeechObjectTokenCategory\SetId(#SpeechCategoryAudioOut, 0) >= #S_OK
If SpeechObjectTokenCategory\get_Default(@Description) >= #S_OK
If Description <> 0
DefaultID.s = PeekS(Description, -1, #PB_Unicode)
;Debug DefaultID
SysFreeString_(Description)
EndIf
EndIf
If SpeechObjectTokenCategory\EnumerateTokens(0, 0, @SpeechObjectTokens.ISpeechObjectTokens) >= #S_OK
If SpeechObjectTokens\get_Count(@Count.l) >= #S_OK
For a = 0 To count -1
If SpeechObjectTokens\Item(a, @SpeechObjectToken.ISpeechObjectToken) >= #S_OK
If SpeechObjectToken\GetDescription(0, @Description) >= #S_OK
If Description <> 0
DescriptionAudio.s = PeekS(Description, -1, #PB_Unicode)
;Debug DescriptionAudio
SysFreeString_(Description)
VoicesAudio(a)\Description = DescriptionAudio
Ret = Ret + 1
EndIf
If SpeechObjectToken\get_Id(@Description) >= #S_OK
If Description <> 0
ID.s = PeekS(Description, -1, #PB_Unicode)
;Debug ID
VoicesAudio(a)\DeviceId = ID
If DefaultID = ID
*DefaultAudio\l = a
EndIf
EndIf
SysFreeString_(Description)
EndIf
VoicesAudio(a)\Item = a
EndIf
SpeechObjectToken\Release()
EndIf
Next a
EndIf
SpeechObjectTokens\Release()
EndIf
EndIf
SpeechObjectTokenCategory\Release()
EndIf
Error3:
ProcedureReturn Ret
EndProcedure
Procedure SetAudioOutput(Array VoicesSapi.AttributeVoice(1), Item.l)
Protected SpeechObjectTokenCategory.ISpeechObjectTokenCategory
Protected SpeechObjectTokens.ISpeechObjectTokens
Protected SpeechObjectToken.ISpeechObjectToken
Protected Clsid.CLSID, Refiid.CLSID, NameInterface$, NameInterface.l
Protected Description.i, DescriptionAudio.s, a.l, Ret.l=0
Protected SpeechVoice.ISpeechVoice
#SpeechCategoryAudioOut = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\AudioOutput"
If Item > -1 And Item <= ArraySize(VoicesSapi())
SpeechVoice = VoicesSapi(0)\SpeechVoice
If SpeechVoice <> 0
NameInterface = GetInterfaceData(-1)
If NameInterface = 1 ; Sapi 5
NameInterface$ = "Speech.SpObjectTokenCategory"
ElseIf NameInterface = 2 ; Speech 11
NameInterface$ = "Sapi.SpObjectTokenCategory"
Else
Goto Error5
EndIf
If CLSIDFromProgID_(ConvertToUnicode(NameInterface$), @Clsid.CLSID) < #S_OK
Goto Error5
EndIf
If CLSIDFromString_(ConvertToUnicode("{CA7EAC50-2D01-4145-86D4-5AE7D70F4469}"), @Refiid.CLSID) < #S_OK
Goto Error5
EndIf
If CoCreateInstance_(Clsid, #Null, #CLSCTX_INPROC_SERVER, Refiid, @SpeechObjectTokenCategory.ISpeechObjectTokenCategory) >= #S_OK
If SpeechObjectTokenCategory\SetId(#SpeechCategoryAudioOut, 0) >= #S_OK
If SpeechObjectTokenCategory\EnumerateTokens(0, 0, @SpeechObjectTokens.ISpeechObjectTokens) >= #S_OK
If SpeechObjectTokens\Item(Item, @SpeechObjectToken.ISpeechObjectToken)>= #S_OK
If SpeechObjectToken <> 0
If SpeechVoice\put_AudioOutput(SpeechObjectToken)>= #S_OK
Ret = 1
EndIf
SpeechObjectToken\Release()
EndIf
EndIf
SpeechObjectTokens\Release()
EndIf
EndIf
SpeechObjectTokenCategory\Release()
EndIf
EndIf
EndIf
Error5:
ProcedureReturn Ret
EndProcedure
Procedure.l FreeSpeakVoice(Array VoicesSapi.AttributeVoice(1))
Protected SpeechVoice.ISpeechVoice, a.l
SpeechVoice = VoicesSapi(0)\SpeechVoice
If SpeechVoice <> 0
SpeechVoice\Pause()
SpeechVoice\Release()
For a = 0 To ArraySize(VoicesSapi())
VoicesSapi(a)\SpeechVoice = 0
Next a
;Debug "Free InterfaceVoice -> OK"
ProcedureReturn #True
EndIf
ProcedureReturn #False
;返回 true 或 false
EndProcedure
Procedure.l InitSpeakVoice(Array VoicesSapi.AttributeVoice(1), NameInterface.l)
Protected ClsidSpVoice.i, RefiidISpeechVoice.i, Clsid.CLSID, Refiid.CLSID, Language.i
Protected SpeechVoice.ISpeechVoice, SpeechObjectTokens.ISpeechObjectTokens
Protected SpeechObjectToken.ISpeechObjectToken, SpeechObjectTokenCategory.ISpeechObjectTokenCategory
Protected LangIDVoice.s, DescriptionVoice.s, Ret.l = -2, Longueur.l, a.l
Protected NameInterface$, Count.l, Lang.i, Description.i
;安全, 为了释放接口 Sapi.SpVoice 如果已经创建
FreeSpeakVoice(VoicesSapi())
Longueur = ArraySize(VoicesSapi())
;
; HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices
If NameInterface = 2 ; Speech 11
NameInterface$ = "Speech.SpVoice" ; HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech Server\v11.0\Voices
SetInterfaceData(1, 2)
ElseIf NameInterface = 1 ; Sapi 5
NameInterface$ = "SAPI.SpVoice" ; HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices
SetInterfaceData(1, 1)
Else
SetInterfaceData(1, 0)
Ret = -1
Goto Error
EndIf
If CLSIDFromProgID_(ConvertToUnicode(NameInterface$), @Clsid.CLSID) < #S_OK
Goto Error
EndIf
If CLSIDFromString_(ConvertToUnicode("{269316D8-57BD-11D2-9EEE-00C04F797396}"), @Refiid.CLSID) < #S_OK
Goto Error
EndIf
If CoCreateInstance_(Clsid, #Null, #CLSCTX_INPROC_SERVER, Refiid, @SpeechVoice) >= #S_OK
Ret = 0
If SpeechVoice\GetVoices(0, 0, @SpeechObjectTokens) >= #S_OK
If SpeechObjectTokens\get_Count(@Count) >= #S_OK
;已安装声音的列表
For a= 0 To Count-1
If SpeechObjectTokens\Item(a, @SpeechObjectToken) >= #S_OK
If SpeechObjectToken\GetDescription(0, @Description) >= #S_OK
If Description <> 0
DescriptionVoice = PeekS(Description, -1, #PB_Unicode)
SysFreeString_(Description)
EndIf
EndIf
If SpeechObjectToken\GetAttribute("Language", @Lang) >= #S_OK
If Lang <> 0
LangIDVoice = PeekS(Lang, -1, #PB_Unicode)
SysFreeString_(Lang)
EndIf
EndIf
If a < (Longueur + 1)
VoicesSapi(a)\Description = DescriptionVoice
VoicesSapi(a)\LangID = LangIDVoice
VoicesSapi(a)\Item = a
VoicesSapi(a)\Rate = 0
VoicesSapi(a)\Volume = 100
VoicesSapi(a)\SpeechVoice = SpeechVoice
Ret = a + 1
EndIf
SpeechObjectToken\Release()
EndIf
Next
EndIf
SpeechObjectTokens\Release()
EndIf
EndIf
Error:
If ClsidSpVoice <> 0
FreeMemory(ClsidSpVoice)
EndIf
If RefiidSpVoice <> 0
FreeMemory(RefiidSpVoice)
EndIf
ProcedureReturn Ret
;引用值 > 0 表示选票数
;如果没有安装声音, 则返回 0 (如果安装运行时没有声音, 则可能)
;返回-1 如果 Sapi 5 或没有安装的语音平台11
;返回-2 如果 InterfaceName 没有所需的值
EndProcedure
Procedure.l putRate(Rate.l,Array VoicesSapi.AttributeVoice(1))
Protected SpeechVoice.ISpeechVoice
SpeechVoice = VoicesSapi(0)\SpeechVoice
If SpeechVoice <> 0
If SpeechVoice\put_Rate(Rate) >= #S_OK
ProcedureReturn #True
EndIf
EndIf
ProcedureReturn #False
EndProcedure
Procedure.l putVolume(Volume.l,Array VoicesSapi.AttributeVoice(1))
Protected SpeechVoice.ISpeechVoice
SpeechVoice = VoicesSapi(0)\SpeechVoice
If SpeechVoice <> 0
If SpeechVoice\put_Volume(Volume) >= #S_OK
ProcedureReturn #True
EndIf
EndIf
ProcedureReturn #False
EndProcedure
Procedure.l PauseSpeak(Array VoicesSapi.AttributeVoice(1))
Protected SpeechVoice.ISpeechVoice
SpeechVoice = VoicesSapi(0)\SpeechVoice
If SpeechVoice <> 0
If SpeechVoice\Pause() >= #S_OK
ProcedureReturn #True
EndIf
EndIf
ProcedureReturn #False
EndProcedure
Procedure.l ResumeSpeak(Array VoicesSapi.AttributeVoice(1))
Protected SpeechVoice.ISpeechVoice
SpeechVoice = VoicesSapi(0)\SpeechVoice
If SpeechVoice <> 0
If SpeechVoice\Resume() >= #S_OK
ProcedureReturn #True
EndIf
EndIf
ProcedureReturn #False
EndProcedure
Procedure.l StatusSpeak(Array VoicesSapi.AttributeVoice(1))
Protected SpeechVoice.ISpeechVoice, State.l=-1
Protected SpeechVoiceStatus.ISpeechVoiceStatus
SpeechVoice = VoicesSapi(0)\SpeechVoice
If SpeechVoice <> 0
If SpeechVoice\get_Status(@SpeechVoiceStatus) >= #S_OK
SpeechVoiceStatus\get_RunningState(@State)
;Debug "RunningState = " + Str(State)
SpeechVoiceStatus\Release()
EndIf
EndIf
ProcedureReturn State;状态
;如果使用 Sapi 返回2
;返回1如果 Sapi ok
;返回0如果说没有启动第一次
;返回-1 如果函数失败
EndProcedure
Procedure.l getSpeakPosition(Array VoicesSapi.AttributeVoice(1))
Protected SpeechVoice.ISpeechVoice, Position.i;=-1
Protected SpeechVoiceStatus.ISpeechVoiceStatus
SpeechVoice = VoicesSapi(0)\SpeechVoice
If SpeechVoice <> 0
If SpeechVoice\get_Status(@SpeechVoiceStatus) >= #S_OK
; SpeechVoiceStatus\get_InputWordPosition(@Position)
; Debug position
SpeechVoiceStatus\get_InputSentencePosition(@Position)
; Debug position
SpeechVoiceStatus\Release()
EndIf
EndIf
ProcedureReturn Position
EndProcedure
Procedure.l CurrentStream(Array VoicesSapi.AttributeVoice(1))
Protected SpeechVoice.ISpeechVoice, Number.l = -1
Protected SpeechVoiceStatus.ISpeechVoiceStatus
SpeechVoice = VoicesSapi(0)\SpeechVoice
If SpeechVoice <> 0
If SpeechVoice\get_Status(@SpeechVoiceStatus) >= #S_OK
SpeechVoiceStatus\get_CurrentStreamNumber(@Number)
SpeechVoiceStatus\Release()
EndIf
EndIf
ProcedureReturn Number;编号
;如果0不是当前流, 则返回当前流的 ID
;返回-1 如果函数失败
EndProcedure
Procedure.l SkipStream(Array VoicesSapi.AttributeVoice(1), Item.l)
Protected SpeechVoice.ISpeechVoice, Sentence.i, ItemSkipped.l
SpeechVoice = VoicesSapi(0)\SpeechVoice
If SpeechVoice <> 0
If SpeechVoice\Skip(ConvertToUnicode("SENTENCE"), Item, @ItemSkipped) >= #S_OK
ProcedureReturn ItemSkipped
EndIf
EndIf
ProcedureReturn #False
;返回跳过的句子数, 可以是正数还是负数
;如果函数失败, 则返回0
EndProcedure
Procedure.l InputSentenceLength(Array VoicesSapi.AttributeVoice(1))
Protected SpeechVoice.ISpeechVoice, Lenght.l = -1
Protected SpeechVoiceStatus.ISpeechVoiceStatus
SpeechVoice = VoicesSapi(0)\SpeechVoice
If SpeechVoice <> 0
If SpeechVoice\get_Status(@SpeechVoiceStatus) >= #S_OK
SpeechVoiceStatus\get_InputSentenceLength(@Lenght)
SpeechVoiceStatus\Release()
EndIf
EndIf
ProcedureReturn Lenght;长度
;返回当前句子的文本长度
;返回-1 如果函数失败
EndProcedure
Procedure.l PlaySpeakAsync(Array VoicesSapi.AttributeVoice(1), VoixID.l, Texte.s, *Stream.long = 0, Rate.l = -20, Volume.l = -1)
Protected SpeechVoice.ISpeechVoice, StreamNumber.l, Ret.l = 0, Ret2.l
Protected SpeechObjectTokens.ISpeechObjectTokens, SpeechObjectToken.ISpeechObjectToken
SpeechVoice = VoicesSapi(0)\SpeechVoice
If SpeechVoice <> 0
If VoixID > -1 And VoixID <= ArraySize(VoicesSapi())
If SpeechVoice\GetVoices(0, 0, @SpeechObjectTokens) >= #S_OK
If SpeechObjectTokens\Item(VoixID, @SpeechObjectToken) >= #S_OK
If SpeechVoice\put_Voice(SpeechObjectToken) >= #S_OK
Ret2 = 1
EndIf
SpeechObjectToken\Release()
EndIf
SpeechObjectTokens\Release()
EndIf
If Volume = -1
SpeechVoice\put_Volume(VoicesSapi(VoixID)\Volume)
Else
If SpeechVoice\put_Volume(Volume) >= #S_OK
VoicesSapi(VoixID)\Volume = Volume
EndIf
EndIf
If Rate = -20
SpeechVoice\put_Rate(VoicesSapi(VoixID)\Rate)
Else
If SpeechVoice\put_Rate(Rate) >= #S_OK
VoicesSapi(VoixID)\Rate = Rate
EndIf
EndIf
EndIf
If SpeechVoice\Speak(Texte, #SVSFlagsAsync, @StreamNumber) >= #S_OK
If *Stream <> 0
*Stream\l = StreamNumber
EndIf
Ret = Ret2 + 1
EndIf
EndIf
ProcedureReturn Ret
;如果函数成功地使用语音指定索引返回2
;如果在未指定语音索引的情况下, 如果指定了声音索引或未找到, 则返回 1 (如果已成功运行)。
;如果函数 SpeechVoice\Speak, 则返回0
EndProcedure
Procedure.l StopSpeak(Array VoicesSapi.AttributeVoice(1))
Protected SpeechVoice.ISpeechVoice, IDVoice.l
SpeechVoice = VoicesSapi(0)\SpeechVoice
If SpeechVoice <> 0
;ResumeSpeak 是必要的, 以防前一个状态被暂停
;因为否则他将不得不做一个 ResumeSpeak 后, 一个新的发言
ResumeSpeak(VoicesSapi())
If SpeechVoice\Speak("", #SVSFPurgeBeforeSpeak, 0) >= #S_OK
;Debug "Stop -> OK"
ProcedureReturn #True
EndIf
EndIf
ProcedureReturn #False
EndProcedure
Procedure EditorGadgetCursorPos(Gadget)
; returns relative Position of Cursor
SendMessage_(GadgetID(Gadget),#EM_EXGETSEL,0,Range.CHARRANGE)
ProcedureReturn Range\cpMax
EndProcedure
;程序创建的开始
Enumeration
#Menu
EndEnumeration
Enumeration
#Window
EndEnumeration
Enumeration
#TimerDetectSpeakfinish
EndEnumeration
Enumeration
#TextPitch
#Pitch
#Voix
#LangID
#Edit
#TextRate
#Rate
#RateArea
#TextVolume
#Volume
#Speak
#Pause
#Resume
#Stop
#SaveToWav
#Sentence
#Sentence1
EndEnumeration
CoInitialize_(0);仅在程序中调用 Coinitialize_ () 一次
#SAPI_5 = 1
#SPEECH_11 = 2
;数组的初始化
;它将在以后调整大小
#LmiteNbVoices = 30
Dim VoicesSapi1.AttributeVoice(#LmiteNbVoices)
; ----------------------------------------------
;用于显示语音的菜单的第一个索引的声明
#DebutIndiceVoix = 10
#DebutIndiceAudio = 100
; ----------------------------------------------
Menu0=#DebutIndiceVoix
Menu1=#DebutIndiceAudio
If OpenWindow(#Window, 200, 200, 400, 520, "SAPI 5 / SPEECH 11 - TTS:text to speech",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
If CreateMenu(#Menu, WindowID(#Window))
MenuTitle("select voice")
TextGadget(#Voix, 10, 5, 280, 20, "voice:")
TextGadget(#LangID, 290, 5, 100, 20, "LangID:")
SetGadgetColor(#Voix, #PB_Gadget_FrontColor, RGB(255,0,0))
SetGadgetColor(#LangID, #PB_Gadget_FrontColor, RGB(255,0,0))
;InitSpeakVoice 初始化接口 Sapi.SpVoice 并更新参数数组中已安装的声音列表
;函数的返回值允许知道我们是否可以得到一个 Sapi.SpVoice 指针和安装声音的数量
;选择, 选择 #SPEECH_11 或 #SAPI_5, 可以测试一个或另一个
;如本例所示
NbVoice = InitSpeakVoice(VoicesSapi1(), #SPEECH_11)
If NbVoice < 0
NbVoice = InitSpeakVoice(VoicesSapi1(), #SAPI_5)
EndIf
If NbVoice > 0
;它调整表的大小与确切的 votes-1 数, 因为从0开始
ReDim VoicesSapi1(NbVoice-1)
;Debug "------------------------------"
;Debug "Nombre de Voix = " + Str(NbVoice)
For a = 0 To NbVoice -1
MenuItem( #DebutIndiceVoix + a, VoicesSapi1(a)\Description)
;Debug "------------------------------"
;Debug "Description = " + VoicesSapi1(a)\Description
;Debug "LangID = " + VoicesSapi1(a)\LangID
;Debug "------------------------------"
If VoicesSapi1(a)\Description="Microsoft Simplified Chinese"
VoiceSapi1 = a:Menu0=#DebutIndiceVoix+a:EndIf
Next
;根据我的测试, 默认的声音是0。
SetMenuItemState(#Menu, Menu0, #True)
SetGadgetText(#LangID, "LangID:"+ VoicesSapi1(VoiceSapi1)\LangID)
SetGadgetText(#Voix,"voice:" + VoicesSapi1(VoiceSapi1)\Description)
ElseIf NbVoice = 0
SetGadgetText(#Voix, "voice : 没有声音安装")
MenuItem( #DebutIndiceVoix, "没有声音安装")
Else
SetGadgetText(#Voix, "voice : 请安装 SAPI 5")
MenuItem( #DebutIndiceVoix, "请安装 SAPI 5")
EndIf
MenuTitle("select device")
Dim SortieAudio.AttributeAudio(10)
CountAudioOutput.l = GetAudioOutput(SortieAudio(), @DefautAudio)
If CountAudioOutput > 0
For a = 0 To CountAudioOutput - 1
MenuItem(#DebutIndiceAudio + a, SortieAudio(a)\Description)
Next a:menu1=#DebutIndiceAudio
SetMenuItemState(#Menu,menu1 , #True)
Else
MenuItem(#DebutIndiceAudio, "错误音频输出")
EndIf
EndIf
Volume = 100
Rate = 0
Pitch=0
TextGadget(#TextRate, 10, 25, 90, 20, "-/+ rate = " + Str(Rate))
;TextGadget(#RateArea, 100, 25, 290, 20, "( -10 To +10 ), 正常速度 = 0")
TrackBarGadget(#Rate, 100, 25, 290, 20, 0, 20)
SetGadgetState(#Rate, Rate + 10)
TextGadget(#TextPitch, 10,45, 90, 20, "-/+ pitch = " + Str(Pitch) )
TrackBarGadget(#Pitch, 100,45, 290, 20, 0, 20)
SetGadgetState(#Pitch, Pitch+10)
TextGadget(#TextVolume, 10, 65, 90, 20, " + volume = " + Str(Volume))
TrackBarGadget(#Volume, 100, 65, 290, 20, 0, 100)
SetGadgetState(#Volume, Volume)
a=520-GetSystemMetrics_(#SM_CYMENU)-30
EditorGadget(#Edit, 10, 90, 380, a-90-5,#PB_Editor_WordWrap)
SetGadgetFont(#Edit, LoadFont(0,"宋体",11,#PB_Font_Bold))
SetGadgetText(#Edit, ~"1 Purebasic 是非常好的! is very nice!\n"+
~"2 通过指定一个文本行应占据的额外空间,可用这个函数对文本进行两端对齐处理\n"+
~"3 额外空间由行内各个分隔字符分摊。这里的“分隔字符”是由特定的字体定义的,\n"+
~"4 函数了解一种字体采用的分隔字符是什么。对文本进行两端对齐排列的时候,通常需要采取的操作步骤如下:\n"+
~"5 用于分散额外空间的分隔字符的数量\n")
ButtonGadget(#Speak, 10, a, 50, 20, "Speak")
ButtonGadget(#Pause, 60, a, 50, 20, "Pause")
ButtonGadget(#Resume, 110, a, 50, 20, "Resume")
ButtonGadget(#Stop, 160, a, 50, 20, "Stop")
ButtonGadget(#Sentence, 215, a, 45, 20,"<<Sent");to prevSentence
ButtonGadget(#Sentence1, 260, a, 45, 20,"ence>>");To nextSentence
ButtonGadget(#SaveToWav, 310, a, 80, 20, "Save to Wav")
DisableGadget(#Resume, 1)
DisableGadget(#Pause, 1)
DisableGadget(#Stop, 1)
If NbVoice < 1
DisableGadget(#Speak, 1)
;DisableGadget(#Pitch, 1)
EndIf
CreatePopupMenu(10)
MenuItem(0, "cut")
MenuItem(1, "copy")
MenuItem(2, "paste")
MenuItem(3, "select all")
MenuBar()
MenuItem(4, "undo")
MenuBar()
MenuItem(5, "speakFromCursor");从这里开始播放
Texte.s = "<pitch middle="+Chr(34)+Str(Pitch)+Chr(34)+"/>" +GetGadgetText(#Edit)
Repeat
Select WaitWindowEvent()
Case #PB_Event_Menu
EventMenu = EventMenu()
Select EventMenu
Case 0:SendMessage_(GadgetID(#Edit), #WM_CUT, 0, 0)
Case 1:SendMessage_(GadgetID(#Edit), #WM_COPY, 0, 0)
Case 2:SendMessage_(GadgetID(#Edit), #WM_PASTE, 0, 0)
Case 3:SendMessage_(GadgetID(#Edit),#EM_SETSEL, 0, -1)
Case 4:SendMessage_(GadgetID(#Edit), #EM_UNDO, 0, 0);#WM_UNDO
Case 5:SetCursorPos_(mouse.POINT\x,mouse\y)
mouse_event_(#MOUSEEVENTF_LEFTDOWN,0,0,0,0)
mouse_event_(#MOUSEEVENTF_LEFTUP,0,0,0,0)
While WindowEvent():Wend
pos=EditorGadgetCursorPos(#Edit):StopSpeak(VoicesSapi1())
SendMessage_(GadgetID(#Edit), #EM_SETSEL, pos, pos+4)
Texte = GetGadgetText(#Edit)
Texte = "<pitch middle="+Chr(34)+Str(Pitch)+Chr(34)+"/>" +Mid(Texte,pos)
Gosub Play
Case #DebutIndiceVoix To (#DebutIndiceVoix + NbVoice-1)
SetMenuItemState(#Menu, Menu0, #False)
SetMenuItemState(#Menu, EventMenu, #True):Menu0=EventMenu
VoiceSapi1 = EventMenu - #DebutIndiceVoix
SetGadgetText(#Voix, "voice:" + VoicesSapi1(VoiceSapi1)\Description)
SetGadgetText(#LangID, "LangID:" + VoicesSapi1(VoiceSapi1)\LangID)
Case #DebutIndiceAudio To (#DebutIndiceAudio + CountAudioOutput-1)
SetMenuItemState(#Menu, Menu1, #False)
SetMenuItemState(#Menu, EventMenu, #True):Menu1=EventMenu
;Debug "/////////////////////////"
;Debug EventMenu - #DebutIndiceAudio
SetAudioOutput(VoicesSapi1(), EventMenu - #DebutIndiceAudio)
EndSelect
Case #PB_Event_Timer
Select EventTimer()
Case #TimerDetectSpeakfinish
;暂停, StatusSpeak 是 0, 实际上没有状态
;目前是2
;准备好了1
;Debug InputSentenceLength(VoicesSapi1())
If StatusSpeak(VoicesSapi1()) = 1
RemoveWindowTimer(#Window, #TimerDetectSpeakfinish)
DisableGadget(#Speak, 0)
DisableGadget(#Pitch, 0)
DisableGadget(#TextPitch,0)
DisableGadget(#Resume, 1)
DisableGadget(#Pause, 1)
DisableGadget(#Stop, 1)
EndIf
EndSelect
Case #PB_Event_Gadget
Select EventGadget()
Case #Pause:RemoveWindowTimer(#Window, #TimerDetectSpeakfinish)
DisableGadget(#Resume, 0)
DisableGadget(#Pause, 1)
;DisableGadget(#Speak, 1)
PauseSpeak(VoicesSapi1())
Case #Resume
DisableGadget(#Resume, 1)
DisableGadget(#Pause, 0)
ResumeSpeak(VoicesSapi1())
AddWindowTimer(#Window, #TimerDetectSpeakfinish, 500)
Case #Stop:RemoveWindowTimer(#Window, #TimerDetectSpeakfinish)
DisableGadget(#Speak, 0)
DisableGadget(#Pitch, 0)
DisableGadget(#TextPitch,0)
DisableGadget(#Resume, 1)
DisableGadget(#Pause, 1)
DisableGadget(#Stop, 1)
StopSpeak(VoicesSapi1())
Case #Volume
Volume = GetGadgetState(#Volume)
SetGadgetText(#TextVolume, " + volume = " + Str(Volume))
putVolume(Volume,VoicesSapi1())
Case #Rate
Rate = GetGadgetState(#Rate) - 10
SetGadgetText(#TextRate, "-/+ rate = " + Str(Rate))
putRate(Rate,VoicesSapi1())
Case #Pitch
Pitch = GetGadgetState(#Pitch) - 10
SetGadgetText(#TextPitch, "-/+ pitch = " + Str(Pitch)):editcg=0
Texte.s = "<pitch middle="+Chr(34)+Str(Pitch)+Chr(34)+"/>" +GetGadgetText(#Edit)
Case #Speak
Gosub Play
Case #edit:et=EventType():If et=#PB_EventType_Change:editcg=1
ElseIf et=#PB_EventType_LostFocus And editcg=1:editcg=0
Texte.s = "<pitch middle="+Chr(34)+Str(Pitch)+Chr(34)+"/>" +GetGadgetText(#Edit)
EndIf
Case #SaveToWav
Texte.s = GetGadgetText(#Edit)
File$ = SaveFileRequester("保存为...", "c:\", "*.wav", 0)
If File$ <> ""
If LCase(Right(File$, 3)) <> "wav"
File$ = File$ + ".wav"
EndIf
;Debug File$
If SaveTextToWav(VoicesSapi1(), VoiceSapi1, Texte, File$, #SPSF_22kHz16BitMono)
MessageRequester("Info", "文本到 Wav 成功!")
EndIf
EndIf
Case #Sentence,#Sentence1
If EventGadget()=#Sentence:SkipStream(VoicesSapi1(),-1)
Else:SkipStream(VoicesSapi1(),1):EndIf
pos=getSpeakPosition(VoicesSapi1())
SendMessage_(GadgetID(#Edit), #EM_SETSEL, pos, pos+4)
EndSelect
Case #WM_RBUTTONUP;DOWN
w.q=WindowMouseX(0)+WindowMouseY(0)<<32:GetCursorPos_(mouse.POINT)
If ChildWindowFromPoint_(WindowID(0),w)=GadgetID(#edit)
DisplayPopupMenu(10,WindowID(0))
EndIf
Case #PB_Event_CloseWindow
Quit = 1
EndSelect
Until Quit = 1
EndIf
;离开前释放资源
;没有效果, 如果已经发布
FreeSpeakVoice(VoicesSapi1())
CoUninitialize_();仅在程序中调用一次
End
play: :If PlaySpeakAsync(VoicesSapi1(), VoiceSapi1, Texte, @Stream, Rate, Volume) > 0
AddWindowTimer(#Window, #TimerDetectSpeakfinish, 500)
DisableGadget(#Speak, 1)
DisableGadget(#Pitch, 1)
DisableGadget(#TextPitch,1)
DisableGadget(#Pause, 0)
DisableGadget(#Stop, 0)
Else
MessageRequester("信息", "说话功能失败的选择的声音!")
EndIf
Return