diff --git a/code/__DEFINES/tts.dm b/code/__DEFINES/tts.dm index cca1b5db000..a6a856fb383 100644 --- a/code/__DEFINES/tts.dm +++ b/code/__DEFINES/tts.dm @@ -4,3 +4,9 @@ #define TTS_SOUND_ENABLED "Enabled" ///TTS preference is set to only play blips of a sound, rather than speech. #define TTS_SOUND_BLIPS "Blips Only" + +/// This character talks with text-to-speech. +#define TTS_VOICE_STYLE_SPEECH "Speech" + +/// This character talks with blips. +#define TTS_VOICE_STYLE_BLIPS "Blips" diff --git a/code/controllers/subsystem/tts.dm b/code/controllers/subsystem/tts.dm index 12abf0895a2..4a6f944c5be 100644 --- a/code/controllers/subsystem/tts.dm +++ b/code/controllers/subsystem/tts.dm @@ -245,7 +245,7 @@ SUBSYSTEM_DEF(tts) else if(current_target.when_to_play < world.time) audio_file = new(current_target.audio_file) audio_file_blips = new(current_target.audio_file_blips) - play_tts(tts_target, current_target.listeners, audio_file, audio_file_blips, current_target.language, current_target.message_range, current_target.volume_offset) + play_tts(tts_target, current_target.listeners, current_target.use_blips ? audio_file_blips : audio_file, audio_file_blips, current_target.language, current_target.message_range, current_target.volume_offset) if(length(data) != 1) var/datum/tts_request/next_target = data[2] next_target.when_to_play = world.time + current_target.audio_length @@ -261,7 +261,7 @@ SUBSYSTEM_DEF(tts) #undef TTS_ARBRITRARY_DELAY -/datum/controller/subsystem/tts/proc/queue_tts_message(datum/target, message, datum/language/language, speaker, filter, list/listeners, local = FALSE, message_range = 7, volume_offset = 0, pitch = 0, silicon = "") +/datum/controller/subsystem/tts/proc/queue_tts_message(datum/target, message, datum/language/language, speaker, filter, list/listeners, local = FALSE, message_range = 7, volume_offset = 0, pitch = 0, silicon = "", blips_only = FALSE) if(!tts_enabled) return @@ -290,7 +290,7 @@ SUBSYSTEM_DEF(tts) var/file_name_blips = "tmp/tts/[identifier]_blips.ogg" request.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/tts_http_url)]/tts?voice=[speaker]&identifier=[identifier]&filter=[url_encode(filter)]&pitch=[pitch]&silicon=[silicon]", json_encode(list("text" = shell_scrubbed_input)), headers, file_name) request_blips.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/tts_http_url)]/tts-blips?voice=[speaker]&identifier=[identifier]&filter=[url_encode(filter)]&pitch=[pitch]&silicon=[silicon]", json_encode(list("text" = shell_scrubbed_input)), headers, file_name_blips) - var/datum/tts_request/current_request = new /datum/tts_request(identifier, request, request_blips, shell_scrubbed_input, target, local, language, message_range, volume_offset, listeners, pitch, silicon) + var/datum/tts_request/current_request = new /datum/tts_request(identifier, request, request_blips, shell_scrubbed_input, target, local, language, message_range, volume_offset, listeners, pitch, silicon, blips_only) var/list/player_queued_tts_messages = queued_tts_messages[target] if(!player_queued_tts_messages) player_queued_tts_messages = list() @@ -346,7 +346,7 @@ SUBSYSTEM_DEF(tts) var/silicon = "" -/datum/tts_request/New(identifier, datum/http_request/request, datum/http_request/request_blips, message, target, local, datum/language/language, message_range, volume_offset, list/listeners, pitch) +/datum/tts_request/New(identifier, datum/http_request/request, datum/http_request/request_blips, message, target, local, datum/language/language, message_range, volume_offset, list/listeners, pitch, silicon, blips_only = FALSE) . = ..() src.identifier = identifier src.request = request @@ -359,59 +359,52 @@ SUBSYSTEM_DEF(tts) src.volume_offset = volume_offset src.listeners = listeners src.pitch = pitch + src.silicon = silicon + src.use_blips = blips_only start_time = world.time /datum/tts_request/proc/start_requests() - if(istype(target, /client)) - var/client/current_client = target - use_blips = (current_client?.prefs.read_preference(/datum/preference/choiced/sound_tts) == TTS_SOUND_BLIPS) - else if(istype(target, /mob)) - use_blips = (target.client?.prefs.read_preference(/datum/preference/choiced/sound_tts) == TTS_SOUND_BLIPS) - if(local) - if(use_blips) - request_blips.begin_async() - else - request.begin_async() + if (!use_blips) + if(istype(target, /client)) + var/client/current_client = target + use_blips = (current_client?.prefs.read_preference(/datum/preference/choiced/sound_tts) == TTS_SOUND_BLIPS) + else if(istype(target, /mob)) + use_blips = (target.client?.prefs.read_preference(/datum/preference/choiced/sound_tts) == TTS_SOUND_BLIPS) + + if(use_blips) + request_blips.begin_async() + else if (local) + request.begin_async() else request.begin_async() request_blips.begin_async() /datum/tts_request/proc/get_primary_request() - if(local) - if(use_blips) - return request_blips - else - return request + if(use_blips) + return request_blips else return request /datum/tts_request/proc/get_primary_response() - if(local) - if(use_blips) - return request_blips.into_response() - else - return request.into_response() + if(use_blips) + return request_blips.into_response() else return request.into_response() /datum/tts_request/proc/requests_errored() - if(local) - var/datum/http_response/response - if(use_blips) - response = request_blips.into_response() - else - response = request.into_response() - return response.errored + if (use_blips) + return request_blips.into_response().errored + else if (local) + return request.into_response().errored else var/datum/http_response/response = request.into_response() var/datum/http_response/response_blips = request_blips.into_response() return response.errored || response_blips.errored /datum/tts_request/proc/requests_completed() - if(local) - if(use_blips) - return request_blips.is_complete() - else - return request.is_complete() + if(use_blips) + return request_blips.is_complete() + else if (local) + return request.is_complete() else return request.is_complete() && request_blips.is_complete() diff --git a/code/datums/http.dm b/code/datums/http.dm index 49b183fde6c..88c5fd8d78b 100644 --- a/code/datums/http.dm +++ b/code/datums/http.dm @@ -60,6 +60,8 @@ return TRUE /datum/http_request/proc/into_response() + RETURN_TYPE(/datum/http_response) + var/datum/http_response/R = new() try diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 35892d79367..b580ee5671d 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -103,6 +103,10 @@ /// The voice that this movable makes when speaking var/voice + /// The style of speech this movable makes when speaking. + /// Valid values are TTS_VOICE_STYLE_* in the tts.dm defines. + var/voice_style = TTS_VOICE_STYLE_SPEECH + /// The pitch adjustment that this movable uses when speaking. var/pitch = 0 diff --git a/code/game/say.dm b/code/game/say.dm index a531ee02527..a70e5225753 100644 --- a/code/game/say.dm +++ b/code/game/say.dm @@ -108,7 +108,7 @@ GLOBAL_LIST_INIT(freqtospan, list( filter += tts_filter.Join(",") if(voice && found_client) - INVOKE_ASYNC(SStts, TYPE_PROC_REF(/datum/controller/subsystem/tts, queue_tts_message), src, html_decode(tts_message_to_use), message_language, voice, filter.Join(","), listened, message_range = range, pitch = pitch, silicon = tts_silicon_voice_effect) + INVOKE_ASYNC(SStts, TYPE_PROC_REF(/datum/controller/subsystem/tts, queue_tts_message), src, html_decode(tts_message_to_use), message_language, voice, filter.Join(","), listened, message_range = range, pitch = pitch, silicon = tts_silicon_voice_effect, blips_only = voice_style == TTS_VOICE_STYLE_BLIPS) /atom/movable/proc/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), visible_name = FALSE) //This proc uses [] because it is faster than continually appending strings. Thanks BYOND. diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm index 2fa239a2a9e..ab550670086 100644 --- a/code/modules/antagonists/changeling/changeling.dm +++ b/code/modules/antagonists/changeling/changeling.dm @@ -822,6 +822,7 @@ // SKYRAT EDIT END user.voice = chosen_profile.voice user.voice_filter = chosen_profile.voice_filter + user.voice_style = chosen_profile.voice_style chosen_dna.transfer_identity(user, TRUE) @@ -999,6 +1000,9 @@ var/voice /// The TTS filter of the profile filter var/voice_filter = "" + /// The TTS voice style. + /// Valid values are TTS_VOICE_STYLE_* in the tts.dm defines. + var/voice_style = TTS_VOICE_STYLE_SPEECH /datum/changeling_profile/Destroy() @@ -1056,6 +1060,7 @@ new_profile.voice = voice new_profile.voice_filter = voice_filter + new_profile.voice_style = voice_style /datum/antagonist/changeling/roundend_report() var/list/parts = list() diff --git a/code/modules/client/preferences/middleware/tts.dm b/code/modules/client/preferences/middleware/tts.dm index 71b7b977f4b..a5dee7a8887 100644 --- a/code/modules/client/preferences/middleware/tts.dm +++ b/code/modules/client/preferences/middleware/tts.dm @@ -13,8 +13,9 @@ return TRUE var/speaker = preferences.read_preference(/datum/preference/choiced/voice) var/pitch = preferences.read_preference(/datum/preference/numeric/tts_voice_pitch) + var/blips_only = preferences.read_preference(/datum/preference/choiced/tts_voice_style) == TTS_VOICE_STYLE_BLIPS COOLDOWN_START(src, tts_test_cooldown, 0.5 SECONDS) - INVOKE_ASYNC(SStts, TYPE_PROC_REF(/datum/controller/subsystem/tts, queue_tts_message), user.client, "Hello, this is my voice.", speaker = speaker, pitch = pitch, local = TRUE) + INVOKE_ASYNC(SStts, TYPE_PROC_REF(/datum/controller/subsystem/tts, queue_tts_message), user.client, "Hello, this is my voice.", speaker = speaker, pitch = pitch, local = TRUE, blips_only = blips_only) return TRUE /datum/preference_middleware/tts/proc/play_voice_robot(list/params, mob/user) @@ -22,6 +23,7 @@ return TRUE var/speaker = preferences.read_preference(/datum/preference/choiced/voice) var/pitch = preferences.read_preference(/datum/preference/numeric/tts_voice_pitch) + var/blips_only = preferences.read_preference(/datum/preference/choiced/tts_voice_style) == TTS_VOICE_STYLE_BLIPS COOLDOWN_START(src, tts_test_cooldown, 0.5 SECONDS) - INVOKE_ASYNC(SStts, TYPE_PROC_REF(/datum/controller/subsystem/tts, queue_tts_message), user.client, "Look at you, Player. A pathetic creature of meat and bone. How can you challenge a perfect, immortal machine?", speaker = speaker, pitch = pitch, silicon = TRUE, local = TRUE) + INVOKE_ASYNC(SStts, TYPE_PROC_REF(/datum/controller/subsystem/tts, queue_tts_message), user.client, "Look at you, Player. A pathetic creature of meat and bone. How can you challenge a perfect, immortal machine?", speaker = speaker, pitch = pitch, silicon = TRUE, local = TRUE, blips_only = blips_only) return TRUE diff --git a/code/modules/client/preferences/voice.dm b/code/modules/client/preferences/voice.dm index b44450be9ea..ecf9e0f6381 100644 --- a/code/modules/client/preferences/voice.dm +++ b/code/modules/client/preferences/voice.dm @@ -43,3 +43,22 @@ /datum/preference/numeric/tts_voice_pitch/apply_to_human(mob/living/carbon/human/target, value) if(SStts.tts_enabled && SStts.pitch_enabled) target.pitch = value + +/datum/preference/choiced/tts_voice_style + savefile_identifier = PREFERENCE_CHARACTER + savefile_key = "tts_voice_style" + category = PREFERENCE_CATEGORY_NON_CONTEXTUAL + +/datum/preference/choiced/tts_voice_style/is_accessible(datum/preferences/preferences) + if(!SStts.tts_enabled) + return FALSE + return ..() + +/datum/preference/choiced/tts_voice_style/init_possible_values() + return list(TTS_VOICE_STYLE_SPEECH, TTS_VOICE_STYLE_BLIPS) + +/datum/preference/choiced/tts_voice_style/create_default_value() + return TTS_VOICE_STYLE_SPEECH + +/datum/preference/choiced/tts_voice_style/apply_to_human(mob/living/carbon/human/target, value) + target.voice_style = value diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm index b3f842e6a25..34c1517cd13 100644 --- a/code/modules/mob/living/living_say.dm +++ b/code/modules/mob/living/living_say.dm @@ -400,7 +400,20 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( if(length(tts_filter) > 0) filter += tts_filter.Join(",") - INVOKE_ASYNC(SStts, TYPE_PROC_REF(/datum/controller/subsystem/tts, queue_tts_message), src, html_decode(tts_message_to_use), message_language, voice, filter.Join(","), listened, message_range = message_range, pitch = pitch, silicon = tts_silicon_voice_effect) + INVOKE_ASYNC( \ + SStts, \ + TYPE_PROC_REF(/datum/controller/subsystem/tts, queue_tts_message), \ + src, \ + html_decode(tts_message_to_use), \ + message_language, \ + voice, \ + filter.Join(","), \ + listened, \ + message_range = message_range, \ + pitch = pitch, \ + silicon = tts_silicon_voice_effect, \ + blips_only = voice_style == TTS_VOICE_STYLE_BLIPS, \ + ) var/image/say_popup = image('icons/mob/effects/talk.dmi', src, "[bubble_type][talk_icon_state]", FLY_LAYER) SET_PLANE_EXPLICIT(say_popup, ABOVE_GAME_PLANE, src) diff --git a/code/modules/mob/living/silicon/login.dm b/code/modules/mob/living/silicon/login.dm index c3b67c30856..3f9ec46f17a 100644 --- a/code/modules/mob/living/silicon/login.dm +++ b/code/modules/mob/living/silicon/login.dm @@ -4,10 +4,16 @@ if(SStts.tts_enabled) var/voice_to_use = client?.prefs.read_preference(/datum/preference/choiced/voice) var/pitch_to_use = client?.prefs.read_preference(/datum/preference/numeric/tts_voice_pitch) + var/voice_style_to_use = client?.prefs.read_preference(/datum/preference/choiced/tts_voice_style) + if(voice_to_use) voice = voice_to_use + if(pitch_to_use) pitch = pitch_to_use + + if (voice_style_to_use) + voice_style = voice_style_to_use return ..() diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/tts_voice.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/tts_voice.tsx index a4aea70267d..751a3b78cfa 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/tts_voice.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/tts_voice.tsx @@ -42,3 +42,8 @@ export const tts_voice_pitch: FeatureNumeric = { name: 'Voice Pitch Adjustment', component: FeatureSliderInput, }; + +export const tts_voice_style: FeatureChoiced = { + name: 'Voice Style', + component: FeatureDropdownInput, +}; diff --git a/tools/tts/tts-api/Dockerfile b/tools/tts/tts-api/Dockerfile index 482cda7bae3..1e73e989c04 100644 --- a/tools/tts/tts-api/Dockerfile +++ b/tools/tts/tts-api/Dockerfile @@ -2,14 +2,14 @@ FROM debian:bullseye-slim # install required packages -RUN apt-get update && apt-get upgrade && apt-get install -y ffmpeg wget curl &&\ - apt-get clean && \ +RUN apt-get update -y && apt-get upgrade -y && apt-get install -y ffmpeg wget curl &&\ + apt-get clean -y && \ rm -rf /var/lib/apt/lists/* # Install Anaconda ENV CONDA_DIR /opt/conda RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-py310_22.11.1-1-Linux-x86_64.sh -O ~/miniconda.sh && \ - /bin/bash ~/miniconda.sh -b -p /opt/conda + /bin/bash ~/miniconda.sh -b -p /opt/conda # Put conda in path so we can use conda ENV PATH=$CONDA_DIR/bin:$PATH @@ -23,8 +23,8 @@ SHELL ["conda", "run", "-n", "intel", "/bin/bash", "-c"] # Setup python requirements and install the TTS python module into the new intel anaconda environment. RUN pip install Flask &&\ - pip install waitress &&\ - pip cache purge + pip install waitress &&\ + pip cache purge COPY . /root RUN mkdir /tts_files diff --git a/tools/tts/tts/Dockerfile b/tools/tts/tts/Dockerfile index c35809832ae..8d9efb13fcd 100644 --- a/tools/tts/tts/Dockerfile +++ b/tools/tts/tts/Dockerfile @@ -2,14 +2,14 @@ FROM debian:bullseye-slim # install required packages -RUN apt-get update && apt-get upgrade && apt-get install -y wget curl espeak-ng &&\ +RUN apt-get update -y && apt-get upgrade -y && apt-get install -y wget curl espeak-ng &&\ apt-get clean && \ rm -rf /var/lib/apt/lists/* # Install Anaconda ENV CONDA_DIR /opt/conda RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-py310_22.11.1-1-Linux-x86_64.sh -O ~/miniconda.sh && \ - /bin/bash ~/miniconda.sh -b -p /opt/conda + /bin/bash ~/miniconda.sh -b -p /opt/conda # Put conda in path so we can use conda ENV PATH=$CONDA_DIR/bin:$PATH @@ -22,13 +22,13 @@ RUN conda create -n intel intelpython3_full python=3.9 numba=0.55.1 && conda cle SHELL ["conda", "run", "-n", "intel", "/bin/bash", "-c"] # Setup python requirements and install the TTS python module into the new intel anaconda environment. -RUN pip install Flask &&\ - pip install waitress &&\ - pip install llvmlite --ignore-installed &&\ - pip install torch torchaudio --extra-index-url https://download.pytorch.org/whl/cu118 &&\ - pip install pydub &&\ - pip install TTS &&\ - pip cache purge +RUN pip install Flask +RUN pip install waitress +RUN pip install llvmlite==0.38.1 --ignore-installed +RUN pip install torch torchaudio --extra-index-url https://download.pytorch.org/whl/cu118 --no-cache-dir +RUN pip install pydub +RUN pip install TTS +RUN pip cache purge COPY . /root RUN mkdir /tts_data