diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 91333138d..96353d11d 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -59,7 +59,7 @@ -- @field #number frequency Radio frequency in MHz. -- @field #number modulation Radio modulation 0=AM or 1=FM. -- @field #number power Radio power in Watts. Default 100 W. --- @field Core.RadioQueue#RADIOQUEUE radioqueue Radio queue for broadcasing messages. +-- @field Sound.RadioQueue#RADIOQUEUE radioqueue Radio queue for broadcasing messages. -- @field #string soundpath Path to sound files. -- @field #string relayunitname Name of the radio relay unit. -- @field #table towerfrequency Table with tower frequencies. @@ -2102,6 +2102,14 @@ end -- @param #string Text Report text. function ATIS:onafterReport(From, Event, To, Text) self:T(self.lid..string.format("Report:\n%s", Text)) + + -- Remove line breaks + local text=string.gsub(Text, "[\r\n]", "") + env.info("FF: "..text) + + local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, Modulation) + msrs:PlayText(text) + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index 3e6afcd51..1f5e9cd07 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -35,6 +35,7 @@ -- @field #table numbers Table of number transmission parameters. -- @field #boolean checking Scheduler is checking the radio queue. -- @field #boolean schedonce Call ScheduleOnce instead of normal scheduler. +-- @field Sound.SRS#MSRS msrs Moose SRS class. -- @extends Core.Base#BASE RADIOQUEUE = { ClassName = "RADIOQUEUE", @@ -69,6 +70,8 @@ RADIOQUEUE = { -- @field #boolean isplaying If true, transmission is currently playing. -- @field #number Tplay Mission time (abs) in seconds when the transmission should be played. -- @field #number interval Interval in seconds before next transmission. +-- @field Sound.SoundFile#SOUNDFILE soundfile Sound file object to play via SRS. +-- @field Sound.SoundFile#SOUNDTEXT soundtext Sound TTS object to play via SRS. --- Create a new RADIOQUEUE object for a given radio frequency/modulation. @@ -170,6 +173,15 @@ function RADIOQUEUE:SetRadioPower(power) return self end +--- Set SRS. +-- @param #RADIOQUEUE self +-- @param #string PathToSRS Path to SRS. +-- @return #RADIOQUEUE self The RADIOQUEUE object. +function RADIOQUEUE:SetSRS(PathToSRS) + self.msrs=MSRS:New(PathToSRS, self.frequency/1000000, self.modulation) + return self +end + --- Set parameters of a digit. -- @param #RADIOQUEUE self -- @param #number digit The digit 0-9. @@ -230,7 +242,7 @@ end -- @param #number interval Interval in seconds after the last transmission finished. -- @param #string subtitle Subtitle of the transmission. -- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec. --- @return #RADIOQUEUE self The RADIOQUEUE object. +-- @return #RADIOQUEUE.Transmission Radio transmission table. function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration) env.info("FF new transmission.") @@ -271,7 +283,7 @@ function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, -- Add transmission to queue. self:AddTransmission(transmission) - return self + return transmission end --- Create a new transmission and add it to the radio queue. @@ -280,10 +292,10 @@ end -- @param #number tstart Start time (abs) seconds. Default now. -- @param #number interval Interval in seconds after the last transmission finished. -- @return #RADIOQUEUE self -function RADIOQUEUE:AddSoundfile(soundfile, tstart, interval) +function RADIOQUEUE:AddSoundFile(soundfile, tstart, interval) env.info(string.format("FF add soundfile: name=%s%s", soundfile:GetPath(), soundfile:GetFileName())) - self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration) - + local transmission=self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration) + transmission.soundfile=soundfile return self end @@ -295,19 +307,9 @@ end -- @param #number interval Interval between the next call. -- @return #number Duration of the call in seconds. function RADIOQUEUE:Number2Transmission(number, delay, interval) - - --- Split string into characters. - local function _split(str) - local chars={} - for i=1,#str do - local c=str:sub(i,i) - table.insert(chars, c) - end - return chars - end -- Split string into characters. - local numbers=UTILS.GetCharacters(number) --l_split(number) + local numbers=UTILS.GetCharacters(number) local wait=0 for i=1,#numbers do @@ -340,6 +342,11 @@ end -- @param #RADIOQUEUE.Transmission transmission The transmission. function RADIOQUEUE:Broadcast(transmission) + if (transmission.soundfile or transmission.soundtext) and self.msrs then + self:_BroadcastSRS(transmission) + return + end + -- Get unit sending the transmission. local sender=self:_GetRadioSender() @@ -431,6 +438,19 @@ function RADIOQUEUE:Broadcast(transmission) end end +--- Broadcast radio message. +-- @param #RADIOQUEUE self +-- @param #RADIOQUEUE.Transmission transmission The transmission. +function RADIOQUEUE:_BroadcastSRS(transmission) + + if transmission.soundfile then + self.msrs:PlaySoundFile(transmission.soundfile) + elseif transmission.soundtext then + self.msrs:PlaySoundText(transmission.soundtext) + end + +end + --- Start checking the radio queue. -- @param #RADIOQUEUE self -- @param #number delay Delay in seconds before checking. diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 35ad41c96..c4fc31c15 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -1,4 +1,4 @@ ---- **Sound** - Simple Radio Standalone Integration +--- **Sound** - Simple Radio Standalone (SRS) Integration. -- -- === -- @@ -41,7 +41,10 @@ -- @field #string name Name. Default "DCS-STTS". -- @field #number volume Volume between 0 (min) and 1 (max). Default 1. -- @field #string culture Culture. Default "en-GB". --- @field #string path Path to the SRS exe. +-- @field #string gender Gender. Default "female". +-- @field #string voice Specifc voce. +-- @field Core.Point#COORDINATE coordinate Coordinate from where the transmission is send. +-- @field #string path Path to the SRS exe. This includes the final slash "/". -- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -62,16 +65,17 @@ MSRS = { ClassName = "MSRS", lid = nil, + port = 5002, + name = "MSRS", frequencies = {}, modulations = {}, coalition = 0, - speed = 1, - port = 5002, - name = "DCS-STTS", - volume = 1, - culture = "en-GB", gender = "female", + culture = "en-GB", voice = nil, + volume = 1, + speed = 1, + coordinate = nil, latitude = nil, longitude = nil, altitude = nil, @@ -79,7 +83,7 @@ MSRS = { --- MSRS class version. -- @field #string version -MSRS.version="0.0.1" +MSRS.version="0.0.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -94,8 +98,8 @@ MSRS.version="0.0.1" --- Create a new MSRS object. -- @param #MSRS self -- @param #string PathToSRS Path to the directory, where SRS is located. --- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. --- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. +-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a #table of multiple frequencies. +-- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a #table of multiple modulations. -- @return #MSRS self function MSRS:New(PathToSRS, Frequency, Modulation) @@ -107,9 +111,11 @@ function MSRS:New(PathToSRS, Frequency, Modulation) local self=BASE:Inherit(self, BASE:New()) -- #MSRS self:SetPath(PathToSRS) + self:SetPort() self:SetFrequencies(Frequency) self:SetModulations(Modulation) - + self:SetGender() + return self end @@ -117,7 +123,6 @@ end -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- Set path to SRS install directory. More precisely, path to where the DCS- -- @param #MSRS self -- @param #string Path Path to the directory, where the sound file is located. @@ -125,6 +130,7 @@ end function MSRS:SetPath(Path) if Path==nil then + self:E("ERROR: No path to SRS directory specified!") return nil end @@ -138,6 +144,8 @@ function MSRS:SetPath(Path) n=n+1 end + self.path=self.path.."/" + self:I(string.format("SRS path=%s", self:GetPath())) return self @@ -145,11 +153,26 @@ end --- Get path to SRS directory. -- @param #MSRS self --- @return #string Path to the directory. +-- @return #string Path to the directory. This includes the final slash "/". function MSRS:GetPath() return self.path end +--- Set port. +-- @param #MSRS self +-- @param #number Port Port. Default 5002. +-- @return #MSRS self +function MSRS:SetPort(Port) + self.port=Port or 5002 +end + +--- Get port. +-- @param #MSRS self +-- @return #number Port. +function MSRS:GetPort() + return self.port +end + --- Set frequencies. -- @param #MSRS self -- @param #table Frequencies Frequencies in MHz. Can also be given as a #number if only one frequency should be used. @@ -166,6 +189,13 @@ function MSRS:SetFrequencies(Frequencies) return self end +--- Get frequencies. +-- @param #MSRS self +-- @param #table Frequencies in MHz. +function MSRS:GetFrequencies() + return self.frequencies +end + --- Set modulations. -- @param #MSRS self @@ -183,25 +213,73 @@ function MSRS:SetModulations(Modulations) return self end +--- Get modulations. +-- @param #MSRS self +-- @param #table Modulations. +function MSRS:GetModulations() + return self.modulations +end + +--- Set gender. +-- @param #MSRS self +-- @param #string Gender Gender: "male" or "female" (default). +-- @return #MSRS self +function MSRS:SetGender(Gender) + + Gender=Gender or "female" + + Gender=Gender:lower() + + self.gender=Gender + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Transmission Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Play sound file (ogg or mp3) via SRS. -- @param #MSRS self -- @param Sound.SoundFile#SOUNDFILE Soundfile Sound file to play. -- @param #number Delay Delay in seconds, before the sound file is played. -- @return #MSRS self -function MSRS:PlaySoundfile(Soundfile, Delay) +function MSRS:PlaySoundFile(Soundfile, Delay) if Delay and Delay>0 then - self:ScheduleOnce(Delay, MSRS.PlaySoundfile, self, Soundfile, 0) + self:ScheduleOnce(Delay, MSRS.PlaySoundFile, self, Soundfile, 0) else - local exe=self:GetPath().."/".."DCS-SR-ExternalAudio.exe" local soundfile=Soundfile:GetName() - local freq=table.concat(self.frequencies, " ") - local modu=table.concat(self.modulations, " ") - local coal=self.coalition - local port=self.port + + local command=self:_GetCommand() - local command=string.format("%s --file %s --freqs %s --modulations %s --coalition %d --port %d -h", exe, soundfile, freq, modu, coal, port) + command=command.." --file="..tostring(soundfile) + + env.info(string.format("FF PlaySoundfile command=%s", command)) + + -- Execute SRS command. + os.execute(command) + + end + + return self +end + +--- Play a SOUNDTEXT text-to-speech object. +-- @param #MSRS self +-- @param Sound.SoundFile#SOUNDTEXT SoundText Sound text. +-- @param #number Delay Delay in seconds, before the sound file is played. +-- @return #MSRS self +function MSRS:PlaySoundText(SoundText, Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MSRS.PlaySoundText, self, SoundText, 0) + else + + local command=self:_GetCommand(nil, nil, nil, SoundText.gender, SoundText.voice, SoundText.culture, SoundText.volume, SoundText.speed) + + command=command..string.format(" --text=\"%s\"", tostring(SoundText.text)) env.info(string.format("FF PlaySoundfile command=%s", command)) @@ -215,29 +293,26 @@ end --- Play text message via STTS. -- @param #MSRS self --- @param #string Message Text message. +-- @param #string Text Text message. -- @param #number Delay Delay in seconds, before the message is played. -- @return #MSRS self -function MSRS:PlayText(Message, Delay) +function MSRS:PlayText(Text, Delay) if Delay and Delay>0 then - self:ScheduleOnce(Delay, MSRS.PlayText, self, Message, 0) + self:ScheduleOnce(Delay, MSRS.PlayText, self, Text, 0) else - local text=string.format("\"%s\"", Message) - local exe=self:GetPath().."/".."DCS-SR-ExternalAudio.exe" - local freq=table.concat(self.frequencies, " ") - local modu=table.concat(self.modulations, " ") - local coal=self.coalition - local port=self.port - local gender="male" + local text=string.format("\"%s\"", Text) - local command=string.format("%s -h --text=%s --freqs=%s --modulations=%s --coalition=%d --port=%d --gender=%s", exe, text, freq, modu, coal, port, gender) + local command=self:_GetCommand() + + command=command..string.format(" --text=\"%s\"", tostring(Text)) env.info(string.format("FF Text command=%s", command)) -- Execute SRS command. - os.execute(command) + local x=os.execute(command) + env.info(x) end @@ -249,6 +324,46 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Get SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. +-- @param #MSRS self +-- @param #table freqs Frequencies in MHz. +-- @param #table modus Modulations. +-- @param #number coal Coalition. +-- @param #string gender Gender. +-- @param #string voice Voice. +-- @param #string culture Culture. +-- @param #number volume Volume. +-- @param #number speed Speed. +-- @param #number port Port. +-- @return #string Command. +function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port) + + + local exe=self:GetPath().."DCS-SR-ExternalAudio.exe" + freqs=table.concat(freqs or self.frequencies, ",") + modus=table.concat(modus or self.modulations, ",") + coal=coal or self.coalition + gender=gender or self.gender + voice=voice or self.voice + culture=culture or self.culture + volume=volume or self.volume + speed=speed or self.speed + port=port or self.port + + local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --gender=%s --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, gender, volume, speed) + + if voice then + command=command..string.format(" --voice=\"%s\"", tostring(voice)) + end + + if culture then + command=command.." --culture="..tostring(culture) + end + + env.info("FF command="..command) + + return command +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Sound/SoundFile.lua b/Moose Development/Moose/Sound/SoundFile.lua index 4b65bffc1..884eeab6a 100644 --- a/Moose Development/Moose/Sound/SoundFile.lua +++ b/Moose Development/Moose/Sound/SoundFile.lua @@ -1,4 +1,4 @@ ---- **Sound** - Sound file management. +--- **Sound** - Sound output classes. -- -- === -- @@ -16,12 +16,46 @@ -- @image Sound_Soundfile.png -- +do -- Sound Base + + --- @type SOUNDBASE + -- @field #string ClassName Name of the class + -- @extends Core.Base#BASE + + + --- Sound files used by other classes. + -- + -- # 1. USERFLAG constructor + -- + -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- + -- @field #SOUNDBASE + SOUNDBASE={ + ClassName = "SOUNDBASE", + } + + --- Constructor to create a new SOUNDBASE object. + -- @param #SOUNDBASE self + -- @return #SOUNDBASE self + function SOUNDBASE:New() + + -- Inherit BASE. + local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE + + + + return self + end + +end + + do -- Sound File --- @type SOUNDFILE -- @field #string ClassName Name of the class -- @field #string filename Name of the flag. - -- @field #string path Directory path, where the sound file is located. + -- @field #string path Directory path, where the sound file is located. This includes the final slash "/". -- @field #string duration Duration of the sound file in seconds. -- @field #string subtitle Subtitle of the transmission. -- @field #number subduration Duration in seconds how long the subtitle is displayed. @@ -39,7 +73,7 @@ do -- Sound File SOUNDFILE={ ClassName = "SOUNDFILE", filename = nil, - path = "l10n/DEFAULT", + path = "l10n/DEFAULT/", duration = 3, subtitle = nil, subduration = 0, @@ -58,12 +92,10 @@ do -- Sound File local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE -- Set file name. - self.filename=FileName or "Hallo World.ogg" - - --TODO: check that sound file is .ogg or .mp3 + self:SetFileName(FileName) -- Set path - self.path=Path or "l10n/DEFAULT/" + self:SetPath(Path) self.duration=Duration or 3 @@ -79,19 +111,39 @@ do -- Sound File -- @return #SOUNDFILE self function SOUNDFILE:SetPath(Path) + -- Init path. self.path=Path or "l10n/DEFAULT/" -- Remove (back)slashes. - local nmax=1000 - local n=1 + local nmax=1000 ; local n=1 while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do self.path=self.path:sub(1,#self.path-1) n=n+1 end + + -- Append slash. + self.path=self.path.."/" return self end - + + --- Get path of the directory, where the sound file is located. + -- @param #SOUNDFILE self + -- @return #string Path. + function SOUNDFILE:GetPath() + local path=self.path or "l10n/DEFAULT/" + return path + end + + --- Set sound file name. This must be a .ogg or .mp3 file! + -- @param #SOUNDFILE self + -- @param #string FileName Name of the file. + -- @return #SOUNDFILE self + function SOUNDFILE:SetFileName(FileName) + --TODO: check that sound file is really .ogg or .mp3 + self.filename=FileName or "HelloWorld.mp3" + return self + end --- Get the sound file name. -- @param #SOUNDFILE self @@ -99,24 +151,133 @@ do -- Sound File function SOUNDFILE:GetFileName() return self.filename end - - --- Get path of the directory, where the sound file is located. + + + --- Set duration how long it takes to play the sound file. -- @param #SOUNDFILE self - -- @return #string Path. - function SOUNDFILE:GetPath() - local path=self.path or "l10n/DEFAULT" - path=path.."/" - return path + -- @param #string Duration Duration in seconds. Default 3 seconds. + -- @return #SOUNDFILE self + function SOUNDFILE:SetDuration(Duration) + self.duration=Duration or 3 + return self + end + + --- Get duration how long the sound file takes to play. + -- @param #SOUNDFILE self + -- @return #number Duration in seconds. + function SOUNDFILE:GetDuration() + return self.duration or 3 end --- Get the complete sound file name inlcuding its path. -- @param #SOUNDFILE self -- @return #string Name of the sound file. function SOUNDFILE:GetName() - local filename=self:GetFileName() local path=self:GetPath() - local name=string.format("%s/%s", path, filename) + local filename=self:GetFileName() + local name=string.format("%s%s", path, filename) return name end +end + +do -- Text-To-Speech + + --- @type SOUNDTEXT + -- @field #string ClassName Name of the class + -- @field #string text Text to speak. + -- @field #number duration Duration in seconds. + -- @field #string gender Gender. + -- @field #string voice Voice. + -- @field #string culture Culture. + -- @extends Core.Base#BASE + + + --- Sound files used by other classes. + -- + -- # 1. USERFLAG constructor + -- + -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- + -- @field #SOUNDTEXT + SOUNDTEXT={ + ClassName = "SOUNDTEXT", + } + + --- Constructor to create a new SOUNDTEXT object. + -- @param #SOUNDTEXT self + -- @param #string Text The text to speak. + -- @param #number Duration Duration in seconds, how long it takes to play the text. Default is 3 seconds. + -- @return #SOUNDTEXT self + function SOUNDTEXT:New(Text, Duration) + + -- Inherit BASE. + local self=BASE:Inherit(self, BASE:New()) -- #SOUNDTEXT + + self:SetText(Text) + self:SetDuration(Duration) + self:SetGender() + self:SetCulture() + + -- Debug info: + self:I(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration)) + + return self + end + + --- Set text. + -- @param #SOUNDTEXT self + -- @param #string Text Text to speak. Default "Hello World!". + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetText(Text) + + self.text=Text or "Hello World!" + + return self + end + + --- Set duration, how long it takes to speak the text. + -- @param #SOUNDTEXT self + -- @param #number Duration Duration in seconds. Default 3 seconds. + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetDuration(Duration) + + self.duration=Duration or 3 + + return self + end + + --- Set gender. + -- @param #SOUNDTEXT self + -- @param #string Gender Gender: "male" or "female" (default). + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetGender(Gender) + + self.gender=Gender or "female" + + return self + end + + --- Set the voice name. See the list from --help or if using google see: https://cloud.google.com/text-to-speech/docs/voices + -- @param #SOUNDTEXT self + -- @param #string Voice + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetVoice(Voice) + + self.voice=Voice + + return self + end + + --- Set TTS culture - local for the voice. + -- @param #SOUNDTEXT self + -- @param #string Culture TTS culture. Default "en-GB". + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetCulture(Culture) + + self.culture=Culture or "en-GB" + + return self + end + end \ No newline at end of file