SpeechRecognitionServer.app vs. NSSpeechRecognizer

Hi,

I’m trying to replace the SpeechRecognitionServer.app with a library module using NSSpeechRecognizer. Has anyone done this before and do you think that there is a possibility that it could be better?

Thanks,
kel

Come to think of it, if nobody has tried it, then we will never know. It doesn’t look too complicated.

Edited: btw, here’s how I’m starting off. Here’s the calling script which I can run from the script editor:

use theScript : script "SpeechLib"

theScript's listenFor:{"hello", "say something", "bye"}

Here’s the library script:

use framework "AppKit"

property userInput : missing value

on listenFor:userInput
	set theSpeechSynth to current application's NSSpeechRecognizer's alloc's init()
	theSpeechSynth's setCommands:{userInput}
	return (theSpeechSynth's commands) as list
end listenFor:

Note that you save the library script to “Script Library”, in the user’s home folder, as “SpeechLib”" and as type script bundle. After you save the library item, in the script editor, click on the bundle identifier button in the Script Editor window. Check to see if the “AppleScript/ObjC” option is checked.

Just the beginnings.

Edited: made a mistake on in the library script:

use framework "AppKit"

property userInput : missing value

on listenFor:userInput
	set theSpeechSynth to current application's NSSpeechRecognizer's alloc's init()
	theSpeechSynth's setCommands:userInput
	return (theSpeechSynth's commands) as list
end listenFor:

Edited: cleaned up the library script (base):

use framework "AppKit"

property userInput : missing value

on listenFor:userInput
	set theSpeechRecognizer to current application's NSSpeechRecognizer's alloc's init()
	theSpeechRecognizer's setCommands:(userInput)
	
	return (theSpeechRecognizer's commands) as list
end listenFor:

Now can start.

Good luck and thanks anyway,
kel

This is a hard one. You know why? When you wait it adds more delays.

You’d have to set your script as the delegate of the recognizer:

theSpeechRecognizer's setDelegate:me

And then implement the delegate method -speechRecognizer:didRecognizeCommand:

on speechRecognizer:theRecognizer didRecognizeCommand:aCommand
-- see what aCommand is, and act accordingly
end speechRecognizer:didRecognizeCommand:

Hi Shane,

That’s what I was missing. It has been a while.

Thanks a lot,
kel

Finally got the library script working with the window. Need to add more stuff, but if anyone is interested:

use framework "AppKit"
use scripting additions

property theCommands : missing value
property theSpeechRecognizer : missing value
property isDone : false

on listenFor:theCommands
	set theSpeechRecognizer to current application's NSSpeechRecognizer's alloc's init()
	theSpeechRecognizer's setCommands:(theCommands)
	theSpeechRecognizer's setDelegate:(me)
	theSpeechRecognizer's setDisplayedCommandsTitle:("Testing")
	theSpeechRecognizer's startListening()
	repeat until isDone
		delay 2
	end repeat
	return
end listenFor:

on speechRecognizer:theSpeechRecognizer didRecognizeCommand:aCommand
	theSpeechRecognizer's stopListening()
	set isDone to true
	beep 2
	return (aCommand as text)
end speechRecognizer:didRecognizeCommand:

It has to be run as an application. If you call this script from Script Editor, then the windows won’t close. I still need to return the command to the calling application.

gl,
kel

Alright, got it working well so far. Here’s the library script:

use framework "AppKit"
use scripting additions

property theCommands : missing value
property theSpeechRecognizer : missing value
property isDone : false
property userCommand : missing value

on listenFor:theCommands
	set theSpeechRecognizer to current application's NSSpeechRecognizer's alloc's init()
	theSpeechRecognizer's setCommands:(theCommands)
	theSpeechRecognizer's setDelegate:(me)
	theSpeechRecognizer's setDisplayedCommandsTitle:("Testing")
	theSpeechRecognizer's startListening()
	repeat until isDone
		delay 2
	end repeat
	return userCommand
end listenFor:

on speechRecognizer:theSpeechRecognizer didRecognizeCommand:aCommand
	theSpeechRecognizer's stopListening()
	set isDone to true
	set userCommand to (aCommand as text)
	beep 2
	return true
end speechRecognizer:didRecognizeCommand:

And here’s the calling application script:

use theScript : script "SpeechLib"
use scripting additions

theScript's listenFor:{"hello", "say something", "bye"}
set r to result
display dialog r

Now I can change it to different forms.

Thanks a lot Shane. I was writing the receiving handler wrongly.

Edited: note that if you accidentally run it from Script Editor, then go to quit the process Speech Recognition Server to get rid of the Feedback Window.

gl,
kel

Oops, eliminated some bad properties:

use framework "AppKit"
use scripting additions

property isDone : false
property userCommand : missing value

on listenFor:theCommands
	set theSpeechRecognizer to current application's NSSpeechRecognizer's alloc's init()
	theSpeechRecognizer's setCommands:(theCommands)
	theSpeechRecognizer's setDelegate:(me)
	theSpeechRecognizer's setDisplayedCommandsTitle:("Testing")
	theSpeechRecognizer's startListening()
	repeat until isDone
		delay 2
	end repeat
	return userCommand
end listenFor:

on speechRecognizer:theSpeechRecognizer didRecognizeCommand:aCommand
	theSpeechRecognizer's stopListening()
	set isDone to true
	set userCommand to (aCommand as text)
	return true
end speechRecognizer:didRecognizeCommand:

gl,
kel

Instead of that delay in the library script, I was thinking that this could be a stay open application. Then, you send the command to open hanker in the main app. Then, I can get rid of the repeat loop.

FYI, that’s because it has to be run on the main thread, and ASE runs scripts on a background thread. Of course, you can also run it in ASObjC Explorer, via the Use main switch.

Much better.

Fixed the problem when running repeatedly. If SpeechRecognitionServer or SpeechFeedbackServer is still running from a previous call it is buggy. Used the blocks other recognizers thing:

use framework "AppKit"
use scripting additions

property isDone : false
property userCommand : missing value

on listenFor:theCommands
	set theSpeechRecognizer to current application's NSSpeechRecognizer's alloc's init()
	theSpeechRecognizer's setCommands:(theCommands)
	theSpeechRecognizer's setDelegate:(me)
	theSpeechRecognizer's setDisplayedCommandsTitle:("Testing")
	
	-- to run repeatedly while the old SpeechFeedbackServer
	-- or SpeechRecognitionServer is still running
	theSpeechRecognizer's setBlocksOtherRecognizers:(true)
	
	theSpeechRecognizer's startListening()
	repeat until isDone
		delay 2
	end repeat
	return userCommand
end listenFor:

on speechRecognizer:theSpeechRecognizer didRecognizeCommand:aCommand
	theSpeechRecognizer's stopListening()
	set isDone to true
	set userCommand to (aCommand as text)
	return true
end speechRecognizer:didRecognizeCommand:

SpeechRecognitionServer.app has the same problem, but here we can fix it! Alright!

Still thinking about the stay open application. Maybe use a reopen handler. Can’t remember right now if you can pass back parameters to the reopen.

Edited: alright, I can pass the command back to any subroutine in the stay open.

gl,
kel

Hi,

Finally got it working right. Here’s the calling stay open application:

use theScript : script "SpeechLib3"
use scripting additions

on run
	set commandScript to "/Users/kelhome/Desktop/Script1.scpt"
	theScript's listenFor:{"hello", "say something", "bye"} aScript:commandScript sender:current application
end run

on reopen
	quit
end reopen

on idle
	return 2
end idle

Here’s the Script Libraries script:

use framework "AppKit"
use scripting additions

property userCommand : missing value
property theSender : missing value
property commandScript : missing value

on listenFor:theCommands aScript:aScript sender:sender
	set theSender to sender
	set commandScript to aScript
	set theSpeechRecognizer to current application's NSSpeechRecognizer's alloc's init()
	theSpeechRecognizer's setCommands:(theCommands)
	theSpeechRecognizer's setDelegate:(me)
	theSpeechRecognizer's setDisplayedCommandsTitle:("Testing")
	
	-- to run repeatedly while the old SpeechFeedbackServer
	-- or SpeechRecognitionServer is still running
	theSpeechRecognizer's setBlocksOtherRecognizers:(true)
	
	theSpeechRecognizer's startListening()
	return true
end listenFor:aScript:sender:

on speechRecognizer:theSpeechRecognizer didRecognizeCommand:aCommand
	theSpeechRecognizer's stopListening()
	set userCommand to (aCommand as text)
	run script commandScript with parameters userCommand
	tell theSender to quit
	return true
end speechRecognizer:didRecognizeCommand:

Here’s the command script on the desktop:

on run p
	set p to p as string
	if p is "hello" then
		display dialog p
	else if p is "say something" then
		say "something"
	else
		say "goodbye"
	end if
end run

I think that instead of running the external command script, you can pass a script or handler to the library script. Still working on that, but this should get whoever is interested started.

Edited: the library script. Removed the is done variable.

gl,
kel

Hi,

Got it working great with sending a handler. Here’s the calling stay open application:

use theScript : script "SpeechLib4"
use scripting additions

on run
	--set commandScript to "/Users/kelhome/Desktop/Script1.scpt"
	theScript's listenFor:{"hello", "say something", "bye"} aHandler:processCommand sender:current application
end run

on reopen
	quit
end reopen

on idle
	return 2
end idle

on processCommand(p)
	if p is "hello" then
		display dialog p
	else if p is "say something" then
		say "something"
	else
		say "goodbye"
	end if
end processCommand

Here’s the library script:

use framework "AppKit"
use scripting additions

property userCommand : missing value
property theSender : missing value
property commandHandler : missing value

on listenFor:theCommands aHandler:aHandler sender:sender
	set theSender to sender
	set commandHandler to aHandler
	set theSpeechRecognizer to current application's NSSpeechRecognizer's alloc's init()
	theSpeechRecognizer's setCommands:(theCommands)
	theSpeechRecognizer's setDelegate:(me)
	theSpeechRecognizer's setDisplayedCommandsTitle:("Testing")
	
	-- to run repeatedly while the old SpeechFeedbackServer
	-- or SpeechRecognitionServer is still running
	theSpeechRecognizer's setBlocksOtherRecognizers:(true)
	
	theSpeechRecognizer's startListening()
	return true
end listenFor:aHandler:sender:

on speechRecognizer:theSpeechRecognizer didRecognizeCommand:aCommand
	theSpeechRecognizer's stopListening()
	set userCommand to (aCommand as text)
	commandHandler(userCommand)
	tell theSender to quit
	return true
end speechRecognizer:didRecognizeCommand:

Now I can rest.

Thanks for everything and good luck,
kel

Hi,

Here’s the final scripts that you can modify. You might want to change some things to manage memory better.

Here’s the calling script that is saved as a stay open application:

use theScript : script "SpeechLib4"
use scripting additions

on run
	theScript's listenFor:{"hello", "say something", "bye"} aHandler:processCommand sender:current application
end run

-- double click icon to quit
on reopen
	quit
end reopen

on idle
	return 2
end idle

on quit
	try
		display dialog "Quit?"
		continue quit
	on error
		run (current application)
	end try
end quit

on processCommand(p)
	if p is "hello" then
		say "hi"
	else if p is "say something" then
		say "something"
	else
		say "goodbye"
	end if
end processCommand

Still need to test the effect of the idle times.

Here’s the Script Libraries script which you save as a script bundle.

use framework "AppKit"
use scripting additions

property userCommand : missing value
property theSender : missing value
property processCommand : missing value

on listenFor:theCommands aHandler:aHandler sender:sender
	set theSender to sender
	set processCommand to aHandler
	set theSpeechRecognizer to current application's NSSpeechRecognizer's alloc's init()
	theSpeechRecognizer's setCommands:(theCommands)
	theSpeechRecognizer's setDelegate:(me)
	theSpeechRecognizer's setDisplayedCommandsTitle:("Testing")
	
	-- to run repeatedly while the old SpeechFeedbackServer
	-- or SpeechRecognitionServer is still running
	theSpeechRecognizer's setBlocksOtherRecognizers:(true)
	
	theSpeechRecognizer's startListening()
	return true
end listenFor:aHandler:sender:

on speechRecognizer:theSpeechRecognizer didRecognizeCommand:aCommand
	theSpeechRecognizer's stopListening()
	theSpeechRecognizer's setBlocksOtherRecognizers:(false)
	set userCommand to (aCommand as text)
	processCommand(userCommand)
	tell theSender to quit
	return true
end speechRecognizer:didRecognizeCommand:

After saving to Script Libraries in the Library folder, click on Bundle Contents and check AppleScript/Objective-C Library in the drawer.

I wouldn’t pass a big handler to the library script.

Edited: just thought of a use for the idle handler besides keeping the app running. You could use it for setting an idle time for quitting. i.e. if the user doesn’t respond within 5 minutes, then quit.

gl,
kel