Files
Iamgoofball 33824ac7c1 TTS 3.0: Blips Rework, Radio TTS, Unknown Languages are Blips now (#95369)
## About The Pull Request

Re-did blips to be significantly nicer sounding and way better. Alien
speech you don't understand now comes through as Blips. If you have TTS
entirely disabled, you won't hear blips. Blips also now include more
customization options, and a dedicated Blips preview button by clicking
the leaf icon.

<img width="1749" height="354" alt="ApplicationFrameHost_5nKJEvaNV1"
src="https://github.com/user-attachments/assets/ef6c6b61-7c22-4a87-94c9-be50e89c65bb"
/>


https://github.com/user-attachments/assets/8cab7e55-6370-4e4e-99ba-ea2475569453

Radio TTS has been implemented. By default, you will hear all TTS over
the radio, but will not hear yourself over the radio. You can configure
this in Game Settings.


https://github.com/user-attachments/assets/94a44d84-17a9-4e6e-ac5c-3b800b11c159

<img width="743" height="193" alt="chrome_MOIcXaC2gh"
src="https://github.com/user-attachments/assets/907379b7-0689-486c-b29c-9be0a2259b05"
/>

The new TTS stack is located at
https://github.com/Iamgoofball/tgtts-qwen3 and is AGPLv3 licensed. The
new blips design was inspired by
https://github.com/joshxviii/animalese-typing.

The configuration to disable TTS on whispering has been removed, as it
is no longer needed and also interferes with radio TTS functioning
properly.

The Tram now utilizes TTS, and has had a general audio tune-up.


https://github.com/user-attachments/assets/f532d002-939c-43a1-95d0-0f0c43048997

TTS audio is now 3D and in space, see attached.


https://github.com/user-attachments/assets/b00df49a-9d1d-4b27-a8a0-d103099f5c7e

## Why It's Good For The Game

Quality of life improvements to the TTS system, long overdue. Also, TGMC
can migrate to this new method of doing radio audio so they aren't
sending double the TTS requests anymore.

## Changelog

🆑
add: Added support for Radio TTS. You will now hear players over the
radio via the TTS system.
add: Configure this in Game Options under the Sound tab.
sound: Re-did blips to be significantly nicer sounding and way better. 
sound: Alien speech you don't understand now comes through as Blips. If
you have TTS entirely disabled, you won't hear blips.
sound: The Tram and Computers now utilize the TTS system. Configs have
been added to set a consistent voice.
sound: TTS audio now utilizes 3D audio; you can now walk away from
people saying stupid shit and it gets quieter.
sound: Blips also now include more customization options, and a
dedicated Blips preview button by clicking the leaf icon.
del: The configuration to disable TTS on whispering has been removed, as
it is no longer needed and also interferes with radio TTS functioning
properly.
/🆑

---------

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Lucy <lucy@absolucy.moe>
Co-authored-by: Aleksej Komarov <stylemistake@gmail.com>
2026-06-15 18:54:24 -04:00

127 lines
3.9 KiB
Plaintext

#define INCLUSIVE_MODE 1
#define EXCLUSIVE_MODE 2
#define RECOGNIZER_MODE 3
#define VOICE_SENSOR_MODE 4
/obj/item/assembly/voice
name = "voice analyzer"
desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated."
icon_state = "voice"
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*5, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.5)
assembly_behavior = ASSEMBLY_TOGGLEABLE_INPUT
verb_say = "beeps"
verb_ask = "beeps"
verb_exclaim = "beeps"
var/listening = FALSE
/// The activation message is tracked using this var.
var/recorded = ""
var/mode = INCLUSIVE_MODE
var/static/list/modes = list(
"inclusive",
"exclusive",
"recognizer",
"voice sensor",
)
drop_sound = 'sound/items/handling/component_drop.ogg'
pickup_sound = 'sound/items/handling/component_pickup.ogg'
/obj/item/assembly/voice/Initialize(mapload)
. = ..()
become_hearing_sensitive(ROUNDSTART_TRAIT)
/obj/item/assembly/voice/examine(mob/user)
. = ..()
. += span_notice("Use a multitool to swap between \"inclusive\", \"exclusive\", \"recognizer\", and \"voice sensor\" mode.")
/obj/item/assembly/voice/Hear(atom/movable/speaker, message_language, raw_message, radio_freq, radio_freq_name, radio_freq_color, list/spans, list/message_mods = list(), message_range)
. = ..()
if(message_mods[WHISPER_MODE] || message_mods[MODE_RELAY]) //Too quiet lad
return FALSE
if(speaker == src)
return FALSE
// raw_message can contain multiple spaces between words etc which are not seen in chat due to HTML rendering
// this means if the teller records a message with e.g. double spaces or tabs, other people will not be able to trigger the sensor since they don't know how to perform the same combination
raw_message = htmlrendertext(raw_message)
if(listening && !radio_freq)
record_speech(speaker, raw_message, message_language)
else
if(check_activation(speaker, raw_message))
send_pulse()
return HEAR_HEARD | HEAR_UNDERSTOOD
/obj/item/assembly/voice/proc/record_speech(atom/movable/speaker, raw_message, datum/language/message_language)
switch(mode)
if(INCLUSIVE_MODE)
recorded = raw_message
listening = FALSE
say("Activation message is '[recorded]'.", sanitize = FALSE, language = message_language)
if(EXCLUSIVE_MODE)
recorded = raw_message
listening = FALSE
say("Activation message is '[recorded]'.", sanitize = FALSE, language = message_language)
if(RECOGNIZER_MODE)
recorded = speaker.get_voice()
listening = FALSE
say("Your voice pattern is saved.", language = message_language)
if(VOICE_SENSOR_MODE)
if(length(raw_message))
send_pulse()
/obj/item/assembly/voice/proc/check_activation(atom/movable/speaker, raw_message)
if (recorded == "")
return FALSE
switch(mode)
if(INCLUSIVE_MODE)
if(findtext(raw_message, recorded))
return TRUE
if(EXCLUSIVE_MODE)
if(raw_message == recorded)
return TRUE
if(RECOGNIZER_MODE)
if(speaker.get_voice() == recorded)
return TRUE
if(VOICE_SENSOR_MODE)
if(length(raw_message))
return TRUE
return FALSE
/obj/item/assembly/voice/proc/send_pulse()
visible_message("clicks.", visible_message_flags = EMOTE_MESSAGE)
playsound(src, 'sound/effects/whirthunk.ogg', 30)
addtimer(CALLBACK(src, PROC_REF(pulse)), 2 SECONDS)
/obj/item/assembly/voice/multitool_act(mob/living/user, obj/item/I)
..()
mode %= modes.len
mode++
to_chat(user, span_notice("You set [src] into [modes[mode]] mode."))
listening = FALSE
recorded = ""
return TRUE
/obj/item/assembly/voice/activate()
if(!secured || holder)
return FALSE
listening = !listening
say("[listening ? "Now" : "No longer"] recording input.")
return TRUE
/obj/item/assembly/voice/attack_self(mob/user)
if(!user)
return FALSE
activate()
return TRUE
/obj/item/assembly/voice/toggle_secure()
. = ..()
listening = FALSE
#undef INCLUSIVE_MODE
#undef EXCLUSIVE_MODE
#undef RECOGNIZER_MODE
#undef VOICE_SENSOR_MODE