Skip to content

Commit

Permalink
Add audio/general/text_to_speech project setting to enable/disable …
Browse files Browse the repository at this point in the history
…TTS.
  • Loading branch information
bruvzg committed May 18, 2023
1 parent 91f3cdf commit 5b9984b
Show file tree
Hide file tree
Showing 16 changed files with 133 additions and 63 deletions.
1 change: 1 addition & 0 deletions core/config/project_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor", false);

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

Expand Down
9 changes: 9 additions & 0 deletions doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,7 @@
- [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.
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.
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
</description>
</method>
<method name="tts_get_voices_for_language" qualifiers="const">
Expand All @@ -1025,34 +1026,39 @@
<description>
Returns an [PackedStringArray] of voice identifiers for the [param language].
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
</description>
</method>
<method name="tts_is_paused" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the synthesizer is in a paused state.
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
</description>
</method>
<method name="tts_is_speaking" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the synthesizer is generating speech, or have utterance waiting in the queue.
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
</description>
</method>
<method name="tts_pause">
<return type="void" />
<description>
Puts the synthesizer into a paused state.
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
</description>
</method>
<method name="tts_resume">
<return type="void" />
<description>
Resumes the synthesizer if it was paused.
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
</description>
</method>
<method name="tts_set_utterance_callback">
Expand All @@ -1065,6 +1071,7 @@
- [constant TTS_UTTERANCE_BOUNDARY] callable's method should take two [int] parameters, the index of the character and the utterance ID.
[b]Note:[/b] The granularity of the boundary callbacks is engine dependent.
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
</description>
</method>
<method name="tts_speak">
Expand All @@ -1086,13 +1093,15 @@
[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].
[b]Note:[/b] The granularity of pitch, rate, and volume is engine and voice dependent. Values may be truncated.
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
</description>
</method>
<method name="tts_stop">
<return type="void" />
<description>
Stops synthesis in progress and removes all utterances from the queue.
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
</description>
</method>
<method name="virtual_keyboard_get_height" qualifiers="const">
Expand Down
4 changes: 4 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@
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.
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.
</member>
<member name="audio/general/text_to_speech" type="bool" setter="" getter="" default="false">
If [code]true[/code], text-to-speech support is enabled, see [method DisplayServer.tts_get_voices] and [method DisplayServer.tts_speak].
[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.
</member>
<member name="audio/video/video_delay_compensation_ms" type="int" setter="" getter="" default="0">
Setting to hardcode audio delay when playing video. Best to leave this untouched unless you know what you are doing.
</member>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,9 @@ private boolean onVideoInit() {
// ...add to FrameLayout
containerLayout.addView(editText);

if (!GodotLib.setup(command_line)) {
tts = new GodotTTS(activity);

if (!GodotLib.setup(command_line, tts)) {
Log.e(TAG, "Unable to setup the Godot engine! Aborting...");
alert(R.string.error_engine_setup_message, R.string.text_error_title, this::forceQuit);
return false;
Expand Down Expand Up @@ -574,7 +576,6 @@ private void initializeGodot() {
final Activity activity = getActivity();
io = new GodotIO(activity);
netUtils = new GodotNetUtils(activity);
tts = new GodotTTS(activity);
Context context = getContext();
directoryAccessHandler = new DirectoryAccessHandler(context);
FileAccessHandler fileAccessHandler = new FileAccessHandler(context);
Expand All @@ -591,8 +592,7 @@ private void initializeGodot() {
netUtils,
directoryAccessHandler,
fileAccessHandler,
use_apk_expansion,
tts);
use_apk_expansion);

result_callback = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ public static native boolean initialize(Activity activity,
GodotNetUtils netUtils,
DirectoryAccessHandler directoryAccessHandler,
FileAccessHandler fileAccessHandler,
boolean use_apk_expansion,
GodotTTS tts);
boolean use_apk_expansion);

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

/**
* Invoked on the GL thread when the underlying Android surface has changed size.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,17 @@ public class GodotTTS extends UtteranceProgressListener {
final private static int EVENT_CANCEL = 2;
final private static int EVENT_BOUNDARY = 3;

final private TextToSpeech synth;
final private LinkedList<GodotUtterance> queue;
final private Activity activity;
private TextToSpeech synth;
private LinkedList<GodotUtterance> queue;
final private Object lock = new Object();
private GodotUtterance lastUtterance;

private boolean speaking;
private boolean paused;

public GodotTTS(Activity p_activity) {
synth = new TextToSpeech(p_activity, null);
queue = new LinkedList<GodotUtterance>();

synth.setOnUtteranceProgressListener(this);
activity = p_activity;
}

private void updateTTS() {
Expand Down Expand Up @@ -186,6 +184,16 @@ public void onError(String utteranceId) {
}
}

/**
* Initialize synth and query.
*/
public void init() {
synth = new TextToSpeech(activity, null);
queue = new LinkedList<GodotUtterance>();

synth.setOnUtteranceProgressListener(this);
}

/**
* Adds an utterance to the queue.
*/
Expand Down
7 changes: 4 additions & 3 deletions platform/android/java_godot_lib_jni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHei
}
}

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) {
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) {
JavaVM *jvm;
env->GetJavaVM(&jvm);

Expand All @@ -133,7 +133,6 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv
DirAccessJAndroid::setup(p_directory_access_handler);
FileAccessFilesystemJAndroid::setup(p_file_access_handler);
NetSocketAndroid::setup(p_net_utils);
TTS_Android::setup(p_godot_tts);

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

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

JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline) {
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline, jobject p_godot_tts) {
setup_android_thread();

const char **cmdline = nullptr;
Expand Down Expand Up @@ -185,6 +184,8 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env
return false;
}

TTS_Android::setup(p_godot_tts);

java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity()));
GDREGISTER_CLASS(JNISingleton);
return true;
Expand Down
4 changes: 2 additions & 2 deletions platform/android/java_godot_lib_jni.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
// These functions can be called from within JAVA and are the means by which our JAVA implementation calls back into our C++ code.
// See java/src/org/godotengine/godot/GodotLib.java for the JAVA side of this (yes that's why we have the long names)
extern "C" {
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);
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);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz);
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline);
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline, jobject p_godot_tts);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface);
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz);
Expand Down
42 changes: 31 additions & 11 deletions platform/android/tts_android.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@
#include "string_android.h"
#include "thread_jandroid.h"

