Skip to content

Commit 5b9984b

Browse files
committed
Add audio/general/text_to_speech project setting to enable/disable TTS.
1 parent 91f3cdf commit 5b9984b

16 files changed

+133
-63
lines changed

core/config/project_settings.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -1290,6 +1290,7 @@ ProjectSettings::ProjectSettings() {
12901290
GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor", false);
12911291

12921292
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "audio/buses/default_bus_layout", PROPERTY_HINT_FILE, "*.tres"), "res://default_bus_layout.tres");
1293+
GLOBAL_DEF_RST("audio/general/text_to_speech", false);
12931294
GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/2d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f);
12941295
GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/3d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f);
12951296

doc/classes/DisplayServer.xml

+9
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,7 @@
10171017
- [code]language[/code] is language code in [code]lang_Variant[/code] format. [code]lang[/code] part is a 2 or 3-letter code based on the ISO-639 standard, in lowercase. And [code]Variant[/code] part is an engine dependent string describing country, region or/and dialect.
10181018
Note that Godot depends on system libraries for text-to-speech functionality. These libraries are installed by default on Windows and macOS, but not on all Linux distributions. If they are not present, this method will return an empty list. This applies to both Godot users on Linux, as well as end-users on Linux running Godot games that use text-to-speech.
10191019
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
1020+
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
10201021
</description>
10211022
</method>
10221023
<method name="tts_get_voices_for_language" qualifiers="const">
@@ -1025,34 +1026,39 @@
10251026
<description>
10261027
Returns an [PackedStringArray] of voice identifiers for the [param language].
10271028
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
1029+
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
10281030
</description>
10291031
</method>
10301032
<method name="tts_is_paused" qualifiers="const">
10311033
<return type="bool" />
10321034
<description>
10331035
Returns [code]true[/code] if the synthesizer is in a paused state.
10341036
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
1037+
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
10351038
</description>
10361039
</method>
10371040
<method name="tts_is_speaking" qualifiers="const">
10381041
<return type="bool" />
10391042
<description>
10401043
Returns [code]true[/code] if the synthesizer is generating speech, or have utterance waiting in the queue.
10411044
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
1045+
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
10421046
</description>
10431047
</method>
10441048
<method name="tts_pause">
10451049
<return type="void" />
10461050
<description>
10471051
Puts the synthesizer into a paused state.
10481052
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
1053+
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
10491054
</description>
10501055
</method>
10511056
<method name="tts_resume">
10521057
<return type="void" />
10531058
<description>
10541059
Resumes the synthesizer if it was paused.
10551060
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
1061+
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
10561062
</description>
10571063
</method>
10581064
<method name="tts_set_utterance_callback">
@@ -1065,6 +1071,7 @@
10651071
- [constant TTS_UTTERANCE_BOUNDARY] callable's method should take two [int] parameters, the index of the character and the utterance ID.
10661072
[b]Note:[/b] The granularity of the boundary callbacks is engine dependent.
10671073
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
1074+
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
10681075
</description>
10691076
</method>
10701077
<method name="tts_speak">
@@ -1086,13 +1093,15 @@
10861093
[b]Note:[/b] On Windows and Linux (X11), utterance [param text] can use SSML markup. SSML support is engine and voice dependent. If the engine does not support SSML, you should strip out all XML markup before calling [method tts_speak].
10871094
[b]Note:[/b] The granularity of pitch, rate, and volume is engine and voice dependent. Values may be truncated.
10881095
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
1096+
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
10891097
</description>
10901098
</method>
10911099
<method name="tts_stop">
10921100
<return type="void" />
10931101
<description>
10941102
Stops synthesis in progress and removes all utterances from the queue.
10951103
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
1104+
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
10961105
</description>
10971106
</method>
10981107
<method name="virtual_keyboard_get_height" qualifiers="const">

doc/classes/ProjectSettings.xml

