Page 1 of 1

Done (OSX) A few interesting issues with NSSpeechSynthesizer

Posted: Sun Sep 20, 2015 10:02 am
by erion
Hi all,

I'm currently trying to implement a speech module in PB. Most things work, however I have a few interesting issues when I try to implement a queue:
1. My didFinishSpeaking function is only called if I don't use a synth function as a parameter to the loop. E.g. if I use isSpeaking, the program just exits or hangs.
2. If my didFinishSpeaking function in the delegate class gets called, the argument values do not mach. E.g. for 'success' I always get 69 and the synth argument's value is not the same as the synth class's I created.

Note:
For this to run, you will need Wilbert's Cocoa lib at http://www.purebasic.fr/english/viewtop ... 19&t=50688
At the moment, the code has a memory leak, as both the synth and the delegate will not be released.

Code: Select all

Global pbNSSpeechSynthesizerDelegateClass, synthDelegate , RunLoop

Procedure.i create(voice.s)
  If voice<>""
    ProcedureReturn CocoaMessage(0, CocoaMessage(0, 0, "NSSpeechSynthesizer alloc"), "initWithVoice:$", @voice)
    Else
  ProcedureReturn CocoaMessage(0, CocoaMessage(0, 0, "NSSpeechSynthesizer alloc"), "init")
EndIf  
EndProcedure

Procedure Speak(synth.i, text.s)
  CocoaMessage(0,synth, "startSpeakingString:$", @text)
  EndProcedure
  
  Procedure Speaking(synth.i)
    ProcedureReturn CocoaMessage(0, synth, "isSpeaking")
    EndProcedure

Procedure availableVoices (Array avArray.s(1))
    Protected i, av.i, vc
    
av=CocoaMessage(0, 0, "NSSpeechSynthesizer availableVoices")
    vc=CocoaMessage(0,av,"count")
    
    If vc
    ReDim avArray(vc-1)
    For i = 0 To vc - 1
