Speaking MUD text

Posted by Onoitsu2 on Thu 25 May 2006 02:24 AM — 20 posts, 113,116 views.

USA #0
if you are a Windows XP user, or have installed Office XP or later, you have something called TTS (text to speech), and VBScript allows you to control the COM object for such a thing.

Below is a copied trigger i made, that SPEAKS everything said that it matches.

I do NOT recommend using this on a spammed channel, or something that occurs VERY often, as it will delay the program until it is done speaking whatever it has matched, which 1-2 seconds is not bad, but CAN be disasterous!

I am probably going to use this knowledge to make something that watches for blindness, failed blindness, and other successful and unsucessful status changes, then speaks a short phrase accordingly, as in the heat of battle seeing the text scroll by in the blink of an eye it can be missed, and then be difficult to continue the battle, while wondering why you cannot land an attack ... haha.


<triggers>
  <trigger
   expand_variables="y"
   ignore_case="y"
   keep_evaluating="y"
   match="^(.*?) gossips \'(.*?)\'$"
   name="gossip_speak"
   regexp="y"
   repeat="y"
   send_to="12"
   sequence="1"
  >
  <send>dim talk
set talk = createobject ("SAPI.SpVoice")
  talk.Speak("%0")
  talk = 0</send>
  </trigger>
</triggers>


Enjoy...


Laterzzz,
Onoitsu2
Australia Forum Administrator #1
This is great! I thought getting speech to work was harder than that. I have done a similar thing in Lua too:



assert (loadlib ("luacom.dll","luaopen_luacom")) ()

-- Instantiate a SAPI voice object
talk = assert (luacom.CreateObject ("SAPI.SpVoice"), "cannot open SAPI")

-- Method call
talk:Speak ("hi there")


The link I used to download the luacom.dll was:


http://luaforge.net/frs/download.php/922/luacom-1.3-luabinaries-bin.zip





My only comment on your code is, it would be better to create SAPI once (eg. in a script on World startup) rather than for every trigger. Your method is going to be much slower as the SAPI object is created, and then destroyed, every time the trigger fires.
Amended on Thu 01 Mar 2007 08:40 PM by Nick Gammon
Russia #2
This is indeed great, but I wanted to comment on the "pausing the program" part. In order to avoid Mushclient locking up while the text is being spoken you can make Speak() run asynchronously by passing it the SPF_ASYNC flag, which is equal to 1:


tts.Speak("Hello world!", 1)


or


tts:Speak("Hello world!", 1)


in Lua.

The full SAPI 5.1 reference is at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wcesapi/html/ceconSAPIReference.asp

The reference for the ISpeak interface (which is what the Speak() methods represents) is at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wcesapi/html/cerefISpVoiceSpeak.asp

And the listing of all SPEAKFLAGS that Speak() recognizes is at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wcesapi/html/ceconSAPIEnumerations.asp

Also, if you wanted to "interrupt the current programme" in order to speak some emergency message, you can OR together the SPF_ASYNC (1) and SPF_PURGEBEFORESPEAK (2) flags (producing 3):

tts:Speak("Behind you!", 3)


That will discard the entire queue of pending messages and speak that one immediately.

P.S. I am confused - doesn't asynchronous mean "do in order", as opposed to "do in parallel"? In other words, doesn't async in this context mean exactly the opposite of what it really means?
Amended on Sat 27 May 2006 10:23 PM by Ked
Australia Forum Administrator #3
Thanks for all that. The asynchronous stuff is a great idea.

I have done another thread to do a bigger example in Lua:

http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=6593

This speaks a warning when your health is low. The warning says the actual health level, which is probably more useful than simply playing a sound, as you will notice it, even with lots of messages scrolling by.

USA #4
The method for async speaking does not work via the trigger method i posted, perhaps via a single object created in a plugin, and then spoken to only that single object.

Just thought I'd put that up there for anyone that was planning on using the simple trigger..

Laterzzz,
Onoitsu2
Australia Forum Administrator #5
Yes, destroying the object with "talk = 0" probably makes it hard for it to do asynchronous speaking.

The method I used in the other post, using Lua, shows how you just create the object the first time you need it.

http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=6593
Australia Forum Administrator #6
Quote:

P.S. I am confused - doesn't asynchronous mean "do in order", as opposed to "do in parallel"? In other words, doesn't async in this context mean exactly the opposite of what it really means?


It means "not synchronized" - that is, the speech (in this case) is not synchronized to waiting for the command to end.

I looked up the definition in Google, and this was part of the answer:


Refers to events that are not synchronized, or coordinated, in time. ... Starting the next operation before the current one is completed.

USA #7
I am in the process of making a plugin in VBScript, that will allow altering of speed of the text, and also possibly some customization of the voice used.

I am making it in VBScript cause I am unsure if version 3.74 of mushclient will cooperate with the LUA com interface, and if someone can tell me that it positively does, then I will consider converting it into an LUA plugin then.


I will post results when more developed, so probably later tonight :)

Laterzzz,
Onoitsu2
Australia Forum Administrator #8
Do you mean 3.84? The latest version?

I have positively tested it to work OK under 3.84, using this slightly revised script (changed loadlib to package.loadlib, and the entry point name in the DLL has changed):


assert (package.loadlib ("luacom.dll","luacom_open")) ()

-- Instantiate a SAPI voice object
talk = assert (luacom.CreateObject ("SAPI.SpVoice"), "cannot open SAPI")

-- Method call
talk:Speak ("hi there")


You also need the luacom.dll designed to work with Lua 5.1. That is available in this directory:

http://www.gammon.com.au/files/mushclient/lua5.1_extras/

