(feat/TTS) Add Text-to-speech via awesome_wotlk

This commit is contained in:
NoM0Re
2025-08-25 21:41:59 +02:00
committed by GitHub
parent 2257d236a5
commit 2752f0a53c
10 changed files with 248 additions and 48 deletions
+6 -2
View File
@@ -50,13 +50,15 @@ end
local severityLevel = {
info = 0,
sound = 1,
warning = 2,
error = 3
tts = 2,
warning = 3,
error = 4
}
local icons = {
info = [[Interface\FriendsFrame\InformationIcon]],
sound = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\ChatFrame",
tts = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\chatframe-button-icon-TTS",
warning = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\ServicesAtlas",
error = "Interface\\AddOns\\WeakAuras\\Media\\Textures\\HelpIcon-Bug",
}
@@ -64,6 +66,7 @@ local icons = {
local titles = {
info = L["Information"],
sound = L["Sound"],
tts = L["Text To Speech"],
warning = L["Warning"],
error = L["Error"],
}
@@ -114,6 +117,7 @@ function Private.AuraWarnings.FormatWarnings(uid)
result = AddMessages(result, messagePerSeverity["error"], icons["error"], mixedSeverity)
result = AddMessages(result, messagePerSeverity["warning"], icons["warning"], mixedSeverity)
result = AddMessages(result, messagePerSeverity["sound"], icons["sound"], mixedSeverity)
result = AddMessages(result, messagePerSeverity["tts"], icons["tts"], mixedSeverity)
result = AddMessages(result, messagePerSeverity["info"], icons["info"], mixedSeverity)
return icons[maxSeverity], titles[maxSeverity], result
end
+10 -6
View File
@@ -110,7 +110,7 @@ local function formatValueForAssignment(vType, value, pathToCustomFunction, path
return "{1, 1, 1, 1}";
elseif(vType == "chat") then
if (value and type(value) == "table") then
local serialized = string.format("{message_type = %s, message = %s, message_dest = %s, message_dest_isunit = %s, r = %s, g = %s, b = %s, message_custom = %s, message_formaters = %s}",
local serialized = string.format("{message_type = %s, message = %s, message_dest = %s, message_dest_isunit = %s, r = %s, g = %s, b = %s, message_custom = %s, message_formaters = %s, message_voice = %s}",
Private.QuotedString(tostring(value.message_type)), Private.QuotedString(tostring(value.message or "")),
Private.QuotedString(tostring(value.message_dest)),
tostring(value.message_dest_isunit),
@@ -118,16 +118,20 @@ local function formatValueForAssignment(vType, value, pathToCustomFunction, path
type(value.message_color) == "table" and tostring(value.message_color[2] or "1") or "1",
type(value.message_color) == "table" and tostring(value.message_color[3] or "1") or "1",
pathToCustomFunction,
pathToFormatters)
pathToFormatters,
tostring(value.message_voice))
return serialized
end
elseif(vType == "sound") then
if (value and type(value) == "table") then
return string.format("{ sound = %s, sound_channel = %s, sound_path = %s, sound_kit_id = %s, sound_type = %s, %s}",
Private.QuotedString(tostring(value.sound or "")), Private.QuotedString(tostring(value.sound_channel or "")),
Private.QuotedString(tostring(value.sound_path or "")), Private.QuotedString(tostring(value.sound_kit_id or "")),
return string.format("{ sound = %s, sound_channel = %s, sound_path = %s, sound_kit_id = %s, sound_type = %s, %s, %s}",
Private.QuotedString(tostring(value.sound or "")),
Private.QuotedString(tostring(value.sound_channel or "")),
Private.QuotedString(tostring(value.sound_path or "")),
Private.QuotedString(tostring(value.sound_kit_id or "")),
Private.QuotedString(tostring(value.sound_type or "")),
value.sound_repeat and "sound_repeat = " .. tostring(value.sound_repeat) or "nil");
value.sound_repeat and "sound_repeat = " .. tostring(value.sound_repeat) or "nil",
value.sound_fade and "sound_fade = " .. tostring(value.sound_fade) or "nil");
end
elseif(vType == "customcode") then
return string.format("%s", pathToCustomFunction);
+4 -2
View File
@@ -12,7 +12,9 @@ local versionStringFromToc = GetAddOnMetadata("WeakAuras", "Version")
local versionString = "5.20.2 Beta"
-- Year, Month, Day, Hour, Minute, Seconds
local buildTime = "2025".."08".."15".."22".."00".."00"
local isAwesomeEnabled = C_NamePlate and C_NamePlate.GetNamePlateForUnit and true or false
local isAwesomeEnabled = C_VoiceChat and C_VoiceChat.SpeakText and 2 -- TTS available
or C_NamePlate and C_NamePlate.GetNamePlateForUnit and 1 -- Nameplates available
or false
local flavor
if GetRealmName() == "Onyxia" or (GetRealmName() == "Blackrock [PvP only]" and GetExpansionLevel() == 1) then
@@ -29,7 +31,7 @@ WeakAuras.newFeatureString = "|TInterface\\OptionsFrame\\UI-OptionsFrame-NewFeat
WeakAuras.BuildInfo = select(4, GetBuildInfo())
function WeakAuras.IsAwesomeEnabled()
return isAwesomeEnabled or false
return isAwesomeEnabled
end
function WeakAuras.IsCorrectVersion()
+22 -21
View File
@@ -147,19 +147,18 @@ local function SoundRepeatStop(self)
Private.StopProfileSystem("sound");
end
--[[
local function SoundStop(self)
local function SoundStop(self, fadeoutTime)
Private.StartProfileSystem("sound");
if (self.soundHandle) then
StopSound(self.soundHandle);
if (StopSound and self.soundHandle) then
StopSound(self.soundHandle, fadeoutTime);
end
Private.StopProfileSystem("sound");
end
]]
local function SoundPlayHelper(self)
Private.StartProfileSystem("sound");
local options = self.soundOptions;
self.soundHandle = nil;
if (not options or options.sound_type == "Stop") then
Private.StopProfileSystem("sound");
return;
@@ -172,27 +171,27 @@ local function SoundPlayHelper(self)
if (options.sound == " custom") then
local ok, _, handle = pcall(PlaySoundFile, options.sound_path, options.sound_channel or "Master")
--if ok then
--self.soundHandle = handle
--end
if ok then
self.soundHandle = handle
end
elseif (options.sound == " KitID") then
local ok, _, handle = pcall(PlaySound, options.sound_kit_id, options.sound_channel or "Master")
--if ok then
--self.soundHandle = handle
--end
local ok, _, handle = pcall(PlaySound,options.sound_kit_id, options.sound_channel or "Master")
if ok then
self.soundHandle = handle
end
else
local ok, _, handle = pcall(PlaySoundFile, options.sound, options.sound_channel or "Master")
--if ok then
--self.soundHandle = handle
--end
if ok then
self.soundHandle = handle
end
end
Private.StopProfileSystem("sound");
end
local function hasSound(options)
--if options.sound_type == "Stop" then
--return true
--end
if options.sound_type == "Stop" then
return true
end
if (options.sound == " custom") then
if (options.sound_path and options.sound_path ~= "") then
return true
@@ -219,14 +218,15 @@ local function SoundPlay(self, options)
return
end
--self:SoundStop();
local fadeoutTime = options.sound_type == "Stop" and options.sound_fade and options.sound_fade * 1000 or 0
self:SoundStop(fadeoutTime);
self:SoundRepeatStop();
self.soundOptions = options;
SoundPlayHelper(self);
local loop = options.do_loop or options.sound_type == "Loop";
if (loop and options.sound_repeat) then
if (loop and options.sound_repeat and options.sound_repeat < Private.maxTimerDuration) then
self.soundRepeatTimer = WeakAuras.timer:ScheduleRepeatingTimer(SoundPlayHelper, options.sound_repeat, self);
end
Private.StopProfileSystem("sound");
@@ -236,7 +236,7 @@ local function SendChat(self, options)
if (not options or WeakAuras.IsOptionsOpen()) then
return
end
Private.HandleChatAction(options.message_type, options.message, options.message_dest, options.message_dest_isunit, options.message_channel, options.r, options.g, options.b, self, options.message_custom, nil, options.message_formaters);
Private.HandleChatAction(options.message_type, options.message, options.message_dest, options.message_dest_isunit, options.message_channel, options.r, options.g, options.b, self, options.message_custom, nil, options.message_formaters, options.message_voice);
end
local function RunCode(self, func)
@@ -715,6 +715,7 @@ Private.regionPrototype.AnchorSubRegion = AnchorSubRegion
function Private.regionPrototype.create(region)
local defaultsForRegion = Private.regionTypes[region.regionType] and Private.regionTypes[region.regionType].default;
region.SoundPlay = SoundPlay;
region.SoundStop = SoundStop;
region.SoundRepeatStop = SoundRepeatStop;
region.SendChat = SendChat;
region.RunCode = RunCode;
+20
View File
@@ -2855,6 +2855,26 @@ Private.send_chat_message_types = {
ERROR = L["Error Frame"]
}
Private.tts_voices = {}
if WeakAuras.IsAwesomeEnabled() then
Private.send_chat_message_types.TTS = L["Text-to-speech"]
local function updateTts()
wipe(Private.tts_voices)
for i, voiceInfo in pairs(C_VoiceChat.GetTtsVoices()) do
Private.tts_voices[voiceInfo.voiceID] = voiceInfo.name
end
end
updateTts()
local TtsUpdateFrame = CreateFrame("FRAME")
TtsUpdateFrame:RegisterEvent("VOICE_CHAT_TTS_VOICES_UPDATE")
TtsUpdateFrame:SetScript("OnEvent", updateTts)
end
Private.group_aura_name_info_types = {
aura = L["Aura Name"],
players = L["Player(s) Affected"],
+73 -14
View File
@@ -45,6 +45,8 @@ WeakAuras.LGT = LibStub("LibGroupTalents-1.0") or {
GetUnitRole = function(_) end
}
Private.maxTimerDuration = 604800; -- A week, in seconds
Private.watched_trigger_events = {}
-- The worlds simplest callback system.
@@ -1048,6 +1050,8 @@ local function tooltip_draw()
tooltip:Show();
end
WeakAuras.GenerateTooltip = tooltip_draw;
local colorFrame = CreateFrame("Frame");
Private.frames["LDB Icon Recoloring"] = colorFrame;
@@ -2721,53 +2725,89 @@ local oldDataStub2 = {
}
function Private.UpdateSoundIcon(data)
local function anySoundCondition()
local function testConditions()
local sound, tts
if data.conditions then
for _, condition in ipairs(data.conditions) do
for changeIndex, change in ipairs(condition.changes) do
if change.property == "sound" then
return true
end
sound = true
end
if change.property == "chat" and change.value and change.value.message_type == "TTS" then
tts = true
end
if sound and tts then break end
end
end
end
return sound, tts
end
local soundCondition, ttsCondition = testConditions()
-- sound
if data.actions.start.do_sound or data.actions.finish.do_sound then
Private.AuraWarnings.UpdateWarning(data.uid, "sound_action", "sound", L["This aura plays a sound via an action."])
else
Private.AuraWarnings.UpdateWarning(data.uid, "sound_action")
end
if anySoundCondition() then
if soundCondition then
Private.AuraWarnings.UpdateWarning(data.uid, "sound_condition", "sound", L["This aura plays a sound via a condition."])
else
Private.AuraWarnings.UpdateWarning(data.uid, "sound_condition")
end
-- tts
if WeakAuras.IsAwesomeEnabled() ~= 2 then return end
if (data.actions.start.do_message and data.actions.start.message_type == "TTS")
or (data.actions.finish.do_message and data.actions.finish.message_type == "TTS")
then
Private.AuraWarnings.UpdateWarning(data.uid, "tts_action", "tts", L["This aura plays a Text To Speech via an action."])
else
Private.AuraWarnings.UpdateWarning(data.uid, "tts_action")
end
if ttsCondition then
Private.AuraWarnings.UpdateWarning(data.uid, "tts_condition", "tts", L["This aura plays a Text To Speech via a condition."])
else
Private.AuraWarnings.UpdateWarning(data.uid, "tts_condition")
end
end
function Private.ClearSounds(uid)
function Private.ClearSounds(uid, severity)
local data = Private.GetDataByUID(uid)
for child in Private.TraverseLeafsOrAura(data) do
local changed = false
-- Conditions
if child.conditions then
for _, condition in ipairs(child.conditions) do
for changeIndex = #condition.changes, 1, -1 do
local change = condition.changes[changeIndex]
if change.property == "sound" then
if change.property == "sound" and severity == "sound" then
tremove(condition.changes, changeIndex)
changed = true
elseif change.property == "chat" and change.value and change.value.message_type == "TTS" and severity == "tts" then
tremove(condition.changes, changeIndex)
changed = true
end
end
end
end
-- Actions
if child.actions.start.do_sound or child.actions.finish.do_sound then
if severity == "sound" and (child.actions.start.do_sound or child.actions.finish.do_sound) then
child.actions.start.do_sound = false
child.actions.finish.do_sound = false
changed = true
elseif severity == "tts" then
if child.actions.start.do_message and child.actions.start.message_type == "TTS" then
child.actions.start.do_message = false
changed = true
end
if child.actions.finish.do_message and child.actions.finish.message_type == "TTS" then
child.actions.finish.do_message = false
changed = true
end
end
if changed then
WeakAuras.Add(child)
@@ -3220,13 +3260,28 @@ function Private.ReleaseClone(id, cloneId, regionType)
end
end
function Private.HandleChatAction(message_type, message, message_dest, message_dest_isunit, message_channel, r, g, b, region, customFunc, when, formatters)
function Private.HandleChatAction(message_type, message, message_dest, message_dest_isunit, message_channel, r, g, b, region, customFunc, when, formatters, voice)
local useHiddenStates = when == "finish"
if (message:find('%%')) then
message = Private.ReplacePlaceHolders(message, region, customFunc, useHiddenStates, formatters);
end
if(message_type == "PRINT") then
DEFAULT_CHAT_FRAME:AddMessage(message, r or 1, g or 1, b or 1);
elseif message_type == "TTS" then
if WeakAuras.IsAwesomeEnabled() == 2 then
local validVoice = voice and Private.tts_voices[voice]
if not Private.SquelchingActions() then
pcall(function()
C_VoiceChat.SpeakText(
validVoice and voice or next(Private.tts_voices) or 0,
message,
1,
C_TTSSettings and C_TTSSettings.GetSpeechRate() or 0,
C_TTSSettings and C_TTSSettings.GetSpeechVolume() or 100
);
end)
end
end
elseif message_type == "ERROR" then
UIErrorsFrame:AddMessage(message, r or 1, g or 1, b or 1)
elseif(message_type == "COMBAT") then
@@ -3239,8 +3294,6 @@ function Private.HandleChatAction(message_type, message, message_dest, message_d
message_dest = Private.ReplacePlaceHolders(message_dest, region, customFunc, useHiddenStates, formatters);
end
if message_dest_isunit == true then
-- send to server like retail doesnt work here
-- message_dest = GetUnitName(message_dest, true)
message_dest = UnitName(message_dest)
end
pcall(function() SendChatMessage(message, "WHISPER", nil, message_dest) end);
@@ -3504,7 +3557,14 @@ function Private.PerformActions(data, when, region)
if(actions.do_message and actions.message_type and actions.message) then
local customFunc = Private.customActionsFunctions[data.id][when .. "_message"];
Private.HandleChatAction(actions.message_type, actions.message, actions.message_dest, actions.message_dest_isunit, actions.message_channel, actions.r, actions.g, actions.b, region, customFunc, when, formatters);
Private.HandleChatAction(actions.message_type, actions.message, actions.message_dest, actions.message_dest_isunit, actions.message_channel, actions.r, actions.g, actions.b, region, customFunc, when, formatters, actions.message_tts_voice);
end
if (actions.stop_sound) then
if (region.SoundStop) then
local fadeoutTime = actions.do_sound_fade and actions.stop_sound_fade and actions.stop_sound_fade * 1000 or 0
region:SoundStop(fadeoutTime);
end
end
if(actions.do_sound and actions.sound) then
@@ -4077,7 +4137,6 @@ end
local threads = {
frame = CreateFrame("Frame"),
size = 0,
---@type table<string, threadPriority>
prios = {},
pools = {
urgent = {},
@@ -431,7 +431,9 @@ local function IsParentRecursive(needle, parent)
end
local tabsForWarning = {
tts_condition = "conditions",
sound_condition = "conditions",
tts_action = "action",
sound_action = "action",
spammy_event_warning = "trigger"
}
@@ -1425,16 +1427,20 @@ local methods = {
end,
["UpdateWarning"] = function(self)
local warnings = OptionsPrivate.Private.AuraWarnings.GetAllWarnings(self.data.uid)
local warningTypes = {"info", "sound", "warning", "error"}
local warningTypes = {"info", "sound", "tts", "warning", "error"}
for _, key in ipairs(warningTypes) do
self:ClearStatusIcon(key)
end
if warnings then
for severity, warning in pairs(warnings) do
local onClick
if severity == "sound" then
if severity == "sound" or severity == "tts" then
local soundText = L["Show Sound Setting"]
local removeText = L["Remove All Sounds"]
if severity == "tts" then
soundText = L["Show Text To Speech Setting"]
removeText = L["Remove All Text To Speech"]
end
onClick = function()
local menu = {
{
@@ -1446,7 +1452,7 @@ local methods = {
{
text = removeText,
func = function()
OptionsPrivate.Private.ClearSounds(self.data.uid)
OptionsPrivate.Private.ClearSounds(self.data.uid, severity)
end
}
}
+56
View File
@@ -208,6 +208,16 @@ function OptionsPrivate.GetActionOptions(data)
return data.actions.start.message_type ~= "WHISPER"
end
},
start_message_tts_voice = {
type = "select",
width = WeakAuras.doubleWidth,
name = L["Voice"],
order = 3.2,
disabled = function() return WeakAuras.IsAwesomeEnabled() ~= 2 or not data.actions.start.do_message end,
hidden = function() return WeakAuras.IsAwesomeEnabled() ~= 2 or data.actions.start.message_type ~= "TTS" end,
values = OptionsPrivate.Private.tts_voices,
desc = L["Available Voices are system specific"]
},
start_message = {
type = "input",
width = WeakAuras.doubleWidth - 0.15,
@@ -713,6 +723,16 @@ function OptionsPrivate.GetActionOptions(data)
return data.actions.finish.message_type ~= "WHISPER"
end
},
finish_message_tts_voice = {
type = "select",
width = WeakAuras.doubleWidth,
name = L["Voice"],
order = 23.2,
disabled = function() return WeakAuras.IsAwesomeEnabled() ~= 2 or not data.actions.finish.do_message end,
hidden = function() return WeakAuras.IsAwesomeEnabled() ~= 2 or data.actions.finish.message_type ~= "TTS" end,
values = OptionsPrivate.Private.tts_voices,
desc = L["Available Voices are system specific"]
},
finish_message = {
type = "input",
width = WeakAuras.doubleWidth - 0.15,
@@ -794,6 +814,42 @@ function OptionsPrivate.GetActionOptions(data)
hidden = function() return data.actions.finish.sound ~= " KitID" end,
disabled = function() return not data.actions.finish.do_sound end
},
finish_stop_sound = {
type = "toggle",
width = WeakAuras.doubleWidth,
name = L["Stop Sound"],
order = 29.1,
hidden = function() return not StopSound end,
disabled = function() return not StopSound end,
},
finish_do_sound_fade = {
type = "toggle",
width = WeakAuras.normalWidth,
name = L["Fadeout Sound"],
order = 29.2,
hidden = function() return not StopSound end,
disabled = function() return not StopSound or not data.actions.finish.stop_sound end,
},
finish_stop_sound_fade = {
type = "range",
control = "WeakAurasSpinBox",
width = WeakAuras.normalWidth,
name = L["Fadeout Time (seconds)"],
order = 29.3,
hidden = function() return not StopSound or not data.actions.finish.do_sound_fade end,
disabled = function() return not StopSound or not data.actions.finish.stop_sound end,
min = 0,
softMax = 10,
bigStep = 1,
},
finish_stop_sound_fade_space = {
type = "description",
width = WeakAuras.doubleWidth,
order = 29.4,
name = "",
hidden = function() return not StopSound end,
disabled = function() return not StopSound end,
},
finish_do_glow = {
type = "toggle",
width = WeakAuras.normalWidth,
+49 -1
View File
@@ -846,6 +846,35 @@ local function addControlsForChange(args, order, data, conditionVariable, totalA
}
order = order + 1;
if StopSound then
args["condition" .. i .. "value" .. j .. "sound_fade"] = {
type = "range",
control = "WeakAurasSpinBox",
width = WeakAuras.normalWidth,
min = 0,
softMax = 10,
bigStep = 1,
name = blueIfNoValue2(data, conditions[i].changes[j], "value", "sound_fade", L["Fadeout Time (seconds)"], L["Fadeout Time (seconds)"]),
desc = descIfNoValue2(data, conditions[i].changes[j], "value", "sound_fade", propertyType),
order = order,
get = function()
return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.sound_fade;
end,
set = setValueComplex("sound_fade"),
disabled = function() return not anySoundType("Stop") end,
hidden = function() return not (anySoundType("Stop")) end
}
order = order + 1;
args["condition" .. i .. "value" .. j .. "sound_fade_space"] = {
type = "description",
width = WeakAuras.normalWidth,
name = "",
order = order,
hidden = function() return not (anySoundType("Stop")) end
}
order = order + 1;
end
elseif (propertyType == "chat") then
args["condition" .. i .. "value" .. j .. "message type"] = {
@@ -987,6 +1016,25 @@ local function addControlsForChange(args, order, data, conditionVariable, totalA
}
order = order + 1;
if WeakAuras.IsAwesomeEnabled() == 2 then
args["condition" .. i .. "value" .. j .. "message voice"] = {
type = "select",
width = WeakAuras.doubleWidth,
name = blueIfNoValue2(data, conditions[i].changes[j], "value", "message_voice", L["Voice"], L["Voice"]),
desc = (descIfNoValue2(data, conditions[i].changes[j], "value", "message_voice", propertyType, OptionsPrivate.Private.tts_voices) or "") .. "\n" .. L["Available Voices are system specific"],
values = OptionsPrivate.Private.tts_voices,
order = order,
get = function()
return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.message_voice;
end,
set = setValueComplex("message_voice"),
hidden = function()
return not anyMessageType("TTS");
end,
}
order = order + 1;
end
local message_getter = function()
return type(conditions[i].changes[j].value) == "table" and conditions[i].changes[j].value.message;
end
@@ -2871,7 +2919,7 @@ local function SubPropertiesForChange(change)
"glow_scale", "glow_border"
}
elseif change.property == "chat" then
local result = { "message_type", "message_dest", "message_channel", "message_color", "message", "custom" }
local result = { "message_type", "message_dest", "message_channel", "message_color", "message", "custom", "message_voice" }
local input = change.value and change.value.message
if input then
local getter = function(key)