+4
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,10 @@
371371
The base strength of the panning effect for all [AudioStreamPlayer3D] nodes. The panning strength can be further scaled on each Node using [member AudioStreamPlayer3D.panning_strength]. A value of [code]0.0[/code] disables stereo panning entirely, leaving only volume attenuation in place. A value of [code]1.0[/code] completely mutes one of the channels if the sound is located exactly to the left (or right) of the listener.
372372
The default value of [code]0.5[/code] is tuned for headphones. When using speakers, you may find lower values to sound better as speakers have a lower stereo separation compared to headphones.
373373
</member>
374+
<member name="audio/general/text_to_speech" type="bool" setter="" getter="" default="false">
375+
If [code]true[/code], text-to-speech support is enabled, see [method DisplayServer.tts_get_voices] and [method DisplayServer.tts_speak].
376+
[b]Note:[/b] Enabling TTS can cause addition idle CPU usage and interfere with the sleep mode, so consider disabling it if TTS is not used.
377+
</member>
374378
<member name="audio/video/video_delay_compensation_ms" type="int" setter="" getter="" default="0">
375379
Setting to hardcode audio delay when playing video. Best to leave this untouched unless you know what you are doing.
376380
</member>

platform/android/java/lib/src/org/godotengine/godot/Godot.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,9 @@ private boolean onVideoInit() {
274274
// ...add to FrameLayout
275275
containerLayout.addView(editText);
276276

277-
if (!GodotLib.setup(command_line)) {
277+
tts = new GodotTTS(activity);
278+
279+
if (!GodotLib.setup(command_line, tts)) {
278280
Log.e(TAG, "Unable to setup the Godot engine! Aborting...");
279281
alert(R.string.error_engine_setup_message, R.string.text_error_title, this::forceQuit);
280282
return false;
@@ -574,7 +576,6 @@ private void initializeGodot() {
574576
final Activity activity = getActivity();
575577
io = new GodotIO(activity);
576578
netUtils = new GodotNetUtils(activity);
577-
tts = new GodotTTS(activity);
578579
Context context = getContext();
579580
directoryAccessHandler = new DirectoryAccessHandler(context);
580581
FileAccessHandler fileAccessHandler = new FileAccessHandler(context);
@@ -591,8 +592,7 @@ private void initializeGodot() {
591592
netUtils,
592593
directoryAccessHandler,
593594
fileAccessHandler,
594-
use_apk_expansion,
595-
tts);
595+
use_apk_expansion);
596596

597597
result_callback = null;
598598
}

platform/android/java/lib/src/org/godotengine/godot/GodotLib.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ public static native boolean initialize(Activity activity,
6161
GodotNetUtils netUtils,
6262
DirectoryAccessHandler directoryAccessHandler,
6363
FileAccessHandler fileAccessHandler,
64-
boolean use_apk_expansion,
65-
GodotTTS tts);
64+
boolean use_apk_expansion);
6665

6766
/**
6867
* Invoked on the main thread to clean up Godot native layer.
@@ -74,7 +73,7 @@ public static native boolean initialize(Activity activity,
7473
* Invoked on the GL thread to complete setup for the Godot native layer logic.
7574
* @param p_cmdline Command line arguments used to configure Godot native layer components.
7675
*/
77-
public static native boolean setup(String[] p_cmdline);
76+
public static native boolean setup(String[] p_cmdline, GodotTTS tts);
7877

7978
/**
8079
* Invoked on the GL thread when the underlying Android surface has changed size.

platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java

+14-6
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,17 @@ public class GodotTTS extends UtteranceProgressListener {
6262
final private static int EVENT_CANCEL = 2;
6363
final private static int EVENT_BOUNDARY = 3;
6464

65-
final private TextToSpeech synth;
66-
final private LinkedList<GodotUtterance> queue;
65+
final private Activity activity;
66+
private TextToSpeech synth;
67+
private LinkedList<GodotUtterance> queue;
6768
final private Object lock = new Object();
6869
private GodotUtterance lastUtterance;
6970

7071
private boolean speaking;
7172
private boolean paused;
7273

7374
public GodotTTS(Activity p_activity) {
74-
synth = new TextToSpeech(p_activity, null);
75-
queue = new LinkedList<GodotUtterance>();
76-
77-
synth.setOnUtteranceProgressListener(this);
75+
activity = p_activity;
7876
}
7977

8078
private void updateTTS() {
@@ -186,6 +184,16 @@ public void onError(String utteranceId) {
186184
}
187185
}
188186

187+
/**
188+
* Initialize synth and query.
189+
*/
190+
public void init() {
191+
synth = new TextToSpeech(activity, null);
192+
queue = new LinkedList<GodotUtterance>();
193+
194+
synth.setOnUtteranceProgressListener(this);
195+
}
196+
189197
/**
190198
* Adds an utterance to the queue.
191199
*/

platform/android/java_godot_lib_jni.cpp

+4-3
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHei
116116
}
117117
}
118118