See the readme.txt file for more details.
Amended on Thu 01 Mar 2007 08:40 PM by Nick Gammon
USA #9
No I mean 3.74, which is the version I use regularly, and I do so, so that I can guarantee that my plugins will be several versions backwards compatible, as MANY people that use my plugins are still using version between 3.74 and 3.78.

From my understanding from the posts about LUACOM the entry point had been moved between versions 5.0 and 5.1 of LUA, and in that having happened that will require 2 different versions of my plugin then. And that is something I have been able to avoid, even with the slight differences between 5.0 and 5.1 of LUA.

Thanks,
Onoitsu2
Australia Forum Administrator #10
You can detect the Lua version from the _VERSION variable, and modify the behaviour of your script slightly accordingly.
USA #11
I do believe that I have made a VERY simple TTS plugin, that can be implemented into virtually ANY scripting scheme, it uses CallPlugin to set the options for a TTS message, and has 2 queues, normal and alert, which will interrupt a normal item and will then replay the interrupted normal item from the beginning. EACH Message can have a different voice and rate, that is set just PRIOR to the actual message to be spoken. So 3 CallPlugin commands in a row in any 1 trigger or alias can mean... Rate Voice Message, Voice Rate Message, Rate Message, Voice Message, or just Message (which will run at default rate 0, and whatever the default voice is set to within the plugin via an alias).

I also have a sub in it that will "Reformat" some symbolic words/meanings and neologisms that are now quite common.

the url is: www.torasin.com/~venificius/MUSHclient_ttsVB.xml

I tried posting it but is 8471 Chars long says SciTE.

You can also get it via my Zip or Installer Versions of my Plugins...

ZIP'd Plugins - www.torasin.com/~venificius/AardwolfMC_Plugins.zip

Installer Version - www.torasin.com/~venificius/Aardwolf_MC_Plugins.exe

THE SERVER IS SADLY VERY CASE SENSITIVE!!!! So please copy the links EXACTLY as I have posted them

Laterzzz,
Onoitsu2
Amended on Sun 04 Mar 2007 10:23 AM by Onoitsu2
#12
Hmm...Ok im usin what im findin here but im a idiot when it comes to this stuff...For the MUSH can somone make a script that only reads lines after "(Web): #######" where anything after the (WEB): is spoken? Just wanted to try this for fun and after 5 hours im tired and dun wanna mess with it if I dont ahve to. Thanks.

Edit: ohh and im usin Vista if that matters.

2nd edit: I am using the program from http://mushclient.com/mushclient/plugins/sapi.xml To attempt to get it to work. But I cannot seem to figure out how to tell the sub command to only activate on (WEB): promts and repeat what comes after the prompt when recieved from the World. Any ideas?

Example

wt but my triggers arent working.
(Web): You say, "But my triggers arent working."
H:2113 M:1385 B:100% [c eb]


Want reading to start after (Web): and end on ." at the end of the line.......but it likes to start reading at the prompt of H: and end on the end of the line above it.
Amended on Thu 16 Apr 2009 04:26 AM by Thellasian
Australia Forum Administrator #13
I would install SAPI at some stage (eg. in your script file) like this:


set sapiobject = CreateObject("SAPI.SpVoice")




Then make a trigger that does "send to script", which matches the things you want to speak, and in the script:


sapiobject.Speak "%1",1

#14
Heh, there in lies the problem. No idea bout triggers in general. I tried to set it using a general <Triggers> <trigger/> command but it refused to apply to that trigger only.
#15
Hmm, how about this. Is it possible to create a trigger that sends a specific line that the server sends me to a different window in MUSH. and set the plugin to only run on that second window? So that when it sees (Web): on the screen it sends over to Window 2..and then the Reader reads it from that screen only?
Australia Forum Administrator #16
I suppose, but I think this sounds like more work than simply making a trigger that speaks the lines you want, and omit the line that speaks everything that comes in.
#17
hmm. The pulgin doesnt like to see lines that do not include the trigger file. Any line entered brings up a error....
#18
fixed it..new error:
[string "Trigger: "]:1: '=' expected near '<eof>'


New Script:


</plugin>

<!-- Triggers -->

<aliases>
<alias script="togglespeaking" match="jfw" enabled="y" group="speech" omit_from_command_history="y" omit_from_log="y" omit_from_output="y" sequence="100">
</alias>
</aliases>

<triggers>
</triggers>

<!-- Script -->


<script>
<![CDATA[

dim sapiobject
dim speak 'do we want to speak?


sub toggleSpeaking (name, line, wc)
if speak = 1 then
speak = 0
sapiobject.Speak "sapi auto speak off.", 1
else
speak = 1
sapiobject.Speak "sapi auto speak on.",1
end if
end sub

sub OnPluginInstall
set sapiobject = CreateObject("SAPI.SpVoice")
sapiobject.Speak "sapi speaker is ready to rock!",1
speak = 1
end sub


sub OnPluginBroadcast (msg, id, name, text)
if msg = 222 then
sapiobject.Speak "%1",1
end if
end sub
]]>
</script>


</muclient>



Trigger:

<triggers>
<trigger
enabled="y"
ignore_case="y"
match="Web"
regexp="y"
send_to="12"
sequence="100"
>
<send>%0</send>
</trigger>
</triggers>


I believe the only prob is telling the trigger WHAT to send...so did %0 to send the whole line to the Sapi program. but thats when the errors appear. it no longer speaks all the time which is what I wanted....
Australia Forum Administrator #19
You are sending what the MUD sent to script, which will generate script errors. You want to send something like:


sapiobject.Speak "%1",1