Skip to content

Commit

Permalink
Add the ability to use blips as your character's voice (tgstation#77640)
Browse files Browse the repository at this point in the history
## About The Pull Request

- Adds the ability to force your TTS voice to always be blips, default
is still normal speech
- Probably fixes a bug with silicon voices not transferring properly?
There was an entire argument missing from tts_request/New
- Fixes problems I got from the Docker build not working at all. Only
thing interesting is `--no-cache-dir` which was to fix the container
running out of RAM even though I literally just got 64GB of RAM today

Got permission from @optimumtact as alternative to being able to disable
your voice, CC @Iamgoofball

## Why It's Good For The Game

I spent two hours trying to find a voice that didn't make me
uncomfortable to use and that was even in the realm of being the voice I
would actually want people to hear. Characters still have distinct
"voices" because the blips are chopped together from the voice itself

## Changelog

:cl:
add: You can now set your voice to just blips.
/:cl:
  • Loading branch information
Mothblocks authored Aug 16, 2023
1 parent b05d0a7 commit 05b5213
Show file tree
Hide file tree
Showing 13 changed files with 109 additions and 54 deletions.
6 changes: 6 additions & 0 deletions code/__DEFINES/tts.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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"
65 changes: 29 additions & 36 deletions code/controllers/subsystem/tts.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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()
2 changes: 2 additions & 0 deletions code/datums/http.dm
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
return TRUE

/datum/http_request/proc/into_response()
RETURN_TYPE(/datum/http_response)

var/datum/http_response/R = new()

try
Expand Down
4 changes: 4 additions & 0 deletions code/game/atoms_movable.dm
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,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

Expand Down
2 changes: 1 addition & 1 deletion code/game/say.dm
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,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.
Expand Down
5 changes: 5 additions & 0 deletions code/modules/antagonists/changeling/changeling.dm
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,7 @@
user.grad_color = LAZYLISTDUPLICATE(chosen_profile.grad_color)
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)

Expand Down Expand Up @@ -912,6 +913,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()
qdel(dna)
Expand Down Expand Up @@ -952,6 +956,7 @@
new_profile.grad_color = LAZYLISTDUPLICATE(grad_color)
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()
Expand Down
6 changes: 4 additions & 2 deletions code/modules/client/preferences/middleware/tts.dm
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@
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)
if(!COOLDOWN_FINISHED(src, tts_test_cooldown))
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
19 changes: 19 additions & 0 deletions code/modules/client/preferences/voice.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
15 changes: 14 additions & 1 deletion code/modules/mob/living/living_say.dm
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,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)
Expand Down
6 changes: 6 additions & 0 deletions code/modules/mob/living/silicon/login.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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 ..()


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
10 changes: 5 additions & 5 deletions tools/tts/tts-api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
18 changes: 9 additions & 9 deletions tools/tts/tts/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 05b5213

Please sign in to comment.