MS Speech SAPI Text to speech COM (Windows only)

Share your advanced PureBasic knowledge/code with the community.
Justin
Addict
Addict
Posts: 829
Joined: Sat Apr 26, 2003 2:49 pm

MS Speech SAPI Text to speech COM (Windows only)

Post by Justin »

Text to speech using Speech SDK 5.1.
You will need to install the sdk if you don't have it:
https://www.microsoft.com/en-us/downloa ... x?id=10121

The first two files are just the converted headers(not complete):
sapi.pbi
sapihelper.pbi

The code is in tts.pb and tts_async.pb

Code: Select all

;- sapi.pbi

#SPCAT_VOICES    = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices"

;- enum SPCFGRULEATTRIBUTES
#SPRAF_TopLevel	= 1 << 0
#SPRAF_Active	= 1 << 1
#SPRAF_Export	= 1 << 2
#SPRAF_Import	= 1 << 3
#SPRAF_Interpreter	= 1 << 4
#SPRAF_Dynamic	= 1 << 5
#SPRAF_AutoPause	= 1 << 16

;- enum SPGRAMMARWORDTYPE
#SPWT_DISPLAY	= 0
#SPWT_LEXICAL	= #SPWT_DISPLAY + 1
#SPWT_PRONUNCIATION	= #SPWT_LEXICAL + 1

;- enum SPEVENTENUM
#SPEI_UNDEFINED	= 0
#SPEI_START_INPUT_STREAM	= 1
#SPEI_END_INPUT_STREAM	= 2
#SPEI_VOICE_CHANGE	= 3
#SPEI_TTS_BOOKMARK	= 4
#SPEI_WORD_BOUNDARY	= 5
#SPEI_PHONEME	= 6
#SPEI_SENTENCE_BOUNDARY	= 7
#SPEI_VISEME	= 8
#SPEI_TTS_AUDIO_LEVEL	= 9
#SPEI_TTS_PRIVATE	= 15
#SPEI_MIN_TTS	= 1
#SPEI_MAX_TTS	= 15
#SPEI_END_SR_STREAM	= 34
#SPEI_SOUND_START	= 35
#SPEI_SOUND_END	= 36
#SPEI_PHRASE_START	= 37
#SPEI_RECOGNITION	= 38
#SPEI_HYPOTHESIS	= 39
#SPEI_SR_BOOKMARK	= 40
#SPEI_PROPERTY_NUM_CHANGE	= 41
#SPEI_PROPERTY_STRING_CHANGE	= 42
#SPEI_FALSE_RECOGNITION	= 43
#SPEI_INTERFERENCE	= 44
#SPEI_REQUEST_UI	= 45
#SPEI_RECO_STATE_CHANGE	= 46
#SPEI_ADAPTATION	= 47
#SPEI_START_SR_STREAM	= 48
#SPEI_RECO_OTHER_CONTEXT	= 49
#SPEI_SR_AUDIO_LEVEL	= 50
#SPEI_SR_PRIVATE	= 52
#SPEI_MIN_SR	= 34
#SPEI_MAX_SR	= 52
#SPEI_RESERVED1	= 30
#SPEI_RESERVED2	= 33
#SPEI_RESERVED3	= 63

