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()