119-
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_activity, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion, jobject p_godot_tts) {
119+
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_activity, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion) {
120120
JavaVM *jvm;
121121
env->GetJavaVM(&jvm);
122122

@@ -133,7 +133,6 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv
133133
DirAccessJAndroid::setup(p_directory_access_handler);
134134
FileAccessFilesystemJAndroid::setup(p_file_access_handler);
135135
NetSocketAndroid::setup(p_net_utils);
136-
TTS_Android::setup(p_godot_tts);
137136

138137
os_android = new OS_Android(godot_java, godot_io_java, p_use_apk_expansion);
139138

@@ -144,7 +143,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env
144143
_terminate(env, false);
145144
}
146145

147-
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline) {
146+
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline, jobject p_godot_tts) {
148147
setup_android_thread();
149148

150149
const char **cmdline = nullptr;
@@ -185,6 +184,8 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env
185184
return false;
186185
}
187186

187+
TTS_Android::setup(p_godot_tts);
188+
188189
java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity()));
189190
GDREGISTER_CLASS(JNISingleton);
190191
return true;

platform/android/java_godot_lib_jni.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@
3737
// These functions can be called from within JAVA and are the means by which our JAVA implementation calls back into our C++ code.
3838
// See java/src/org/godotengine/godot/GodotLib.java for the JAVA side of this (yes that's why we have the long names)
3939
extern "C" {
40-
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_activity, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion, jobject p_godot_tts);
40+
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_activity, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion);
4141
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz);
42-
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline);
42+
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline, jobject p_godot_tts);
4343
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height);
4444
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface);
4545
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz);

platform/android/tts_android.cpp

+31-11
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@
3535
#include "string_android.h"
3636
#include "thread_jandroid.h"
3737

38+
bool TTS_Android::initialized = false;
3839
jobject TTS_Android::tts = nullptr;
3940
jclass TTS_Android::cls = nullptr;
4041

42+
jmethodID TTS_Android::_init = nullptr;
4143
jmethodID TTS_Android::_is_speaking = nullptr;
4244
jmethodID TTS_Android::_is_paused = nullptr;
4345
jmethodID TTS_Android::_get_voices = nullptr;
@@ -49,23 +51,34 @@ jmethodID TTS_Android::_stop_speaking = nullptr;
4951
HashMap<int, Char16String> TTS_Android::ids;
5052