#SPFEI_FLAGCHECK = ((1 << #SPEI_RESERVED1) | (1 << #SPEI_RESERVED2) )
#SPFEI_ALL_TTS_EVENTS = ($000000000000FFFE | #SPFEI_FLAGCHECK) 
#SPFEI_ALL_SR_EVENTS = ($001FFFFC00000000 | #SPFEI_FLAGCHECK)
#SPFEI_ALL_EVENTS = $EFFFFFFFFFFFFFFF
Macro SPFEI(SPEI_ord) : ((1 << SPEI_ord) | #SPFEI_FLAGCHECK) : EndMacro 

;- enum SPRULESTATE
#SPRS_INACTIVE	= 0
#SPRS_ACTIVE	= 1
#SPRS_ACTIVE_WITH_AUTO_PAUSE	= 3

;enum SPRUNSTATE
#SPRS_DONE = 1 << 0
#SPRS_IS_SPEAKING = 1 << 1

;- enum SPPHRASERNG
#SPPR_ALL_ELEMENTS	= -1

#SP_GETWHOLEPHRASE = #SPPR_ALL_ELEMENTS
#SPRR_ALL_ELEMENTS = #SPPR_ALL_ELEMENTS

;- enum SPGRAMMARSTATE
#SPGS_ENABLED = 0
#SPGS_DISABLED = 1
#SPGS_EXCLUSIVE = 3

;- enum SPLOADOPTIONS
#SPLO_STATIC	= 0
#SPLO_DYNAMIC	= 1

;- enum SPEAKFLAGS
#SPF_DEFAULT	= 0
#SPF_ASYNC	= 1 << 0
#SPF_PURGEBEFORESPEAK	= 1 << 1
#SPF_IS_FILENAME	= 1 << 2
#SPF_IS_XML	= 1 << 3
#SPF_IS_NOT_XML	= 1 << 4
#SPF_PERSIST_XML	= 1 << 5
#SPF_NLP_SPEAK_PUNC	= 1 << 6
#SPF_NLP_MASK	= #SPF_NLP_SPEAK_PUNC
#SPF_VOICE_MASK	= #SPF_ASYNC | #SPF_PURGEBEFORESPEAK | #SPF_IS_FILENAME | #SPF_IS_XML | #SPF_IS_NOT_XML | #SPF_NLP_MASK | #SPF_PERSIST_XML
#SPF_UNUSED_FLAGS	= ~#SPF_VOICE_MASK

;- enum SPEVENTLPARAMTYPE
#SPET_LPARAM_IS_UNDEFINED	= 0
#SPET_LPARAM_IS_TOKEN	= #SPET_LPARAM_IS_UNDEFINED + 1
#SPET_LPARAM_IS_OBJECT	= #SPET_LPARAM_IS_TOKEN + 1
#SPET_LPARAM_IS_POINTER	= #SPET_LPARAM_IS_OBJECT + 1
#SPET_LPARAM_IS_STRING	= #SPET_LPARAM_IS_POINTER + 1

;- SPEVENT
Structure SPEVENT Align #PB_Structure_AlignC
	eEventId.w
	elParamType.w
	ulStreamNum.l
	ullAudioStreamOffset.q
	wParam.i
	lParam.i
EndStructure

;- SPVOICESTATUS
Structure SPVOICESTATUS Align #PB_Structure_AlignC
	ulCurrentStream.l
	ulLastStreamQueued.l
	hrLastResult.l
	dwRunningState.l
	ulInputWordPos.l
	ulInputWordLen.l
	ulInputSentPos.l
	ulInputSentLen.l
	lBookmarkId.l
	PhonemeId.a
	VisemeId.l
	dwReserved1.l
	dwReserved2.l
EndStructure

;- ISpProperties
Interface ISpProperties Extends IUnknown
	SetPropertyNum(name.s, value.l)
	GetPropertyNum(name.s, value.l)
	SetPropertyString(name.s, value.s)
	GetPropertyString(name.s, value.i)
EndInterface

;- ISpRecognizer
Interface ISpRecognizer Extends ISpProperties
	SetRecognizer(recognizer.i)
	GetRecognizer(recognizer.i)
	SetInput(input.i, AllowFormatChanges.l)
	GetInputObjectToken(token.i)
	GetInputStream(stream.i)
	CreateRecoContext(NewCtxt.i)
	GetRecoProfile(token.i)
	SetRecoProfile(token.i)
	IsSharedInstance()
	GetRecoState(state.i)
	SetRecoState(state.i)
	GetStatus(status.i)
	GetFormat(WaveFormatType.i, FormatId.i, CoMemWFEX.i)
	IsUISupported(TypeOfUI.s, ExtraData.i, ExtraData.l, Supported.l)
	DisplayUI(hwndParent.i, Title.s, TypeOfUI.s, ExtraData.i, ExtraData.l)
	EmulateRecognition(Phrase.i)
EndInterface

;- ISpNotifySource
Interface ISpNotifySource Extends IUnknown
	SetNotifySink(NotifySink.i)
	SetNotifyWindowMessage(hWnd.i, mdsg.l, wparam.i, lparam.i)
	SetNotifyCallbackFunction(Callback.i, wparam.i, lparam.i)
	SetNotifyCallbackInterface(SpCallback.i, wparam.i, lparam.i)
	SetNotifyWin32Event()
	WaitForNotifyEvent(Milliseconds.l)
	GetNotifyEventHandle()
EndInterface

;- ISpEventSource
Interface ISpEventSource Extends ISpNotifySource
	SetInterest(EventInterest.q, QueuedInterest.q)
	GetEvents(Count.l, EventArray.i, Fetched.i)
	GetInfo(info.i)
EndInterface

;- ISpRecoContext
Interface ISpRecoContext Extends ISpEventSource
	GetRecognizer(Recognizer.i)
	CreateGrammar(GrammarId.q, Grammar.i)
	GetStatus(Status.i)
	GetMaxAlternates(Alternates.i)
	SetMaxAlternates(Alternates.l)
	SetAudioOptions(options.l, AudioFormatId.i, WaveFormatEx.i)
	GetAudioOptions(Options.i, AudioFormatId.i, CoMemWFEX.i)
	DeserializeResult(SerializedResult.i, Result.i)
	Bookmark(Options.l, StreamPosition.q, lparamEvent.i)
	SetAdaptationData(AdaptationData.s, cch.l)
	Pause(Reserved.l)
	Resume(Reserved.l)
	SetVoice(pVoice.i, fAllowFormatChanges.l)
	GetVoice(ppVoice.i)
	SetVoicePurgeEvent(ullEventInterest.q)
	GetVoicePurgeEvent(pullEventInterest.i)
	SetContextState(eContextState.l)
	GetContextState(peContextState.i)
EndInterface

;- ISpGrammarBuilder
Interface ISpGrammarBuilder Extends IUnknown
	ResetGrammar(NewLanguage.w)
	GetRule(pszRuleName.s, dwRuleId.l, dwAttributes.l, fCreateIfNotExist.l, phInitialState.i)
	ClearRule(hState.i)
	CreateNewState(hState.i, phState.i)
	AddWordTransition(hFromState.i, hToState.i, psz.s, pszSeparators.s, eWordType.l, Weight.f, pPropInfo.i)
	AddRuleTransition(hFromState.i, hToState.i, hRule.i, Weight.f, pPropInfo.i)
	AddResource(hRuleState.i, pszResourceName.s, pszResourceValue.s)
	Commit(dwReserved.l)
EndInterface

;- ISpRecoGrammar
Interface ISpRecoGrammar Extends ISpGrammarBuilder
	GetGrammarId(pullGrammarId.i)
	GetRecoContext(ppRecoCtxt.i)
	LoadCmdFromFile(pszFileName, Options.l)
	LoadCmdFromObject(rcid.i, pszGrammarName.s, Options.l)
	LoadCmdFromResource(hModule.i, pszResourceName.s, pszResourceType.s, wLanguage.w, Options.l)
	LoadCmdFromMemory(pGrammar.i, Options.l)
	LoadCmdFromProprietaryGrammar(rguidParam.i, pszStringParam.s, pvDataPrarm.i, cbDataSize.l, Options.l)
	SetRuleState(pszName.s, pReserved.i, NewState.l)
	SetRuleIdState(ulRuleId.l, NewState.l)
	LoadDictation(pszTopicName.s, Options.l)
	UnloadDictation()
	SetDictationState(NewState.l)
	SetWordSequenceData(pText.s, cchText.l, pInfo.i)
	SetTextSelection(pInfo.i)
	IsPronounceable(pszWord.s, pWordPronounceable.i)
	SetGrammarState(eGrammarState.l)
	SaveCmd(pStream.i, ppszCoMemErrorText.i)
	GetGrammarState(peGrammarState.i)
EndInterface

;- ISpPhrase
Interface ISpPhrase Extends IUnknown
	GetPhrase(ppCoMemPhrase.i)
	GetSerializedPhrase(ppCoMemPhrase.i)
	GetText(ulStart.l, ulCount.l, fUseTextReplacements.l, ppszCoMemText.i, pbDisplayAttributes.i)
	Discard(dwValueTypes.l)
EndInterface

;- ISpRecoResult
Interface ISpRecoResult Extends ISpPhrase
	GetResultTimes(pTimes.i)
	GetAlternates(ulStartElement.l, cElements.l, ulRequestCount.l, ppPhrases.i, pcPhrasesReturned.i)
	GetAudio(ulStartElement.l, cElements.l, ppStream.i)
	SpeakAudio(ulStartElement.l, cElements.l, dwFlags.l, pulStreamNumber.i)
	Serialize(ppCoMemSerializedResult.i)
	ScaleAudio(pAudioFormatId.i, pWaveFormatEx.i)
	GetRecoContext(ppRecoContext.i)
EndInterface

;- enum SPVPRIORITY
#SPVPRI_NORMAL	= 0
#SPVPRI_ALERT	= 1 << 0
#SPVPRI_OVER	= 1 << 1

#CLSID_SpVoice$ = "{96749377-3391-11D2-9EE3-00C04F797396}"
#IID_ISpVoice$ = "{6C44DF74-72B9-4992-A1EC-EF996E0422D4}"

;- ISpVoice
Interface ISpVoice Extends ISpEventSource
	SetOutput(pUnkOutput.i, fAllowFormatChanges.l)
	GetOutputObjectToken(ppObjectToken.i)
	GetOutputStream(ppStream.i)
	Pause()
	Resume()
	SetVoice(pToken.i)
	GetVoice(ppToken.i)
	Speak(pwcs.s, dwFlags.l, pulStreamNumber.i)
	SpeakStream(pStream.i, dwFlags.l, pulStreamNumber.i)
	GetStatus(pStatus.i, ppszLastBookmark.i)
	Skip(pItemType.s, lNumItems.l, pulNumSkipped.i)
	SetPriority(ePriority.l)
	GetPriority(pePriority.i)
	SetAlertBoundary(eBoundary.l)
	GetAlertBoundary(peBoundary.i)
	SetRate(RateAdjust.l)
	GetRate(pRateAdjust.i)
	SetVolume(usVolume.w)
	GetVolume(pusVolume.i)
	WaitUntilDone(msTimeout.l)
	SetSyncSpeakTimeout(msTimeout.l)
	GetSyncSpeakTimeout(pmsTimeout.i)
	SpeakCompleteEvent()
	IsUISupported(pszTypeOfUI.s, pvExtraData.i, cbExtraData.l, pfSupported.i)
	DisplayUI(hwndParent.i, pszTitle.s, pszTypeOfUI.s, pvExtraData.i, cbExtraData.l)                                                   
EndInterface

;- IEnumSpObjectTokens
Interface IEnumSpObjectTokens Extends IUnknown
 	Next(celt.l, pelt.i, pceltFetched.i)
  Skip(celt.l)
  Reset()
  Clone(ppEnum.i)
  Item(Index.l, ppToken.i)
  GetCount(pCount.i)           
EndInterface

;- ISpDataKey
Interface ISpDataKey Extends IUnknown
	SetData(pszValueName.s, cbData.l, pData.i)
	GetData(pszValueName.s, pcbData.i, pData.i)
	SetStringValue(pszValueName.s, pszValue.s)
	GetStringValue(pszValueName.s, ppszValue.i)
	SetDWORD(pszValueName.s, dwValue.l)
	GetDWORD(pszValueName.s, pdwValue.i)
	OpenKey(pszSubKeyName.s, ppSubKey.i)
	CreateKey(pszSubKey.s, ppSubKey.i)
	DeleteKey(pszSubKey.s)
	DeleteValue(pszValueName.s)
	EnumKeys(Index.l, ppszSubKeyName.i)
	EnumValues(Index.l, ppszValueName.i)                                 
EndInterface

;- enum SPDATAKEYLOCATION
#SPDKL_DefaultLocation	= 0
#SPDKL_CurrentUser	= 1
#SPDKL_LocalMachine	= 2
#SPDKL_CurrentConfig	= 5

#CLSID_SpObjectTokenCategory$ = "{A910187F-0C7A-45AC-92CC-59EDAFB77B53}"
#IID_ISpObjectTokenCategory$ = "{2D3D3845-39AF-4850-BBF9-40B49780011D}"

;- ISpObjectTokenCategory
Interface ISpObjectTokenCategory Extends ISpDataKey
	SetId(pszCategoryId.s, fCreateIfNotExist.l)
	GetId(ppszCoMemCategoryId.i)
	GetDataKey(spdkl.l, ppDataKey.i)
	EnumTokens(pzsReqAttribs.s, pszOptAttribs.s, ppEnum.i)
	SetDefaultTokenId(pszTokenId.s)
	GetDefaultTokenId(ppszCoMemTokenId.i)          
EndInterface

;- ISpObjectToken
Interface ISpObjectToken Extends ISpDataKey
	SetId(pszCategoryId.s, pszTokenId.s, fCreateIfNotExist.l)
	GetId(ppszCoMemTokenId.i)
	GetCategory(ppTokenCategory.i)
	CreateInstance(pUnkOuter.i, dwClsContext.l, riid.i, ppvObject.i)
	GetStorageFileName(clsidCaller.i, pszValueName.s, pszFileNameSpecifier.s, nFolder.l, ppszFilePath.i)
	RemoveStorageFileName(clsidCaller.i, pszKeyName.s, fDeleteFile.l)
	Remove(pclsidCaller.i)
	IsUISupported(pszTypeOfUI.s, pvExtraData.i, cbExtraData.l, punkObject.i, pfSupported.i)
	DisplayUI(hwndParent.i, pszTitle.s, pszTypeOfUI.s, pvExtraData.i, cbExtraData.l, punkObject.i)
	MatchesAttributes(pszAttributes.s, pfMatches.i)
EndInterface

Code: Select all

;sapihelper.pbi

XIncludeFile "sapi.pbi"

EnableExplicit
	
Declare SpEnumTokens(pszCategoryId.s, pszReqAttribs.s, pszOptAttribs.s, ppEnum.i)
Declare SpGetCategoryFromId(pszCategoryId.s, ppCategory.ISpObjectTokenCategory, fCreateIfNotExist.l = #False)

Procedure SpEnumTokens(pszCategoryId.s, pszReqAttribs.s, pszOptAttribs.s, *ppEnum.INTEGER)
	Define.l hr
	Define.ISpObjectTokenCategory tokenCategory

  hr = SpGetCategoryFromId(pszCategoryId, @tokenCategory)
  If hr = #S_OK
		hr = tokenCategory\EnumTokens(pszReqAttribs, pszOptAttribs, *ppEnum)
	EndIf
	
	ProcedureReturn hr
EndProcedure

Procedure SpGetCategoryFromId(pszCategoryId.s, *ppCategory.INTEGER, fCreateIfNotExist.l = #False)
	Define.IID CLSID_SpObjectTokenCategory
	Define.IID IID_ISpObjectTokenCategory
	Define.ISpObjectTokenCategory tokenCategory
	Define.l hr
	Define.i test
	
	IIDFromString_(#CLSID_SpObjectTokenCategory$, @CLSID_SpObjectTokenCategory)
	IIDFromString_(#IID_ISpObjectTokenCategory$, @IID_ISpObjectTokenCategory)

	hr = CoCreateInstance_(@CLSID_SpObjectTokenCategory, #Null, #CLSCTX_INPROC_SERVER, @IID_ISpObjectTokenCategory, @tokenCategory)
	If hr = #S_OK
		hr = tokenCategory\SetId(pszCategoryId, fCreateIfNotExist)
		If hr = #S_OK
			*ppCategory\i = tokenCategory
		
		Else
			tokenCategory\Release()
		EndIf 
	EndIf 
	
	ProcedureReturn hr
EndProcedure

Procedure SpClearEvent(*pe.SPEVENT)
	Protected.IUnknown pUnk
	
	If *pe\elParamType <> #SPEI_UNDEFINED
		If *pe\elParamType = #SPET_LPARAM_IS_POINTER Or *pe\elParamType = #SPET_LPARAM_IS_STRING
			CoTaskMemFree_(*pe\lParam)
		
		ElseIf *pe\elParamType = #SPET_LPARAM_IS_TOKEN Or *pe\elParamType = #SPET_LPARAM_IS_OBJECT
			pUnk = *pe\lParam
			pUnk\Release()
		EndIf 
	EndIf 
	
	FillMemory(*pe, 0, SizeOf(SPEVENT))
EndProcedure

Code: Select all

;tts.pb

XIncludeFile "sapi.pbi"
XIncludeFile "sapihelper.pbi"

EnableExplicit

Define.IID CLSID_SpVoice
Define.IID IID_ISpVoice
Define.ISpVoice pVoice
Define.IEnumSpObjectTokens cpEnum
Define.ISpObjectToken voiceToken
Define.l vc, hr
Define.i voiceId
Define.s txt

CoInitialize_(0)

IIDFromString_(#CLSID_SpVoice$, @CLSID_SpVoice)
IIDFromString_(#IID_ISpVoice$, @IID_ISpVoice)

hr = CoCreateInstance_(@CLSID_SpVoice, #Null, #CLSCTX_INPROC_SERVER, @IID_ISpVoice, @pVoice)
If hr <> #S_OK
	Debug "Failed to create voice opbject"
	End
EndIf

txt = "hello, this is the default voice"
pVoice\Speak(txt, 0, #Null)
pVoice\Release()

If SpEnumTokens(#SPCAT_VOICES, "", "", @cpEnum) = #S_OK	
	Debug "Available voices:"
	While cpEnum\Next(1, @voiceToken, #Null) = #S_OK
		If voiceToken\GetId(@voiceId) = #S_OK
			Debug PeekS(voiceId)
		EndIf 
		
		voiceToken\Release()
	Wend 
	cpEnum\Release()
	
Else
	Debug "Failed to enumerate voices"
EndIf 

Code: Select all

;tts_async.pb

XIncludeFile "sapi.pbi"
XIncludeFile "sapihelper.pbi"

EnableExplicit

;- _APP
Structure _APP
	win.i
	edText.i
	btnSpeak.i
	btnPause.i
	btnResume.i
	btnStop.i
	cbVoices.i
	txtStatus.i
	txtVoice.i
	pVoice.ISpVoice
	isVoiceRunning.b
	isVoicePaused.b
EndStructure
Global app._APP

Procedure updateStatusText()
	If app\isVoiceRunning = #False
		SetGadgetText(app\txtStatus, "Stopped")
		
	ElseIf app\isVoicePaused
		SetGadgetText(app\txtStatus, "Paused")
		
	Else
		SetGadgetText(app\txtStatus, "Running")
	EndIf 
EndProcedure

Procedure wnd_proc(hwnd.i, msg.l, wparam.i, lparam.i)
	Select msg
		Case #WM_USER
			Protected.SPEVENT eventItem
			
			While app\pVoice\GetEvents(1, @eventItem, #Null) = #S_OK
				Select eventItem\eEventId 
					Case #SPEI_START_INPUT_STREAM
						app\isVoiceRunning = #True
						updateStatusText()
						Break
						
					Case #SPEI_END_INPUT_STREAM
						app\isVoiceRunning = #False
						If app\isVoicePaused
							app\pVoice\Resume()
							app\isVoicePaused = #False
						EndIf 
						updateStatusText()
						Break
				EndSelect
			Wend 
			
			SpClearEvent(@eventItem)
	EndSelect
	
	ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

Procedure main()
	Protected.IID CLSID_SpVoice
	Protected.IID IID_ISpVoice
	Protected.ISpVoice pVoice
	Protected.IEnumSpObjectTokens cpEnum
	Protected.ISpObjectToken voiceToken
	Protected.l hr
	Protected.i voiceId
	Protected.i ev, ww, wh, btnHeight, btnWidth
	Protected.l quit, cbHeight, iVoice
	Protected.LOGFONT lf
	
	CoInitialize_(0)
	
	IIDFromString_(#CLSID_SpVoice$, @CLSID_SpVoice)
	IIDFromString_(#IID_ISpVoice$, @IID_ISpVoice)
	
	hr = CoCreateInstance_(@CLSID_SpVoice, #Null, #CLSCTX_INPROC_SERVER, @IID_ISpVoice, @app\pVoice)
	If hr <> #S_OK
		Debug "Failed to create voice opbject"
		End
	EndIf
	
	app\isVoiceRunning = #False
	app\isVoicePaused = #False
	
	ww = 600
	wh = 400
	btnHeight = 40
	btnWidth = 100
	app\win = OpenWindow(#PB_Any, 10, 10, ww, wh, "SAPI", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_Invisible)
	;Set events
	SetWindowCallback(@wnd_proc())
	app\pVoice\SetInterest(#SPFEI_ALL_EVENTS, #SPFEI_ALL_EVENTS)
	app\pVoice\SetNotifyWindowMessage(WindowID(app\win), #WM_USER, 0, 0 )

	app\cbVoices = ComboBoxGadget(#PB_Any, 0, 0, 0, 0)
	app\txtVoice = TextGadget(#PB_Any, 0, 0, 0, 0, " Set voice:  ")
	
	cbHeight = GadgetHeight(app\cbVoices, #PB_Gadget_RequiredSize)
	ResizeGadget(app\txtVoice, 0, 0, GadgetWidth(app\txtVoice, #PB_Gadget_RequiredSize), cbHeight)

	ResizeGadget(app\cbVoices, GadgetWidth(app\txtVoice), 0, ww - GadgetWidth(app\txtVoice), cbHeight)
	app\edText = EditorGadget(#PB_Any, 0, cbHeight, ww, wh - btnHeight - cbHeight, #PB_Editor_WordWrap)
	app\btnSpeak = ButtonGadget(#PB_Any, 0, wh - btnHeight, btnWidth, btnHeight, "Speak")
	app\btnPause = ButtonGadget(#PB_Any, btnWidth + 4, wh - btnHeight, btnWidth, btnHeight, "Pause")
	app\btnResume = ButtonGadget(#PB_Any, (btnWidth + 4) * 2, wh - btnHeight, btnWidth, btnHeight, "Resume")
	app\btnStop = ButtonGadget(#PB_Any, (btnWidth + 4) * 3, wh - btnHeight, btnWidth, btnHeight, "Stop")
	app\txtStatus = TextGadget(#PB_Any, (btnWidth + 4) * 4, wh - btnHeight, WindowWidth(app\win) - (GadgetX(app\btnStop) + btnWidth + 4), btnHeight, "", #PB_Text_Border | #PB_Text_Center)
	SetGadgetText(app\edText, "Entet text here")
	
	;Get voices
	If SpEnumTokens(#SPCAT_VOICES, "", "", @cpEnum) = #S_OK	
		iVoice = 0
		While cpEnum\Next(1, @voiceToken, #Null) = #S_OK
			If voiceToken\GetId(@voiceId) = #S_OK
				AddGadgetItem(app\cbVoices, iVoice, GetFilePart(PeekS(voiceId)))
				SetGadgetItemData(app\cbVoices, iVoice, voiceToken)
			EndIf 
		
			iVoice + 1
		Wend 
		cpEnum\Release()
		
	Else
		Debug "Failed to enumerate voices"
		End 
	EndIf 
	SetGadgetState(app\cbVoices, 0)

	updateStatusText()
	
	HideWindow(app\win, #False)
	
	quit = #False 
	Repeat
		ev = WaitWindowEvent()
		Select ev
			Case #PB_Event_Gadget
				Select EventGadget()
					Case app\btnSpeak
						If app\isVoiceRunning = #False
							app\pVoice\Speak(GetGadgetText(app\edText), #SPF_IS_XML | #SPF_ASYNC, #Null)
							
						ElseIf app\isVoicePaused = #True
							app\pVoice\Resume()
							app\isVoicePaused = #False
							updateStatusText()
						EndIf 
						
					Case app\btnPause
						If app\isVoiceRunning = #True And app\isVoicePaused = #False
							app\pVoice\Pause()
							app\isVoicePaused = #True
							updateStatusText()
						EndIf 
						
					Case app\btnResume
						If app\isVoiceRunning = #True And app\isVoicePaused = #True
							app\pVoice\Resume()
							app\isVoicePaused = #False
							updateStatusText()
						EndIf 
						
					Case app\btnStop
						If app\isVoiceRunning
							app\pVoice\Speak("", #SPF_PURGEBEFORESPEAK, #Null)
							;Alt method to stop
; 						app\pVoice\Skip("Sentence", Len(GetGadgetText(app\edText)), #Null)
						EndIf 
						
					Case app\cbVoices
						If EventType() =  #PB_EventType_Change   
							voiceToken = GetGadgetItemData(app\cbVoices, GetGadgetState(app\cbVoices))
							If voiceToken
								app\pVoice\SetVoice(voiceToken)
							EndIf 
						EndIf 
				EndSelect
							
			Case #PB_Event_CloseWindow
				quit = #True
		EndSelect
	Until quit
	
	;Release voices
	For iVoice = 0 To CountGadgetItems(app\cbVoices) - 1
		voiceToken = GetGadgetItemData(app\cbVoices, iVoice)
		If voiceToken
			voiceToken\Release()
		EndIf 
	Next 
	
	If app\pVoice : app\pVoice\Release() : EndIf 
EndProcedure

main()
Last edited by Justin on Mon May 08, 2023 12:00 am, edited 2 times in total.
User avatar
Rings
Moderator
Moderator
Posts: 1427
Joined: Sat Apr 26, 2003 1:11 am

Re: MS Speech SAPI Text to speech COM

Post by Rings »

good one,
works also under PB x64 windows;)
SPAMINATOR NR.1
Justin
Addict
Addict
Posts: 829
Joined: Sat Apr 26, 2003 2:49 pm

Re: MS Speech SAPI Text to speech COM (Windows only)

Post by Justin »

Yes the code is 32/64 bit compatible i forgot it.
Little John
Addict
Addict
Posts: 4519
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: MS Speech SAPI Text to speech COM (Windows only)

Post by Little John »

On Windows 10, the Speech SDK seems to be preinstalled. Your code worked right away here.
Very cool. 8) Many thanks for sharing!
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: MS Speech SAPI Text to speech COM (Windows only)

Post by Kwai chang caine »

On Windows 10, the Speech SDK seems to be preinstalled. Your code worked right away here.
Same thing for me :D
Works perfectly without installing something, and directly in the good voice on W10 X64 :shock:

In fact, when i run the first time, your english text is speaking like i speak english....very bad :mrgreen:
Then i have replace your text by a long french sentence, and my PC talk to me with a very nice and sexy woman voice 8)

Image

I believe, i more talk to my machine now....than with my wife, it's more sure for not fight :lol:

Thanks a lot for sharing this code 8)
ImageThe happiness is a road...
Not a destination
BarryG
Addict
Addict
Posts: 3292
Joined: Thu Apr 18, 2019 8:17 am

Re: MS Speech SAPI Text to speech COM (Windows only)

Post by BarryG »

Works great on Win 10; thanks!

But a question: how do we use make our app continue after the speaking starts? I tried putting #SPF_ASYNC everywhere as a flag, with no luck.

And lastly: can we abort a voice (stop it speaking) when we want, say after 5 seconds of a 60-second speech starting?
Last edited by BarryG on Sat Mar 25, 2023 10:24 am, edited 1 time in total.
User avatar
Caronte3D
Addict
Addict
Posts: 1025
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: MS Speech SAPI Text to speech COM (Windows only)

Post by Caronte3D »

BarryG wrote: Sat Mar 25, 2023 2:25 am ...But a question: are we supposed to free/release the "hr" variable after use? It won't cause any issues f we don't?
...Also, how do we use make our app continue after the speaking starts?
...And lastly: can we abort a voice (stop it speaking) when we want...
Hi BarryG, I don't know about the code in this thread, but with the one I mentioned here:
viewtopic.php?p=598191#p598191
Do you can stop the voice when you want and you can let the speak running while the program continues.
I used it on a BINGO game and everyting works perfect.
BarryG
Addict
Addict
Posts: 3292
Joined: Thu Apr 18, 2019 8:17 am

Re: MS Speech SAPI Text to speech COM (Windows only)

Post by BarryG »

Hi Caronte3D, I worked out the async issue (can't use "pVoice\Release()" after it, lol). I'll try to work out the stopping now before I look at your version.
User avatar
Caronte3D
Addict
Addict
Posts: 1025
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: MS Speech SAPI Text to speech COM (Windows only)

Post by Caronte3D »

BarryG wrote: Sat Mar 25, 2023 10:25 am Hi Caronte3D, I worked out the async issue (can't use "pVoice\Release()" after it, lol). I'll try to work out the stopping now before I look at your version.
This is the procedure in the code that I am using (in case it can help you):

Code: Select all

Procedure.l StopSpeak(Array VoicesSapi.AttributeVoice(1))
  Protected SpeechVoice.ISpeechVoice, IDVoice.l
  
  SpeechVoice = VoicesSapi(0)\SpeechVoice
  If SpeechVoice <> 0
    ; Le ResumeSpeak est nécessaire au cas où l'état précédent est en Pause
    ; car sinon il faudrait faire un ResumeSpeak après un nouveau Speak
    ResumeSpeak(VoicesSapi())
    If SpeechVoice\Speak("", #SVSFPurgeBeforeSpeak, 0) >= #S_OK
      ;Debug "Stop -> OK"
      ProcedureReturn #True
    EndIf
  EndIf
  
  ProcedureReturn #False
  ; Renvoi vrai ou faux
EndProcedure
Justin
Addict
Addict
Posts: 829
Joined: Sat Apr 26, 2003 2:49 pm

Re: MS Speech SAPI Text to speech COM (Windows only)

Post by Justin »

Hi, to properly handle Stop / Pause / Resume you have to use async mode, setup events and use some flags. Pause() and Resume() need to be balanced.

I updated the headers and removed the modules in favor of plain includes.

See the tts_async.pb example.
BarryG
Addict
Addict
Posts: 3292
Joined: Thu Apr 18, 2019 8:17 am

Re: MS Speech SAPI Text to speech COM (Windows only)

Post by BarryG »

Thanks for the new example! I'll give it a go.
Justin
Addict
Addict
Posts: 829
Joined: Sat Apr 26, 2003 2:49 pm

Re: MS Speech SAPI Text to speech COM (Windows only)

Post by Justin »

tts_async.pb example updated with voice selection.
jassing
Addict
Addict
Posts: 1745
Joined: Wed Feb 17, 2010 12:00 am

Re: MS Speech SAPI Text to speech COM (Windows only)

Post by jassing »

Have you had any luck using Cepstral voices?
The code does 'see' the voice, but it doesn't speak it.

Code: Select all

EnableExplicit

XIncludeFile "sapihelper.pbi"

Procedure main()
  Protected.IEnumSpObjectTokens cpEnum
  Protected.ISpObjectToken      voiceToken
  Protected.ISpVoice            pVoice
  Protected.IID                 CLSID_SpVoice, IID_ISpVoice
  Protected NewMap              voicetokens.ISpObjectToken()
  Protected.i                   voiceId
  Protected.s                   name
  
  CoInitialize_(0)
  IIDFromString_( #CLSID_SpVoice$, @CLSID_SpVoice )
  IIDFromString_( #IID_ISpVoice$,  @IID_ISpVoice  )
  
  If CoCreateInstance_( @CLSID_SpVoice, #Null, #CLSCTX_INPROC_SERVER, @IID_ISpVoice, @pVoice ) <> #S_OK
    Debug "Failed to create voice opbject"
  Else  
    pVoice\SetInterest( #SPFEI_ALL_EVENTS, #SPFEI_ALL_EVENTS     )
    pVoice\Speak( "Default Voice", #SPF_IS_XML|#SPF_ASYNC, #Null )
    
    ;pVoice\GetVoice( @voiceToken )
    ;voicetokens( "___Default" ) = voiceToken
    
    ;Get voices
    If SpEnumTokens( #SPCAT_VOICES, "", "", @cpEnum ) <> #S_OK	
      Debug "Failed to enumerate voices"
    Else
      While cpEnum\Next( 1, @voiceToken, #Null ) = #S_OK
        If voiceToken\GetId( @voiceId )          = #S_OK
          voicetokens( GetFilePart(PeekS(voiceId)) ) = voiceToken
        EndIf 
      Wend 
      cpEnum\Release()	
            
      ForEach voicetokens()
        If Left(MapKey( voicetokens() ),3)="TTS"
          name = StringField( MapKey( voicetokens() ), 4, "_" )
        ElseIf Left(MapKey( voicetokens() ),8)="Cepstral"
          name = StringField(MapKey( voicetokens() ),2,"_")
        Else
          name = MapKey( voicetokens() )
        EndIf 
        Debug name +#TAB$+" 0x" + Hex( voiceTokens() )
        pvoice\setvoice( voicetokens() )
        pvoice\speak( "My name is "+name, 0, #NUL )
      Next 
      Delay(2000) 
      ForEach voicetokens()
        voicetokens()\release()
      Next 
    EndIf 
  EndIf 
  If pVoice : pVoice\Release() : EndIf 
EndProcedure

main()
The voice shows up as an enumerated voice with a voice token, but setting it doesn't yield a voice.
it's supposed to be sapi5 compatible.
Justin
Addict
Addict
Posts: 829
Joined: Sat Apr 26, 2003 2:49 pm

Re: MS Speech SAPI Text to speech COM (Windows only)

Post by Justin »

Those voices are 32 bit only, run the example with the 32 bit version of Purebasic on your 64 bit machine and it should work. You will need to compile a 32 bit exe if you want to distribute your program.
Justin
Addict
Addict
Posts: 829
Joined: Sat Apr 26, 2003 2:49 pm

Re: MS Speech SAPI Text to speech COM (Windows only)

Post by Justin »

It's weird after running the example in PB32 bit now the voice also works in PB64.
Post Reply