Weird, I ran the 64bit installer.Justin wrote: Wed May 10, 2023 9:19 pm 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.
MS Speech SAPI Text to speech COM (Windows only)
Re: MS Speech SAPI Text to speech COM (Windows only)
Re: MS Speech SAPI Text to speech COM (Windows only)
I downloaed a demo voice from here
https://www.cepstral.com/en/personal/download
And there isn't a 32/64 bit option and it's installed in the x86 Programs folder Anyways is working now.
https://www.cepstral.com/en/personal/download
And there isn't a 32/64 bit option and it's installed in the x86 Programs folder Anyways is working now.
Re: MS Speech SAPI Text to speech COM (Windows only)
Open the exe with something like 7z, inside it, there are two MSI's, one 32 and one 64Justin wrote: Thu May 11, 2023 9:02 am I downloaed a demo voice from here
https://www.cepstral.com/en/personal/download
And there isn't a 32/64 bit option and it's installed in the x86 Programs folder Anyways is working now.

-
- User
- Posts: 21
- Joined: Fri Jan 17, 2020 8:20 pm
Re: MS Speech SAPI Text to speech COM (Windows only)
Hello everybody!
The voice I'm using doesn't appear in my screen reader:
scansoft Raquel brazilian portuguese 22khz
I know it's called that because that's what's on my screen reader.
It doesn't appear in the list, and I don't know how to select it in the code either.
32 and 64 bit. It's nothing...
Any idea?
Thanks in advance!
The voice I'm using doesn't appear in my screen reader:
scansoft Raquel brazilian portuguese 22khz
I know it's called that because that's what's on my screen reader.
It doesn't appear in the list, and I don't know how to select it in the code either.
32 and 64 bit. It's nothing...
Any idea?
Thanks in advance!
When our generation/OS updates, we either update ourselves, or we are removed.
But we are never fully uninstalled.
But we are never fully uninstalled.
Re: MS Speech SAPI Text to speech COM (Windows only)
Because it's a nextup voice, not one from MS?
-
- User
- Posts: 21
- Joined: Fri Jan 17, 2020 8:20 pm
Re: MS Speech SAPI Text to speech COM (Windows only)
It is a common sapi 5 voice. Recognized by programs like balabouca and the PureTts library (available only for PureBasic v4.00), among other programs and screen readers.
https://superusuarios.com/download/voz- ... nstalavel/
Only Portuguese from Brazil.
https://superusuarios.com/download/voz- ... nstalavel/
Only Portuguese from Brazil.
When our generation/OS updates, we either update ourselves, or we are removed.
But we are never fully uninstalled.
But we are never fully uninstalled.
Re: MS Speech SAPI Text to speech COM (Windows only)
Really great work!
I have simplified things further so you don't have to bother about anything but to supply the text to be read out. Just one pbi needed in which everything is included.
These are the only functions you need to know to do all the work:
All functions (but the last two of course) return 1 if successful and usually 0 otherwise. Some return -1 or -2 on errors to indicate details (for example -1 on sapi_Init means that sapi is already initialized, -2 means that the voices couldn't be enumerated but the module was initialized anyway).
An error code of -1 always means that sapi_Init wasn't called at the beginning (exception: sapi_Init).
Text is read out asynchronously (sapi_Say returns immediately after having commenced the readout). sapi_IsVoiceRunning() can be used to determine whether a text has been read out completely.
Please note that subsequent calls to sapi_Say will automatically stop reading out a previously started text that hasn't finished yet and start over with the new text.
The first piece of code should be named "sapi-all.pbi". The second one is an example of how to use it.
I have simplified things further so you don't have to bother about anything but to supply the text to be read out. Just one pbi needed in which everything is included.
These are the only functions you need to know to do all the work:
Code: Select all
sapi_Init(hMainWnd.i,UsermessageNumber.i=#WM_USER) ;call once at the beginning, supply ID (MS-Windows handle) of main program window; optional: set user message number that is used internally by the pbi if #WM_USER is already in use by your program
sapi_DeInit() ;call once at the end
sapi_Say(Text$)
sapi_Pause()
sapi_Resume()
sapi_Stop()
sapi_GetVoiceNames() ;returns the names of all installed voices separated by "|"
sapi_SetVoice(VoiceName$) ;VoiceName$=one of the names returned by sapi_GetVoiceNames()
sapi_SetSpeed(Speed.b=0) ;-10 to +10; default=0
sapi_SetVolume(Volume.b=100) ;0 to 100; default=100
sapi_IsVoiceRunning() ;returns 1 if text is played (even when paused), 0 otherwise
sapi_IsVoicePaused() ;returns 1 if readout is paused, 0 otherwise
sapi_GetLastBookmark() ;returns the name of the last bookmark reached; e.g. »<bookmark mark="bookmark_one"/>« in the text
sapi_GetCurrentWordPos(TakeChr13IntoAccount.i=0) ;returns the position of the word currently read out
sapi_GetCurrentWordLen() ;returns the length of the word currently read out
An error code of -1 always means that sapi_Init wasn't called at the beginning (exception: sapi_Init).
Text is read out asynchronously (sapi_Say returns immediately after having commenced the readout). sapi_IsVoiceRunning() can be used to determine whether a text has been read out completely.
Please note that subsequent calls to sapi_Say will automatically stop reading out a previously started text that hasn't finished yet and start over with the new text.
The first piece of code should be named "sapi-all.pbi". The second one is an example of how to use it.
Code: Select all
;sapi-all.pbi
;see here: https://www.purebasic.fr/english/viewtopic.php?t=71402
;{ ;Version history
;}
;V1.00: first distribution version [12.01.2025]
;V1.01: voice procedures added [12.01.2025]
;V1.02: own usermessage number added [12.01.2025]
;V1.03: sapi_SetSpeed & sapi_SetVolume added [12.01.2025]
;V1.04: sapi_IsVoiceRunning & sapi_IsVoicePaused added [13.01.2025]
;V1.05: errorhandling for sapi_SetVoice implemented [18.01.2025]
;V1.06: UserMessages for speech start & stop implemented [18.01.2025]
;V1.07: switched to alternative voice source [18.01.2025]
;V1.08: Windows XP voices added (Sam, Mike, Mary) [18.01.2025]
;V1.09: processing of bookmarks added [19.01.2025]
;V1.10: #SPEI_WORD_BOUNDARY event added [19.01.2025]
;{ ;sapi.pbi
#SPCAT_VOICES1 = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices"
#SPCAT_VOICES2 = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCore\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
;}
;{ ;sapihelper.pbi
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
;}
declare sapi_Stop()
global _sapi_cbVoices.i,_sapi_iVoice.i,_sapi_cpEnum.IEnumSpObjectTokens,_sapi_voiceToken.ISpObjectToken
global _sapi_CLSID_SpVoice.IID,_sapi_IID_ISpVoice.IID,_sapi_pVoice.ISpVoice,_sapi_hr.l,_sapi_voiceId.i
global _sapi_isVoiceRunning.b,_sapi_isVoicePaused.b,_sapi_BookmarkReached$
global _sapi_addr_DefSubclassProc.i,_sapi_addr_SetWindowSubclass.i,_sapi_addr_RemoveWindowSubclass.i,_Comctl32.i
global _sapi_initialized.b=0,_sapi_hMainWnd.i,_sapi_InternalUserMess.i,_sapi_UserMessStart.i,_sapi_UserMessStop.i,_sapi_UserMessBookmarkReached.i,_sapi_UserMessWordBoundary.i
global _sapi_CurrentWordLen.l,_sapi_CurrentWordPos.l,_sapi_CurrentText$
Procedure sapi_SubclassProc(hWnd.i,uMsg.i,wParam.i,lParam.i,uIdSubclass.i,dwRefData.i)
protected erg,ret
protected eventItem.SPEVENT
erg=999 ;if kept =999 the original window procedure will be called at the end, otherwise not
if uMsg=_sapi_InternalUserMess
While _sapi_pVoice\GetEvents(1, @eventItem, #Null) = #S_OK
Select eventItem\eEventId
Case #SPEI_START_INPUT_STREAM
_sapi_isVoiceRunning = #True
_sapi_BookmarkReached$=""
if _sapi_UserMessStart<>0
sendmessage_(_sapi_hMainWnd,_sapi_UserMessStart,0,0)
endif
Break
Case #SPEI_END_INPUT_STREAM
_sapi_isVoiceRunning = #False
_sapi_BookmarkReached$=""
If _sapi_isVoicePaused
_sapi_pVoice\Resume()
_sapi_isVoicePaused = #False
EndIf
if _sapi_UserMessStop<>0
sendmessage_(_sapi_hMainWnd,_sapi_UserMessStop,0,0)
endif
Break
Case #SPEI_TTS_BOOKMARK ;e.g. »<bookmark mark="bookmark_one"/>« in the text
if eventItem\elParamType=#SPET_LPARAM_IS_STRING
_sapi_BookmarkReached$=PeekS(eventItem\lParam) ;PeekS(PeekI(lParam),-1)
else
_sapi_BookmarkReached$=str(PeekL(eventItem\wParam))
endif
if _sapi_UserMessBookmarkReached<>0
sendmessage_(_sapi_hMainWnd,_sapi_UserMessBookmarkReached,0,0)
endif
Break
Case #SPEI_WORD_BOUNDARY
_sapi_CurrentWordLen=eventItem\wParam
_sapi_CurrentWordPos=eventItem\lParam
if _sapi_UserMessWordBoundary<>0
sendmessage_(_sapi_hMainWnd,_sapi_UserMessWordBoundary,_sapi_CurrentWordPos,_sapi_CurrentWordLen)
endif
EndSelect
Wend
SpClearEvent(@eventItem)
endif
;{ ;return value, call original window procedure if appropriate
If erg=999 ;call original window procedure
ProcedureReturn CallFunctionFast(_sapi_addr_DefSubclassProc,hWnd,uMsg,wParam,lParam)
Else ;return special value and *do not* call original window procedure
ProcedureReturn erg
EndIf
;}
EndProcedure ;sapi_SubClassProc
Procedure sapi_Init(hMainWnd.i,UserMessStart.i=0,UserMessStop.i=0,UserMessBookmarkReached.i=0,UserMessWordBoundary.i=0,InternalUserMess.i=#WM_USER) ;call once at the beginning, supply ID (MS-Windows handle) of main program window
protected ret.l,err.l
ret=1
err=0
if _sapi_initialized=0
_sapi_hMainWnd=hMainWnd
_sapi_InternalUserMess=InternalUserMess
_sapi_UserMessStart=UserMessStart
_sapi_UserMessStop=UserMessStop
_sapi_UserMessBookmarkReached=UserMessBookmarkReached
_sapi_UserMessWordBoundary=UserMessWordBoundary
_sapi_isVoiceRunning = #False
_sapi_isVoicePaused = #False
_sapi_BookmarkReached$=""
;{ ;COM-Init
CoInitialize_(0)
IIDFromString_(#CLSID_SpVoice$, @_sapi_CLSID_SpVoice)
IIDFromString_(#IID_ISpVoice$, @_sapi_IID_ISpVoice)
_sapi_hr = CoCreateInstance_(@_sapi_CLSID_SpVoice, #Null, #CLSCTX_INPROC_SERVER, @_sapi_IID_ISpVoice, @_sapi_pVoice)
If _sapi_hr <> #S_OK
;Debug "Failed to create voice object"
;End
ret=0
Else
_Comctl32=OpenLibrary(#PB_Any,"Comctl32.dll") ;neccessary because the subclassing functions aren't included in PB with an underscore at their end
_sapi_addr_DefSubclassProc=GetFunction(_Comctl32,"DefSubclassProc")
_sapi_addr_SetWindowSubclass=GetFunction(_Comctl32,"SetWindowSubclass")
_sapi_addr_RemoveWindowSubclass=GetFunction(_Comctl32,"RemoveWindowSubclass")
_sapi_cbVoices=ComboBoxGadget(#PB_Any,-10,0,0,0)
;{ ;get voices
If SpEnumTokens(#SPCAT_VOICES2, "", "", @_sapi_cpEnum) = #S_OK ;VOICES2 first, because normally they contain more entries than VOICES 1
_sapi_iVoice = 0
While _sapi_cpEnum\Next(1, @_sapi_voiceToken, #Null) = #S_OK
If _sapi_voiceToken\GetId(@_sapi_voiceId) = #S_OK
AddGadgetItem(_sapi_cbVoices, _sapi_iVoice, GetFilePart(PeekS(_sapi_voiceId)))
SetGadgetItemData(_sapi_cbVoices, _sapi_iVoice, _sapi_voiceToken)
;messagebox_(0,str(_sapi_iVoice)+" "+str(_sapi_voiceToken)+" "+PeekS(_sapi_voiceId),"",0)
EndIf
_sapi_iVoice + 1
Wend
_sapi_cpEnum\Release()
SetGadgetState(_sapi_cbVoices, 0)
Else
;Debug "Failed to enumerate voices"
;End
;err=err+1
EndIf
if CountGadgetItems(_sapi_cbVoices)=0
;err=err+1
If SpEnumTokens(#SPCAT_VOICES1, "", "", @_sapi_cpEnum) = #S_OK
_sapi_iVoice = 0
While _sapi_cpEnum\Next(1, @_sapi_voiceToken, #Null) = #S_OK
If _sapi_voiceToken\GetId(@_sapi_voiceId) = #S_OK
AddGadgetItem(_sapi_cbVoices, _sapi_iVoice, GetFilePart(PeekS(_sapi_voiceId)))
SetGadgetItemData(_sapi_cbVoices, _sapi_iVoice, _sapi_voiceToken)
EndIf
_sapi_iVoice + 1
Wend
_sapi_cpEnum\Release()
SetGadgetState(_sapi_cbVoices, 0)
Else
;Debug "Failed to enumerate voices"
;End
;err=err+1
EndIf
if CountGadgetItems(_sapi_cbVoices)=0
ret=-2
endif
else ;add Windows XP voices if available (MSMike, MSSam, MSMary) --> see here (2nd reply!): https://answers.microsoft.com/en-us/windows/forum/all/how-to-get-microsoft-sam-on-windows-7/20b2beb4-e927-4be5-90d3-45c3af1bf2ce
If SpEnumTokens(#SPCAT_VOICES1, "", "", @_sapi_cpEnum) = #S_OK
_sapi_iVoice = 0
While _sapi_cpEnum\Next(1, @_sapi_voiceToken, #Null) = #S_OK
If _sapi_voiceToken\GetId(@_sapi_voiceId) = #S_OK
if (ucase(GetFilePart(PeekS(_sapi_voiceId)))="MSMIKE") or (ucase(GetFilePart(PeekS(_sapi_voiceId)))="MSSAM") or (ucase(GetFilePart(PeekS(_sapi_voiceId)))="MSMARY")
AddGadgetItem(_sapi_cbVoices, _sapi_iVoice, GetFilePart(PeekS(_sapi_voiceId)))
SetGadgetItemData(_sapi_cbVoices, _sapi_iVoice, _sapi_voiceToken)
endif
EndIf
_sapi_iVoice + 1
Wend
_sapi_cpEnum\Release()
SetGadgetState(_sapi_cbVoices, 0)
Else
;Debug "Failed to enumerate voices"
;End
;err=err+1
EndIf
endif
;}
EndIf
;}
else
ret=-1 ;already initialized
endif
if (ret=1) or (ret=-2)
CallFunctionFast(_sapi_addr_SetWindowSubclass,hMainWnd,@sapi_SubclassProc(),1,0)
_sapi_pVoice\SetInterest(#SPFEI_ALL_EVENTS, #SPFEI_ALL_EVENTS)
_sapi_pVoice\SetNotifyWindowMessage(hMainWnd, _sapi_InternalUsermess, 0, 0 )
_sapi_initialized=1
endif
ProcedureReturn ret
EndProcedure ;sapi_Init
Procedure sapi_DeInit() ;call once at the end
protected iVoice.l,erg.l
erg=1
if _sapi_initialized
if _sapi_isVoiceRunning
sapi_Stop()
endif
;{ ;release voices
For iVoice = 0 To CountGadgetItems(_sapi_cbVoices) - 1
_sapi_voiceToken = GetGadgetItemData(_sapi_cbVoices, iVoice)
If _sapi_voiceToken
_sapi_voiceToken\Release()
EndIf
Next
If _sapi_pVoice : _sapi_pVoice\Release() : EndIf
;}
CallFunctionFast(_sapi_addr_RemoveWindowSubclass,_sapi_hMainWnd,@sapi_SubclassProc(),1)
CloseLibrary(_Comctl32)
if _sapi_cbVoices
FreeGadget(_sapi_cbVoices)
_sapi_cbVoices=0
endif
_sapi_initialized=0
else
erg=-1
endif
ProcedureReturn erg
EndProcedure ;sapi_DeInit
Procedure sapi_Say(Text$)
protected erg.i
erg=0
if _sapi_initialized
_sapi_CurrentText$=Text$
_sapi_CurrentWordPos=0
_sapi_CurrentWordLen=0
If _sapi_isVoiceRunning = #False
erg=_sapi_pVoice\Speak(Text$, #SPF_IS_XML | #SPF_ASYNC, #Null)
if erg=0
erg=1
else
erg=0
endif
_sapi_BookmarkReached$=""
_sapi_isVoiceRunning=#True
Else ;If _sapi_isVoicePaused = #True
;{ ;original logic (resume)
;erg=_sapi_pVoice\Resume()
;_sapi_isVoicePaused = #False
;}
sapi_Stop()
erg=_sapi_pVoice\Speak(Text$, #SPF_IS_XML | #SPF_ASYNC, #Null)
if erg=0
erg=1
else
erg=0
endif
_sapi_BookmarkReached$=""
_sapi_isVoiceRunning=#True
_sapi_isVoicePaused=#False
EndIf
else
erg=-1
endif
ProcedureReturn erg
EndProcedure ;sapi_Say
Procedure sapi_Pause()
protected erg.i
erg=0
if _sapi_initialized
If (_sapi_isVoiceRunning = #True) And (_sapi_isVoicePaused = #False)
erg=_sapi_pVoice\Pause()
if erg=0
erg=1
else
erg=0
endif
_sapi_isVoicePaused = #True
EndIf
else
erg=-1
endif
ProcedureReturn erg
EndProcedure ;sapi_Pause
Procedure sapi_Resume()
protected erg.i
erg=0
if _sapi_initialized
If (_sapi_isVoiceRunning = #True) And (_sapi_isVoicePaused = #True)
erg=_sapi_pVoice\Resume()
if erg=0
erg=1
else
erg=0
endif
_sapi_isVoicePaused = #False
EndIf
else
erg=-1
endif
ProcedureReturn erg
EndProcedure ;sapi_Resume
Procedure sapi_Stop()
protected erg.i
protected eventItem.SPEVENT
erg=0
if _sapi_initialized
If _sapi_isVoiceRunning
if _sapi_isVoicePaused
sapi_Resume()
endif
erg=_sapi_pVoice\Speak("", #SPF_PURGEBEFORESPEAK, #Null)
;erg=_sapi_pVoice\Skip("Sentence", Len(_CurrentText$), #Null)
if erg=0
erg=1
else
erg=0
endif
;Alt method to stop
;app\pVoice\Skip("Sentence", Len(GetGadgetText(app\edText)), #Null)
_sapi_isVoiceRunning=#False
_sapi_isVoicePaused=#False
_sapi_BookmarkReached$=""
_sapi_CurrentWordPos=0
_sapi_CurrentWordLen=0
while _sapi_pVoice\GetEvents(1, @eventItem, #Null)=#S_OK ;purge all events that would otherwise be stuck in the loop until the next readout commences
wend
EndIf
else
erg=-1
endif
ProcedureReturn erg
EndProcedure ;sapi_Stop
Procedure.s sapi_GetVoiceNames() ;returns the names of all installed voices separated by "|"
protected erg$,i.l
erg$=""
if _sapi_initialized
for i=0 to CountGadgetItems(_sapi_cbVoices)-1
if erg$<>""
erg$=erg$+"|"
endif
erg$=erg$+GetGadgetItemText(_sapi_cbVoices,i)
next i
else
erg$="-1"
endif
ProcedureReturn erg$
EndProcedure ;sapi_GetVoiceNames
Procedure sapi_SetVoice(VoiceName$) ;VoiceName$=one of the names returned by sapi_GetVoiceNames()
protected erg.i,voiceToken.ISpObjectToken
erg=0
if _sapi_initialized
if VoiceName$<>""
SetGadgetText(_sapi_cbVoices,VoiceName$)
voiceToken = GetGadgetItemData(_sapi_cbVoices, GetGadgetState(_sapi_cbVoices))
If voiceToken
_sapi_pVoice\SetVoice(voiceToken)
erg=1
EndIf
endif
else
erg=-1
endif
ProcedureReturn erg
EndProcedure ;sapi_SetVoice
Procedure sapi_SetSpeed(Speed.b=0) ;-10 to +10; default=0
protected erg.i
erg=0
if _sapi_initialized
if (Speed>=-10) and (Speed<=10)
erg=_sapi_pVoice\SetRate(Speed)
erg=1-erg
endif
else
erg=-1
endif
ProcedureReturn erg
EndProcedure ;sapi_SetSpeed
Procedure sapi_SetVolume(Volume.b=100) ;0 to 100; default=100
protected erg.i
erg=0
if _sapi_initialized
if (Volume>=0) and (Volume<=100)
erg=_sapi_pVoice\SetVolume(Volume)
erg=1-erg
endif
else
erg=-1
endif
ProcedureReturn erg
EndProcedure ;sapi_SetVolume
Procedure sapi_IsVoiceRunning() ;returns 1 if text is being read out (even if paused), 0 otherwise
protected erg.i
erg=0
if _sapi_initialized
erg=_sapi_isVoiceRunning
else
erg=-1
endif
ProcedureReturn erg
EndProcedure ;sapi_IsVoiceRunning
Procedure sapi_IsVoicePaused() ;returns 1 if readout is paused, 0 otherwise
protected erg.i
erg=0
if _sapi_initialized
erg=_sapi_isVoicePaused
else
erg=-1
endif
ProcedureReturn erg
EndProcedure ;sapi_IsVoicePaused
Procedure.s sapi_GetLastBookmark() ;e.g. »<bookmark mark="bookmark_one"/>« in the text
protected erg$
erg$=""
if _sapi_initialized
erg$=_sapi_BookmarkReached$
else
erg$="-1"
endif
ProcedureReturn erg$
EndProcedure ;sapi_GetLastBookmark
Procedure sapi_GetCurrentWordPos(TakeChr13IntoAccount.i=0)
protected s$
if TakeChr13IntoAccount
s$=left(_sapi_CurrentText$,_sapi_CurrentWordPos)
ProcedureReturn _sapi_CurrentWordPos-CountString(s$,chr(13))
else
ProcedureReturn _sapi_CurrentWordPos
endif
EndProcedure ;sapi_GetCurrentWordPos
Procedure sapi_GetCurrentWordLen()
ProcedureReturn _sapi_CurrentWordLen
EndProcedure ;sapi_GetCurrentWordLen
Code: Select all
;sapi-all test
;see here: https://www.purebasic.fr/english/viewtopic.php?t=71402
EnableExplicit
XIncludeFile "sapi-all.pbi"
global _nWnd,_hWnd,_event,_progend.b,_gadgetevent,_nBend,_menuitem,_nBsay,_nETextToSay,_nBpause,_nBresume,_nBstop,_nBSpeedUp,_nBSpeedDown,_nBVolumeUp,_nBVolumeDown,_nCBHighlightWords
global _Speed.b,_Volume.b,_nTstatus,_nCBvoices,_s$,_i,_OldPlaying.b,_OldPaused.b,_OldBookmarkReached$,_nThunder.i,_OldCurrentWordPos.l,_Sel1.l,_Sel2.l
_nWnd=OpenWindow(#PB_Any,100,100,600,430,"sapi-Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
_hWnd=WindowID(_nWnd)
InitSound()
if FileSize("thunder1.wav")>0
_nThunder=LoadSound(#PB_Any,"thunder1.wav")
endif
sapi_Init(_hWnd) ;call once at the beginning, supply ID (MS-Windows handle) of main program window
;_Speed=1 ;I set this to 1 because in my eyes the default speed (0) is a little bit too slow (at least with the German voice)
;sapi_SetSpeed(_Speed) ;range: -10 to 10, 0=default
_Volume=100
;{ ;create GUI
_nTstatus=CreateStatusBar(#PB_Any,_hWnd)
AddStatusBarField(85)
AddStatusBarField(85)
AddStatusBarField(85)
AddStatusBarField(85)
AddStatusBarField(250)
StatusBarText(_nTstatus,0," Speed="+str(_Speed))
StatusBarText(_nTstatus,1,"Volume="+str(_Volume)+"%")
StatusBarText(_nTstatus,2,"Playing="+str(sapi_IsVoiceRunning()))
StatusBarText(_nTstatus,3,"Paused="+str(sapi_IsVoicePaused()))
StatusBarText(_nTstatus,4,"Bookmark="+_sapi_BookmarkReached$)
_nCBvoices=ComboBoxGadget(#PB_Any,10,10,580,23)
_s$=sapi_GetVoiceNames()
for _i=1 to CountString(_s$,"|")+1
AddGadgetItem(_nCBvoices,-1,StringField(_s$,_i,"|"))
next _i
SetGadgetState(_nCBvoices,0)
_nETextToSay=EditorGadget(#PB_Any,10,44,580,241,#PB_Editor_WordWrap)
;SetGadgetText(_nETextToSay,"Enter text here. This is a mere sample text.")
SetGadgetText(_nETextToSay,""+
"There was a thunderstorm approaching,<bookmark mark="+chr(34)+"Thunder"+chr(34)+"/><silence msec="+chr(34)+"2500"+chr(34)+"/>which everyone was afraid of."+chr(13)+
"Demonstration of different languages as long as they're installed:"+chr(13)+
"<lang langid="+chr(34)+"409"+chr(34)+">This is english.</lang>"+chr(13)+
"<silence msec="+chr(34)+"250"+chr(34)+"/>"+chr(13)+
"<lang langid="+chr(34)+"407"+chr(34)+">Das ist deutsch.</lang>"+chr(13)+
"<lang langid="+chr(34)+"40C"+chr(34)+">C'est francais.</lang>"+chr(13)+
"<silence msec="+chr(34)+"500"+chr(34)+"/>"+chr(13)+
"Now <emph>this</emph> word is emphasized."+chr(13)+
"<pitch absmiddle="+chr(34)+"-10"+chr(34)+">Very low voice.</pitch>)"+chr(13)+
"<pitch absmiddle="+chr(34)+"10"+chr(34)+">Very high voice.</pitch>)"+chr(13)+
"<pitch absmiddle="+chr(34)+"0"+chr(34)+">Normal voice.</pitch>)"+chr(13)+
"<rate absspeed="+chr(34)+"-7"+chr(34)+"><spell>spell it out</spell></rate>"+chr(13)+
"<silence msec="+chr(34)+"500"+chr(34)+"/>"+chr(13)+
"<volume level="+chr(34)+"50"+chr(34)+">Hushed voice.</volume>"+chr(13)+
"<volume level="+chr(34)+"100"+chr(34)+">Loud voice.</volume>"+chr(13)+
"")
_nBsay=ButtonGadget(#PB_Any,10,295,100,30,"Say text")
_nBstop=ButtonGadget(#PB_Any,120,295,100,30,"Stop")
_nBpause=ButtonGadget(#PB_Any,230,295,100,30,"Pause")
_nBresume=ButtonGadget(#PB_Any,340,295,100,30,"Resume")
_nBSpeedDown=ButtonGadget(#PB_Any,10,335,100,30,"Speed down")
_nBSpeedUp=ButtonGadget(#PB_Any,120,335,100,30,"Speed up")
_nBVolumeDown=ButtonGadget(#PB_Any,230,335,100,30,"Volume down")
_nBVolumeUp=ButtonGadget(#PB_Any,340,335,100,30,"Volume up")
_nCBHighlightWords=CheckBoxGadget(#PB_Any,10,375,200,20,"highlight words")
SetGadgetState(_nCBHighlightWords,1)
_nBend=ButtonGadget(#PB_Any,490,335,100,30,"Quit")
;}
;{ ;message loop
_progend=0
_OldPlaying=sapi_IsVoiceRunning()
_OldPaused=sapi_IsVoicePaused()
while _progend=0
_event=WaitWindowEvent()
_gadgetevent=0
if _event=#PB_Event_Gadget
_gadgetevent=EventGadget()
elseif _event=#PB_Event_Menu
_MenuItem=EventMenu()
endif
if _OldBookmarkReached$<>sapi_GetLastBookmark()
_OldBookmarkReached$=sapi_GetLastBookmark()
StatusBarText(_nTstatus,4,"Bookmark="+_OldBookmarkReached$)
if _nThunder
if _OldBookmarkReached$="Thunder"
PlaySound(_nThunder)
endif
endif
endif
if (sapi_IsVoiceRunning()<>_OldPlaying) or (sapi_IsVoicePaused()<>_OldPaused)
StatusBarText(_nTstatus,2,"Playing="+str(sapi_IsVoiceRunning())):StatusBarText(_nTstatus,3,"Paused="+str(sapi_IsVoicePaused()))
_OldPlaying=sapi_IsVoiceRunning():_OldPaused=sapi_IsVoicePaused()
endif
if _gadgetevent=_nBsay
if _nThunder
StopSound(_nThunder)
endif
_OldCurrentWordPos=-1
sendmessage_(GadgetID(_nETextToSay),#EM_SETSEL,0,0)
sapi_Say(GetGadgetText(_nETextToSay))
elseif _gadgetevent=_nBpause
if (sapi_IsVoiceRunning()=1) and (sapi_IsVoicePaused()=0)
sapi_Pause()
endif
elseif _gadgetevent=_nBresume
if (sapi_IsVoiceRunning()=1) and (sapi_IsVoicePaused()=1)
sapi_Resume()
endif
elseif _gadgetevent=_nBstop
if sapi_IsVoiceRunning()=1
if _nThunder
StopSound(_nThunder)
endif
sapi_Stop()
sendmessage_(GadgetID(_nETextToSay),#EM_SETSEL,0,0)
endif
elseif _gadgetevent=_nBSpeedUp
if _Speed<10
_Speed=_Speed+1
sapi_SetSpeed(_Speed)
StatusBarText(_nTstatus,0," Speed="+str(_Speed))
endif
elseif _gadgetevent=_nBSpeedDown
if _Speed>-10
_Speed=_Speed-1
sapi_SetSpeed(_Speed)
StatusBarText(_nTstatus,0," Speed="+str(_Speed))
endif
elseif _gadgetevent=_nBVolumeUp
if _Volume<=90
_Volume=_Volume+10
sapi_SetVolume(_Volume)
StatusBarText(_nTstatus,1,"Volume="+str(_Volume)+"%")
endif
elseif _gadgetevent=_nBVolumeDown
if _Volume>=10
_Volume=_Volume-10
sapi_SetVolume(_Volume)
StatusBarText(_nTstatus,1,"Volume="+str(_Volume)+"%")
endif
elseif _gadgetevent=_nCBvoices
if EventType()= #PB_EventType_Change
sapi_SetVoice(GetGadgetText(_nCBvoices))
endif
elseif (_event=#PB_Event_CloseWindow) or (_gadgetevent=_nBend)
if _nThunder
StopSound(_nThunder)
endif
sendmessage_(GadgetID(_nETextToSay),#EM_SETSEL,0,0)
_progend=1
endif
if GetGadgetState(_nCBHighlightWords)
if (_OldCurrentWordPos<>sapi_GetCurrentWordPos(1)) and (sapi_GetCurrentWordLen()<>0)
_OldCurrentWordPos=sapi_GetCurrentWordPos(1)
sendmessage_(GadgetID(_nETextToSay),#EM_SETSEL,_OldCurrentWordPos,_OldCurrentWordPos+sapi_GetCurrentWordLen())
endif
else
sendmessage_(GadgetID(_nETextToSay),#EM_GETSEL,@_Sel1,@_Sel2)
if _Sel1<>_Sel2
sendmessage_(GadgetID(_nETextToSay),#EM_SETSEL,0,0)
endif
endif
_gadgetevent=0
_MenuItem=0
wend
;}
sapi_DeInit() ;call once at the end
if _nThunder
FreeSound(_nThunder)
endif
CloseWindow(_nWnd)
end
Last edited by Jens-Arne on Tue Feb 25, 2025 4:34 pm, edited 52 times in total.
Re: MS Speech SAPI Text to speech COM (Windows only)
Very nice work! Just tested and this all works on Windows 10. Thanks for sharing!Jens-Arne wrote: Sun Jan 12, 2025 12:58 pm Really great work!
I have simplified things further so you don't have to bother about anything but to supply the text to be read out. Just one pbi needed in which everything is included.
These are the only functions you need to do all the work:
Code: Select all
sapi_Init(hMainWnd.i) ;call once at the beginning, supply ID (MS-Windows handle) of main program window sapi_DeInit() ;call once at the end sapi_Say(Text$) sapi_Pause() sapi_Resume() sapi_Stop() sapi_GetVoiceNames() ;returns the names of all installed voices separated by "|" sapi_SetVoice(VoiceName$) ;VoiceName$=one of the names returned from sapi_GetVoiceNames()

Re: MS Speech SAPI Text to speech COM (Windows only)
I've added a second optional parameter for sapi_Init(), see updated description above. This way you can choose what user message number the pbi uses internally in its subclass function should #WM_USER already be occupied by your own program.
Furthermore I've changed the edit control in the example to be an EditorGadget rather than a StringGadget (that's the way it was supposed to be from the beginning).
Furthermore I've changed the edit control in the example to be an EditorGadget rather than a StringGadget (that's the way it was supposed to be from the beginning).
Re: MS Speech SAPI Text to speech COM (Windows only)
sapi_SetSpeed & sapi_SetVolume added, example updated (now with ComboBox for voice selection).
Use functions instead of variables now to determine status of playing and pausing.
This should be it - for the time being...
Use functions instead of variables now to determine status of playing and pausing.
This should be it - for the time being...

Re: MS Speech SAPI Text to speech COM (Windows only)
Thanks Justin! very nice code! Thanks for share.
If translation=Error: reply="Sorry, Im Spanish": Endif
Re: MS Speech SAPI Text to speech COM (Windows only)
I've changed the source of the voices so that more of them should be found now.
Re: MS Speech SAPI Text to speech COM (Windows only)
Added support for XP-Voices, namely Microsoft Sam (and Mike and Mary)!
See here how to get these vintage voices (2nd reply!): https://answers.microsoft.com/en-us/win ... c3af1bf2ce
It worked for me to install these voices the way that is stated there. Cool to have Sam back once more. Soi soi soi soi soi...
See here how to get these vintage voices (2nd reply!): https://answers.microsoft.com/en-us/win ... c3af1bf2ce
It worked for me to install these voices the way that is stated there. Cool to have Sam back once more. Soi soi soi soi soi...

Re: MS Speech SAPI Text to speech COM (Windows only)
I've changed the default text in the test program to demonstrate a number of things that are possible with SAPI.
For this purpose I've added a new function sapi_GetLastBookmark() which returns the name of the last bookmark reached in the text. This way one could trigger certain program functions on reaching a given bookmark if one liked (e.g. playing a thunderbolt sound upon reaching the part of the story telling that there was a thunderstorm looming).
For this purpose I've added a new function sapi_GetLastBookmark() which returns the name of the last bookmark reached in the text. This way one could trigger certain program functions on reaching a given bookmark if one liked (e.g. playing a thunderbolt sound upon reaching the part of the story telling that there was a thunderstorm looming).
Re: MS Speech SAPI Text to speech COM (Windows only)
thanks!
Can you increase the function: Automatically select the word being read.
Can you increase the function: Automatically select the word being read.
my pb for chinese:
http://ataorj.ys168.com
http://ataorj.ys168.com