avArray(i)=PeekS(CocoaMessage(0, CocoaMessage(0, av, "objectAtIndex:", i), "UTF8String"), -1, #PB_UTF8)
Next i
ProcedureReturn 1
EndIf
    ProcedureReturn 0
    EndProcedure
    
    ProcedureC didFinishSpeaking(synth.i,finishedSpeaking.a)
    Debug "finished: "+Str(finishedSpeaking)+" synth is "+Str(synth)
        EndProcedure
    
    Procedure Init()
    pbNSSpeechSynthesizerDelegateClass = oCreateClass(oClass("NSObject"), "pbNSSpeechSynthesizerDelegateClass")
oClassAddMethod(pbNSSpeechSynthesizerDelegateClass, oSel("speechSynthesizer:didFinishSpeaking:"), @didFinishSpeaking(), "v@:@@")
oRegisterClass(pbNSSpeechSynthesizerDelegateClass)
synthDelegate = CocoaMessage(0, 0, "pbNSSpeechSynthesizerDelegateClass new")
RunLoop=cocoaMessage(0,0,"NSRunLoop currentRunLoop")
    EndProcedure
    
    
    Procedure new(voice.s="")
          Protected synth.i=create(voice)
    
    If synth
cocoaMessage(0,synth,"setDelegate:@",@synthDelegate)
Debug Str(synth)
ProcedureReturn synth
EndIf
ProcedureReturn 0
EndProcedure

Procedure freeSynth(synth)
cocoaMessage(0,synth,"release")
EndProcedure
  
  Procedure Free()
cocoaMessage(0,synthDelegate,"release")
  cocoaMessage(0,pbNSSpeechSynthesizerDelegateClass,"release")
    EndProcedure
  
  Init()
  synth=new()
  Speak(synth,"This is  a test.")
        While 1
CocoaMessage(0, runloop, "run")
Delay(5)
	Wend
Delay(2000)
freeSynth(synth)
Free()

Re: (OSX) A few interesting issues with NSSpeechSynthesizer

Posted: Thu Sep 24, 2015 12:51 pm
by erion
I have done some more investigating, unfortunately my issue is still not fully resolved.

1. It seems that NSRunLoop:run will permanently block my application, I will probably have to use a separate thread. I wonder if I could tap into PB's RunLoop, if any.
2. Using Wilbert's oClassName function, I figured out that didFinishSpeaking returns a "pbNSSpeechSynthesizerDelegateClass" as the sender. This is particularly interesting, as the documentation says for the sender parameter:
An NSSpeechSynthesizer object that has stopped speaking into the sound output device.
The second parameter now returns 65 for true. Either there is a third, unmentioned parameter added, which I doubt, or I am using the wrong type for the bool param.

Erion

Re: (OSX) A few interesting issues with NSSpeechSynthesizer

Posted: Thu Sep 24, 2015 12:59 pm
by wilbert
The Cocoa tips thread has some examples of dealing with delegates
http://www.purebasic.fr/english/viewtop ... 19&t=50795

For speech there's also another option.
You can use the C functions like SpeakCFString from the Speech Synthesis Manager.

Re: (OSX) A few interesting issues with NSSpeechSynthesizer

Posted: Thu Sep 24, 2015 1:17 pm
by erion
Huge thanks Wilbert, I'm going to check these out.

Erion

Re: (OSX) A few interesting issues with NSSpeechSynthesizer

Posted: Thu Sep 24, 2015 2:33 pm
by erion
Wilbert,
I checked out the Cocoa methods thread. Apart from not specifying a type for my delegate function parameters and using your methods to create my class, everything seems to be the same.
The speech manager functions are sadly deprecated since 10.8.

My delegate function is called, honouring a sent stop function properly. My only problem seems to be the passed parameters from OS X.

I am not sure why I get back my delegate class, instead of the NSSpeechSynthesizer object as Apple's documentation says.

Debugging the bool parameter, it does not really change, whether I call delay then stop to stop the synth, or just let it finish speaking.

Any ideas?

Erion

Re: (OSX) A few interesting issues with NSSpeechSynthesizer

Posted: Thu Sep 24, 2015 2:41 pm
by wilbert
I think your callback procedure should look like this

Code: Select all

ProcedureC didFinishSpeaking(obj, sel, sender, success)

EndProcedure
You forgot the first two arguments.
erion wrote:The speech manager functions are sadly deprecated since 10.8.
Some of them are but not all of them. Here's an example

Code: Select all

ImportC ""
  
  CFSTR(cStr.p-ascii) As "___CFStringMakeConstantString"
  
  ContinueSpeech (chan)
  CopySpeechProperty (chan, property, *object)
  CountVoices (*numVoices)
  DisposeSpeechChannel (chan)
  GetIndVoice (index, *voice)
  GetVoiceDescription (*voice, *info, infoLength)
  NewSpeechChannel (*voice, *chan)
  PauseSpeechAt (chan, whereToPause)
  SetSpeechProperty (chan, property, object)
  SpeakCFString (chan, aString, options)
  SpeechBusy ()
  SpeechBusySystemWide ()
  StopSpeech (chan)
  StopSpeechAt (chan, whereToStop)
  
EndImport

Structure VoiceSpec
  creator.l
  id.l
EndStructure

Structure VoiceDescription
  length.l
  voice.VoiceSpec
  version.l
  nameLen.a
  name.a[63]
  commentLen.a
  comment.a[255]
  gender.w
  age.w
  script.w
  language.w
  region.w
  reserved.l[4]
EndStructure


kSpeechSpeechDoneCallBack = CFSTR("sdcb")


; create a voice channel with voice named Alex

CountVoices(@voiceCount.w)
idx = 0
While idx < voiceCount
  GetIndVoice(idx, @voice.VoiceSpec)
  GetVoiceDescription (voice, @voiceInfo.VoiceDescription, SizeOf(VoiceDescription))
  If PeekS(@voiceInfo\name, voiceInfo\nameLen, #PB_Ascii) = "Alex"
    Break
  EndIf
  idx + 1  
Wend

NewSpeechChannel(@voice, @chan)


; setup the callback

ProcedureC DoneCB(chan, refCon)
  Debug "done"
EndProcedure

CallbackAsNumber = CocoaMessage(0, 0, "NSNumber numberWithInteger:", @DoneCB())
SetSpeechProperty(chan, kSpeechSpeechDoneCallBack, CallbackAsNumber)


; speak a text 

Text = CocoaMessage(0,0,"NSString stringWithString:$", @"This is a test")
SpeakCFString(chan, Text, #Null)

MessageRequester("","")
PB 5.40+

Code: Select all

*a.Integer = dlsym_(#RTLD_DEFAULT, "kSpeechSpeechDoneCallBack")
kSpeechSpeechDoneCallBack = *a\i

; create a voice channel with voice named Alex

CountVoices_(@voiceCount.w)
idx = 0
While idx < voiceCount
  GetIndVoice_(idx, @voice.VoiceSpec)
  GetVoiceDescription_(voice, @voiceInfo.VoiceDescription, SizeOf(VoiceDescription))
  If PeekS(@voiceInfo\name, voiceInfo\nameLen, #PB_Ascii) = "Alex"
    Break
  EndIf
  idx + 1  
Wend

NewSpeechChannel_(@voice, @chan)


; setup the callback

ProcedureC DoneCB(chan, refCon)
  Debug "done"
EndProcedure

CallbackAsNumber = CocoaMessage(0, 0, "NSNumber numberWithInteger:", @DoneCB())
SetSpeechProperty_(chan, kSpeechSpeechDoneCallBack, CallbackAsNumber)


; speak a text 

Text = CocoaMessage(0,0,"NSString stringWithString:$", @"This is a test")
SpeakCFString_(chan, Text, #Null)

MessageRequester("Speaking", "This is a test")

Re: (OSX) A few interesting issues with NSSpeechSynthesizer

Posted: Thu Sep 24, 2015 3:01 pm
by erion
Wilbert, you should get an award. Declaring my procedure as you suggested made everything work like a charm. Huge, huge thanks!

Apparently, obj and sel are passed natively via swift and objC, which I should have thought about as something similar is happening in c++.

Erion

Re: (OSX) A few interesting issues with NSSpeechSynthesizer

Posted: Thu Sep 24, 2015 7:04 pm
by wilbert
erion wrote:Apparently, obj and sel are passed natively via swift and objC
Yes, it's described in the Objective-C Runtime Programming Guide.
Objective-C Runtime Programming Guide wrote:These arguments give every method implementation explicit information about the two halves of the message expression that invoked it. They’re said to be “hidden” because they aren’t declared in the source code that defines the method. They’re inserted into the implementation when the code is compiled.

Re: Done (OSX) A few interesting issues with NSSpeechSynthes

Posted: Fri Sep 28, 2018 4:21 pm
by zefiro_flashparty
link down?

Re: Done (OSX) A few interesting issues with NSSpeechSynthes

Posted: Fri Sep 28, 2018 4:53 pm
by RSBasic
What link?