Skip to content
Permalink
Browse files

TextToSpeech_macx: reimplement using NSSpeechSynthesizer.

Fixes Text-to-Speech not working on Mavericks and
avoids several deprecated API calls.
  • Loading branch information...
zorgiepoo authored and mkrautz committed Jan 21, 2014
1 parent 29a65c6 commit 15f761075863ccb2ecee7ada59bae3d4de18a707
Showing with 72 additions and 61 deletions.
  1. +70 −59 src/mumble/{TextToSpeech_macx.cpp → TextToSpeech_macx.mm}
  2. +2 −2 src/mumble/mumble.pro
@@ -1,6 +1,7 @@
/* Copyright (C) 2005-2011, Thorvald Natvig <thorvald@natvig.com>
Copyright (C) 2007, Sebastian Schlingmann <mit_service@users.sourceforge.net>
Copyright (C) 2008-2011, Mikkel Krautz <mikkel@krautz.dk>
Copyright (C) 2014, Mayur Pawashe <zorgiepoo@gmail.com>
All rights reserved.
@@ -35,89 +36,99 @@
#include "Global.h"
#include "TextToSpeech.h"

class TextToSpeechPrivate {
public:
SpeechChannel scChannel;
QMutex qmLock;
Fixed fVolume;
QList<QByteArray> qlMessages;
bool bRunning;
@interface MUSpeechSynthesizerPrivateHelper : NSObject <NSSpeechSynthesizerDelegate>
@property (nonatomic, readonly) NSSpeechSynthesizer *synthesizer;
- (void)appendMessage:(NSString *)message;
- (void)processSpeech;
@end

TextToSpeechPrivate();
void ProcessSpeech();
void say(const QString &text);
void setVolume(int v);
};
@interface MUSpeechSynthesizerPrivateHelper ()
@property (nonatomic, retain) NSMutableArray *messages;
@property (nonatomic, retain) NSSpeechSynthesizer *synthesizer;
@end

static void speech_done_cb(SpeechChannel scChannel, void *udata) {
TextToSpeechPrivate *tts = reinterpret_cast<TextToSpeechPrivate *>(udata);
@implementation MUSpeechSynthesizerPrivateHelper

Q_ASSERT(scChannel == tts->scChannel);
- (id)init {
if ((self = [super init])) {
self.synthesizer = [[NSSpeechSynthesizer alloc] initWithVoice:nil];
self.messages = [NSMutableArray array];
self.synthesizer.delegate = self;
}
return self;
}

DisposeSpeechChannel(tts->scChannel);
- (void)dealloc {
self.synthesizer = nil;
self.messages = nil;
[super dealloc];
}

if (tts->qlMessages.isEmpty())
tts->bRunning = false;
else
tts->ProcessSpeech();
- (void)appendMessage:(NSString *)message {
[self.messages insertObject:message atIndex:0];
}

TextToSpeechPrivate::TextToSpeechPrivate() {
bRunning = false;
- (void)processSpeech {
Q_ASSERT(self.messages.count == 0);

NSString *poppedMessage = [self.messages lastObject];
[self.synthesizer startSpeakingString:poppedMessage];
[self.messages removeLastObject];
}

void TextToSpeechPrivate::ProcessSpeech() {
QByteArray ba;
- (void)speechSynthesizer:(NSSpeechSynthesizer *)synthesizer didFinishSpeaking:(BOOL)success {
Q_UNUSED(synthesizer);
Q_UNUSED(success);

if (self.messages.count != 0) {
[self processSpeech];
}
}

@end

class TextToSpeechPrivate {
public:
MUSpeechSynthesizerPrivateHelper *m_synthesizerHelper;

qmLock.lock();
ba = qlMessages.takeFirst();
qmLock.unlock();
TextToSpeechPrivate();
~TextToSpeechPrivate();
void say(const QString &text);
void setVolume(int v);
};

NewSpeechChannel(NULL, &scChannel);
SetSpeechInfo(scChannel, soVolume, &fVolume);
SetSpeechInfo(scChannel, soRefCon, this);
SetSpeechInfo(scChannel, soSpeechDoneCallBack, reinterpret_cast<void *>(speech_done_cb));
SpeakText(scChannel, ba.constData(), ba.size());
TextToSpeechPrivate::TextToSpeechPrivate() {
m_synthesizerHelper = [[MUSpeechSynthesizerPrivateHelper alloc] init];
}

TextToSpeechPrivate::~TextToSpeechPrivate() {
[m_synthesizerHelper release];
}

void TextToSpeechPrivate::say(const QString &text) {
QTextCodec *codec = QTextCodec::codecForName("Apple Roman");
Q_ASSERT(codec != NULL);
QByteArray byteArray = text.toUtf8();
NSString *message = [[NSString alloc] initWithBytes:byteArray.constData() length:byteArray.size() encoding:NSUTF8StringEncoding];
if (message == nil) {
return;
}

qmLock.lock();
qlMessages.append(codec->fromUnicode(text));
qmLock.unlock();
[m_synthesizerHelper appendMessage:message];
[message release];

if (!bRunning) {
ProcessSpeech();
bRunning = true;
if (![m_synthesizerHelper.synthesizer isSpeaking]) {
[m_synthesizerHelper processSpeech];
}
}

void TextToSpeechPrivate::setVolume(int volume) {
fVolume = FixRatio(volume, 100);
// Check for setVolume: availability. It's only available on 10.5+.
if ([m_synthesizerHelper.synthesizer respondsToSelector:@selector(setVolume:)]) {
[m_synthesizerHelper.synthesizer setVolume:volume / 100.0];
}
}

TextToSpeech::TextToSpeech(QObject *) {
enabled = true;
d = NULL;

/* Determine which release of OS X we're running on. Tiger has a buggy implementation, and
* therefore we'll just disable ourselves when we're running on that.
*
* What it comes down to, is that calling DisposeSpeechChannel() on Tiger will crash in certain
* situations.
*
* For more information, see this thread on Apple's speech mailing list:
* http://lists.apple.com/archives/speech-dev/2005/Aug/msg00000.html
*/

int version = QSysInfo::MacintoshVersion;
if (version != QSysInfo::MV_Unknown && version < QSysInfo::MV_LEOPARD) {
qWarning("Mac OS X 10.4 (Tiger) detected. Disabling Text-to-Speech because of a buggy implementation in 10.4.");
return;
}

d = new TextToSpeechPrivate();
}

@@ -236,8 +236,8 @@ unix {
LIBS += -framework Security -framework SecurityInterface -framework ApplicationServices

HEADERS *= GlobalShortcut_macx.h ConfigDialogDelegate.h
SOURCES *= TextToSpeech_macx.cpp SharedMemory_unix.cpp
OBJECTIVE_SOURCES *= GlobalShortcut_macx.mm os_macx.mm Log_macx.mm
SOURCES *= SharedMemory_unix.cpp
OBJECTIVE_SOURCES *= TextToSpeech_macx.mm GlobalShortcut_macx.mm os_macx.mm Log_macx.mm

!CONFIG(no-cocoa) {
DEFINES *= USE_COCOA

0 comments on commit 15f7610

Please sign in to comment.
You can’t perform that action at this time.