bool TTS_Android::initialized = false;
jobject TTS_Android::tts = nullptr;
jclass TTS_Android::cls = nullptr;

jmethodID TTS_Android::_init = nullptr;
jmethodID TTS_Android::_is_speaking = nullptr;
jmethodID TTS_Android::_is_paused = nullptr;
jmethodID TTS_Android::_get_voices = nullptr;
Expand All @@ -49,23 +51,34 @@ jmethodID TTS_Android::_stop_speaking = nullptr;
HashMap<int, Char16String> TTS_Android::ids;

void TTS_Android::setup(jobject p_tts) {
JNIEnv *env = get_jni_env();
bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");
if (tts_enabled) {
JNIEnv *env = get_jni_env();
ERR_FAIL_COND(env == nullptr);

tts = env->NewGlobalRef(p_tts);

tts = env->NewGlobalRef(p_tts);
jclass c = env->GetObjectClass(tts);
cls = (jclass)env->NewGlobalRef(c);

jclass c = env->GetObjectClass(tts);
cls = (jclass)env->NewGlobalRef(c);
_init = env->GetMethodID(cls, "init", "()V");
_is_speaking = env->GetMethodID(cls, "isSpeaking", "()Z");
_is_paused = env->GetMethodID(cls, "isPaused", "()Z");
_get_voices = env->GetMethodID(cls, "getVoices", "()[Ljava/lang/String;");
_speak = env->GetMethodID(cls, "speak", "(Ljava/lang/String;Ljava/lang/String;IFFIZ)V");
_pause_speaking = env->GetMethodID(cls, "pauseSpeaking", "()V");
_resume_speaking = env->GetMethodID(cls, "resumeSpeaking", "()V");
_stop_speaking = env->GetMethodID(cls, "stopSpeaking", "()V");

_is_speaking = env->GetMethodID(cls, "isSpeaking", "()Z");
_is_paused = env->GetMethodID(cls, "isPaused", "()Z");
_get_voices = env->GetMethodID(cls, "getVoices", "()[Ljava/lang/String;");
_speak = env->GetMethodID(cls, "speak", "(Ljava/lang/String;Ljava/lang/String;IFFIZ)V");
_pause_speaking = env->GetMethodID(cls, "pauseSpeaking", "()V");
_resume_speaking = env->GetMethodID(cls, "resumeSpeaking", "()V");
_stop_speaking = env->GetMethodID(cls, "stopSpeaking", "()V");
if (_init) {
env->CallVoidMethod(tts, _init);
initialized = true;
}
}
}

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

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

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

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

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

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

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) {
ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
if (p_interrupt) {
stop();
}
Expand All @@ -157,6 +174,7 @@ void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volum
}

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

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

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

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

void TTS_Android::stop() {
ERR_FAIL_COND_MSG(!initialized, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
for (const KeyValue<int, Char16String> &E : ids) {
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, E.key);
}
Expand Down
3 changes: 3 additions & 0 deletions platform/android/tts_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#ifndef TTS_ANDROID_H
#define TTS_ANDROID_H

#include "core/config/project_settings.h"
#include "core/string/ustring.h"
#include "core/templates/hash_map.h"
#include "core/variant/array.h"
Expand All @@ -39,9 +40,11 @@
#include <jni.h>

class TTS_Android {
static bool initialized;
static jobject tts;
static jclass cls;

static jmethodID _init;
static jmethodID _is_speaking;
static jmethodID _is_paused;
static jmethodID _get_voices;
Expand Down
Loading

0 comments on commit 5b9984b

Please sign in to comment.