diff --git a/code/__DEFINES/tts.dm b/code/__DEFINES/tts.dm index cca1b5db000..fd88016408c 100644 --- a/code/__DEFINES/tts.dm +++ b/code/__DEFINES/tts.dm @@ -4,3 +4,7 @@ #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" +///TTS filter to activate start/stop radio clicks on speech. +#define TTS_FILTER_RADIO "radio" +///TTS filter to activate a silicon effect on speech. +#define TTS_FILTER_SILICON "silicon" diff --git a/code/controllers/subsystem/tts.dm b/code/controllers/subsystem/tts.dm index 712168b5027..b9cb85ea78a 100644 --- a/code/controllers/subsystem/tts.dm +++ b/code/controllers/subsystem/tts.dm @@ -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, special_filters = "") if(!tts_enabled) return @@ -277,7 +277,7 @@ SUBSYSTEM_DEF(tts) var/shell_scrubbed_input = tts_speech_filter(message) shell_scrubbed_input = copytext(shell_scrubbed_input, 1, 300) - var/identifier = "[sha1(speaker + filter + num2text(pitch) + num2text(silicon) + shell_scrubbed_input)].[world.time]" + var/identifier = "[sha1(speaker + filter + num2text(pitch) + special_filters + shell_scrubbed_input)].[world.time]" if(!(speaker in available_speakers)) return @@ -288,9 +288,9 @@ SUBSYSTEM_DEF(tts) var/datum/http_request/request_blips = new() var/file_name = "tmp/tts/[identifier].ogg" 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) + request.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/tts_http_url)]/tts?voice=[speaker]&identifier=[identifier]&filter=[url_encode(filter)]&pitch=[pitch]&special_filters=[url_encode(special_filters)]", 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]&special_filters=[url_encode(special_filters)]", 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) var/list/player_queued_tts_messages = queued_tts_messages[target] if(!player_queued_tts_messages) player_queued_tts_messages = list() @@ -342,8 +342,6 @@ SUBSYSTEM_DEF(tts) var/use_blips = FALSE /// What's the pitch adjustment? var/pitch = 0 - /// Are we using the silicon vocal effect on this? - 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) diff --git a/code/datums/records/record.dm b/code/datums/records/record.dm index b13646c64c4..debb70a813c 100644 --- a/code/datums/records/record.dm +++ b/code/datums/records/record.dm @@ -24,6 +24,8 @@ var/species /// The character's ID trim var/trim + /// The character's voice, if they have one. + var/voice /datum/record/New( age = 18, @@ -37,6 +39,7 @@ rank = "Unassigned", species = "Human", trim = "Unassigned", + voice = "?????", ) src.age = age src.blood_type = blood_type diff --git a/code/game/say.dm b/code/game/say.dm index 528c81c656d..e2f3934dac3 100644 --- a/code/game/say.dm +++ b/code/game/say.dm @@ -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) /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/client/preferences/middleware/tts.dm b/code/modules/client/preferences/middleware/tts.dm index 71b7b977f4b..4d3ee3261bd 100644 --- a/code/modules/client/preferences/middleware/tts.dm +++ b/code/modules/client/preferences/middleware/tts.dm @@ -23,5 +23,5 @@ var/speaker = preferences.read_preference(/datum/preference/choiced/voice) var/pitch = preferences.read_preference(/datum/preference/numeric/tts_voice_pitch) 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, special_filters = TTS_FILTER_SILICON, local = TRUE) return TRUE diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm index dfcc3060c9f..ad296d30356 100644 --- a/code/modules/clothing/masks/_masks.dm +++ b/code/modules/clothing/masks/_masks.dm @@ -12,6 +12,10 @@ var/adjusted_flags = null ///Did we install a filtering cloth? var/has_filter = FALSE + /// If defined, what voice should we override with if TTS is active? + var/voice_override + /// If set to true, activates the radio effect on TTS. Used for sec hailers, but other masks can utilize it for their own vocal effect. + var/use_radio_beeps_tts = FALSE /obj/item/clothing/mask/attack_self(mob/user) if((clothing_flags & VOICEBOX_TOGGLABLE)) diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index 8374e423d74..a74d8bfca40 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -28,7 +28,7 @@ GLOBAL_LIST_INIT(clown_mask_options, list( var/has_fov = TRUE ///Cigarette in the mask var/obj/item/clothing/mask/cigarette/cig - + voice_filter = "lowpass=f=750,volume=2" /datum/armor/mask_gas bio = 100 @@ -274,6 +274,7 @@ GLOBAL_LIST_INIT(clown_mask_options, list( dog_fashion = /datum/dog_fashion/head/clown has_fov = FALSE var/list/clownmask_designs = list() + voice_filter = null // performer masks expect to be talked through /obj/item/clothing/mask/gas/clown_hat/plasmaman starting_filter_type = /obj/item/gas_filter/plasmaman diff --git a/code/modules/clothing/masks/hailer.dm b/code/modules/clothing/masks/hailer.dm index af1d3975645..64de19b95aa 100644 --- a/code/modules/clothing/masks/hailer.dm +++ b/code/modules/clothing/masks/hailer.dm @@ -68,6 +68,8 @@ GLOBAL_LIST_INIT(hailer_phrases, list( var/recent_uses = 0 ///Whether the hailer is emagged or not var/safety = TRUE + voice_filter = @{"[0:a] asetrate=%SAMPLE_RATE%*0.7,aresample=16000,atempo=1/0.7,lowshelf=g=-20:f=500,highpass=f=500,aphaser=in_gain=1:out_gain=1:delay=3.0:decay=0.4:speed=0.5:type=t [out]; [out]atempo=1.2,volume=15dB [final]; anoisesrc=a=0.01:d=60 [noise]; [final][noise] amix=duration=shortest"} + use_radio_beeps_tts = TRUE /obj/item/clothing/mask/gas/sechailer/plasmaman starting_filter_type = /obj/item/gas_filter/plasmaman diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm index ed17ad42b3a..50aacd2fdf0 100644 --- a/code/modules/mob/living/living_say.dm +++ b/code/modules/mob/living/living_say.dm @@ -389,13 +389,29 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( tts_message_to_use = message_raw var/list/filter = list() + var/list/special_filter = list() + var/voice_to_use = voice + var/use_radio = FALSE if(length(voice_filter) > 0) filter += voice_filter if(length(tts_filter) > 0) filter += tts_filter.Join(",") + if(ishuman(src)) + var/mob/living/carbon/human/human_speaker = src + if(human_speaker.wear_mask) + var/obj/item/clothing/mask/worn_mask = human_speaker.wear_mask + if(worn_mask.voice_override) + voice_to_use = worn_mask.voice_override + if(worn_mask.voice_filter) + filter += worn_mask.voice_filter + use_radio = worn_mask.use_radio_beeps_tts + if(use_radio) + special_filter += TTS_FILTER_RADIO + if(issilicon(src)) + special_filter += TTS_FILTER_SILICON - 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_to_use, filter.Join(","), listened, message_range = message_range, pitch = pitch, special_filters = special_filter.Join("|")) 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/modular_computers/file_system/programs/records.dm b/code/modules/modular_computers/file_system/programs/records.dm index 960702d608c..9b5617364c0 100644 --- a/code/modules/modular_computers/file_system/programs/records.dm +++ b/code/modules/modular_computers/file_system/programs/records.dm @@ -45,6 +45,7 @@ current_record["rank"] = person.rank current_record["species"] = person.species current_record["wanted"] = person.wanted_status + current_record["voice"] = person.voice all_records += list(current_record) if("medical") diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm index 3a23d8a6302..94f9abafa14 100644 --- a/code/modules/surgery/organs/internal/tongue/_tongue.dm +++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm @@ -181,7 +181,7 @@ languages_native = list(/datum/language/draconic) liked_foodtypes = GORE | MEAT | SEAFOOD | NUTS | BUGS disliked_foodtypes = GRAIN | DAIRY | CLOTH | GROSS - + voice_filter = @{"[0:a] asplit [out0][out2]; [out0] asetrate=%SAMPLE_RATE%*0.9,aresample=%SAMPLE_RATE%,atempo=1/0.9,aformat=channel_layouts=mono,volume=0.2 [p0]; [out2] asetrate=%SAMPLE_RATE%*1.1,aresample=%SAMPLE_RATE%,atempo=1/1.1,aformat=channel_layouts=mono,volume=0.2[p2]; [p0][0][p2] amix=inputs=3"} /obj/item/organ/internal/tongue/lizard/modify_speech(datum/source, list/speech_args) var/static/regex/lizard_hiss = new("s+", "g") var/static/regex/lizard_hiSS = new("S+", "g") @@ -475,7 +475,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) say_mod = "hisses" taste_sensitivity = 10 // LIZARDS ARE ALIENS CONFIRMED modifies_speech = TRUE // not really, they just hiss - + voice_filter = @{"[0:a] asplit [out0][out2]; [out0] asetrate=%SAMPLE_RATE%*0.8,aresample=%SAMPLE_RATE%,atempo=1/0.8,aformat=channel_layouts=mono [p0]; [out2] asetrate=%SAMPLE_RATE%*1.2,aresample=%SAMPLE_RATE%,atempo=1/1.2,aformat=channel_layouts=mono[p2]; [p0][0][p2] amix=inputs=3"} // Aliens can only speak alien and a few other languages. /obj/item/organ/internal/tongue/alien/get_possible_languages() return list( @@ -579,6 +579,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) toxic_foodtypes = NONE //no food is particularly toxic to ethereals attack_verb_continuous = list("shocks", "jolts", "zaps") attack_verb_simple = list("shock", "jolt", "zap") + voice_filter = @{"[0:a] asplit [out0][out2]; [out0] asetrate=%SAMPLE_RATE%*0.99,aresample=%SAMPLE_RATE%,volume=0.3 [p0]; [p0][out2] amix=inputs=2"} // Ethereal tongues can speak all default + voltaic /obj/item/organ/internal/tongue/ethereal/get_possible_languages() diff --git a/tgui/packages/tgui/interfaces/SecurityRecords/RecordView.tsx b/tgui/packages/tgui/interfaces/SecurityRecords/RecordView.tsx index 9967efbb2eb..ee3dc799679 100644 --- a/tgui/packages/tgui/interfaces/SecurityRecords/RecordView.tsx +++ b/tgui/packages/tgui/interfaces/SecurityRecords/RecordView.tsx @@ -56,6 +56,7 @@ const RecordInfo = (props, context) => { rank, species, wanted_status, + voice, } = foundRecord; const hasValidCrimes = !!crimes.find((crime) => !!crime.valid); @@ -168,6 +169,9 @@ const RecordInfo = (props, context) => { text={fingerprint} /> + + +