5153
void TTS_Android::setup(jobject p_tts) {
52-
JNIEnv *env = get_jni_env();
54+
bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");
55+
if (tts_enabled) {
56+
JNIEnv *env = get_jni_env();
57+
ERR_FAIL_COND(env == nullptr);
58+
59+
tts = env->NewGlobalRef(p_tts);
5360

54-
tts = env->NewGlobalRef(p_tts);
61+
jclass c = env->GetObjectClass(tts);
62+
cls = (jclass)env->NewGlobalRef(c);
5563

56-
jclass c = env->GetObjectClass(tts);
57-
cls = (jclass)env->NewGlobalRef(c);
64+
_init = env->GetMethodID(cls, "init", "()V");
65+
_is_speaking = env->GetMethodID(cls, "isSpeaking", "()Z");
66+
_is_paused = env->GetMethodID(cls, "isPaused", "()Z");
67+
_get_voices = env->GetMethodID(cls, "getVoices", "()[Ljava/lang/String;");
68+
_speak = env->GetMethodID(cls, "speak", "(Ljava/lang/String;Ljava/lang/String;IFFIZ)V");
69+
_pause_speaking = env->GetMethodID(cls, "pauseSpeaking", "()V");
70+
_resume_speaking = env->GetMethodID(cls, "resumeSpeaking", "()V");
71+
_stop_speaking = env->GetMethodID(cls, "stopSpeaking", "()V");
5872

59-
_is_speaking = env->GetMethodID(cls, "isSpeaking", "()Z");
60-
_is_paused = env->GetMethodID(cls, "isPaused", "()Z");
61-
_get_voices = env->GetMethodID(cls, "getVoices", "()[Ljava/lang/String;");
62-
_speak = env->GetMethodID(cls, "speak", "(Ljava/lang/String;Ljava/lang/String;IFFIZ)V");
63-
_pause_speaking = env->GetMethodID(cls, "pauseSpeaking", "()V");
64-
_resume_speaking = env->GetMethodID(cls, "resumeSpeaking", "()V");
65-
_stop_speaking = env->GetMethodID(cls, "stopSpeaking", "()V");
73+
if (_init) {
74+
env->CallVoidMethod(tts, _init);
75+
initialized = true;
76+
}
77+
}
6678
}
6779

6880
void TTS_Android::_java_utterance_callback(int p_event, int p_id, int p_pos) {
81+
ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
6982
if (ids.has(p_id)) {
7083
int pos = 0;
7184
if ((DisplayServer::TTSUtteranceEvent)p_event == DisplayServer::TTS_UTTERANCE_BOUNDARY) {
@@ -86,6 +99,7 @@ void TTS_Android::_java_utterance_callback(int p_event, int p_id, int p_pos) {
8699
}
87100

88101
bool TTS_Android::is_speaking() {
102+
ERR_FAIL_COND_V_MSG(!initialized, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
89103
if (_is_speaking) {
90104
JNIEnv *env = get_jni_env();
91105

@@ -97,6 +111,7 @@ bool TTS_Android::is_speaking() {
97111
}
98112

99113
bool TTS_Android::is_paused() {
114+
ERR_FAIL_COND_V_MSG(!initialized, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
100115
if (_is_paused) {
101116
JNIEnv *env = get_jni_env();
102117

@@ -108,6 +123,7 @@ bool TTS_Android::is_paused() {
108123
}
109124

110125
Array TTS_Android::get_voices() {
126+
ERR_FAIL_COND_V_MSG(!initialized, Array(), "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
111127
Array list;
112128
if (_get_voices) {
113129
JNIEnv *env = get_jni_env();
@@ -135,6 +151,7 @@ Array TTS_Android::get_voices() {
135151
}
136152

137153
void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
154+
ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
138155
if (p_interrupt) {
139156
stop();
140157
}
@@ -157,6 +174,7 @@ void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volum
157174
}
158175

159176
void TTS_Android::pause() {
177+
ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
160178
if (_pause_speaking) {
161179
JNIEnv *env = get_jni_env();
162180

@@ -166,6 +184,7 @@ void TTS_Android::pause() {
166184
}
167185

168186
void TTS_Android::resume() {
187+
ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
169188
if (_resume_speaking) {
170189
JNIEnv *env = get_jni_env();
171190

@@ -175,6 +194,7 @@ void TTS_Android::resume() {
175194
}
176195

177196
void TTS_Android::stop() {
197+
ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
178198
for (const KeyValue<int, Char16String> &E : ids) {
179199
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, E.key);
180200
}

platform/android/tts_android.h

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#ifndef TTS_ANDROID_H
3232
#define TTS_ANDROID_H
3333

34+
#include "core/config/project_settings.h"
3435
#include "core/string/ustring.h"
3536
#include "core/templates/hash_map.h"
3637
#include "core/variant/array.h"
@@ -39,9 +40,11 @@
3940
#include <jni.h>
4041

4142
class TTS_Android {
43+
static bool initialized;
4244
static jobject tts;
4345
static jclass cls;
4446

47+
static jmethodID _init;
4548
static jmethodID _is_speaking;
4649
static jmethodID _is_paused;
4750
static jmethodID _get_voices;

0 commit comments

Comments
 (0)