Sound update

This commit is contained in:
Frank 2021-05-30 01:36:22 +02:00
parent fcd75a34eb
commit 4c3d44a63b
11 changed files with 1235 additions and 736 deletions

View File

@ -0,0 +1,442 @@
--- **Core** - TACAN and other beacons.
--
-- ===
--
-- ## Features:
--
-- * Provide beacon functionality to assist pilots.
--
-- ===
--
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
--
-- @module Core.Beacon
-- @image Core_Radio.JPG
--- *In order for the light to shine so brightly, the darkness must be present.* -- Francis Bacon
--
-- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon.
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
-- attach to a cargo crate, for exemple.
--
-- ## AA TACAN Beacon usage
--
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon.
-- Use @#BEACON:StopAATACAN}() to stop it.
--
-- ## General Purpose Radio Beacon usage
--
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon.
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
--
-- @type BEACON
-- @field #string ClassName Name of the class "BEACON".
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
-- @extends Core.Base#BASE
BEACON = {
ClassName = "BEACON",
Positionable = nil,
name = nil,
}
--- Beacon types supported by DCS.
-- @type BEACON.Type
-- @field #number NULL
-- @field #number VOR
-- @field #number DME
-- @field #number VOR_DME
-- @field #number TACAN TACtical Air Navigation system.
-- @field #number VORTAC
-- @field #number RSBN
-- @field #number BROADCAST_STATION
-- @field #number HOMER
-- @field #number AIRPORT_HOMER
-- @field #number AIRPORT_HOMER_WITH_MARKER
-- @field #number ILS_FAR_HOMER
-- @field #number ILS_NEAR_HOMER
-- @field #number ILS_LOCALIZER
-- @field #number ILS_GLIDESLOPE
-- @field #number PRMG_LOCALIZER
-- @field #number PRMG_GLIDESLOPE
-- @field #number ICLS Same as ICLS glideslope.
-- @field #number ICLS_LOCALIZER
-- @field #number ICLS_GLIDESLOPE
-- @field #number NAUTICAL_HOMER
BEACON.Type={
NULL = 0,
VOR = 1,
DME = 2,
VOR_DME = 3,
TACAN = 4,
VORTAC = 5,
RSBN = 128,
BROADCAST_STATION = 1024,
HOMER = 8,
AIRPORT_HOMER = 4104,
AIRPORT_HOMER_WITH_MARKER = 4136,
ILS_FAR_HOMER = 16408,
ILS_NEAR_HOMER = 16424,
ILS_LOCALIZER = 16640,
ILS_GLIDESLOPE = 16896,
PRMG_LOCALIZER = 33024,
PRMG_GLIDESLOPE = 33280,
ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE
ICLS_LOCALIZER = 131328,
ICLS_GLIDESLOPE = 131584,
NAUTICAL_HOMER = 65536,
}
--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon
-- @type BEACON.System
-- @field #number PAR_10 ?
-- @field #number RSBN_5 Russian VOR/DME system.
-- @field #number TACAN TACtical Air Navigation system on ground.
-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band.
-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band.
-- @field #number VOR Very High Frequency Omni-Directional Range
-- @field #number ILS_LOCALIZER ILS localizer
-- @field #number ILS_GLIDESLOPE ILS glideslope.
-- @field #number PRGM_LOCALIZER PRGM localizer.
-- @field #number PRGM_GLIDESLOPE PRGM glideslope.
-- @field #number BROADCAST_STATION Broadcast station.
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band.
-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band.
-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME).
-- @field #number ICLS_LOCALIZER Carrier landing system.
-- @field #number ICLS_GLIDESLOPE Carrier landing system.
BEACON.System={
PAR_10 = 1,
RSBN_5 = 2,
TACAN = 3,
TACAN_TANKER_X = 4,
TACAN_TANKER_Y = 5,
VOR = 6,
ILS_LOCALIZER = 7,
ILS_GLIDESLOPE = 8,
PRMG_LOCALIZER = 9,
PRMG_GLIDESLOPE = 10,
BROADCAST_STATION = 11,
VORTAC = 12,
TACAN_AA_MODE_X = 13,
TACAN_AA_MODE_Y = 14,
VORDME = 15,
ICLS_LOCALIZER = 16,
ICLS_GLIDESLOPE = 17,
}
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
-- @param #BEACON self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @return #BEACON Beacon object or #nil if the positionable is invalid.
function BEACON:New(Positionable)
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) --#BEACON
-- Debug.
self:F(Positionable)
-- Set positionable.
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
self.Positionable = Positionable
self.name=Positionable:GetName()
self:I(string.format("New BEACON %s", tostring(self.name)))
return self
end
self:E({"The passed positionable is invalid, no BEACON created", Positionable})
return nil
end
--- Activates a TACAN BEACON.
-- @param #BEACON self
-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
-- @usage
-- -- Let's create a TACAN Beacon for a tanker
-- local myUnit = UNIT:FindByName("MyUnit")
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
--
-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
-- Get frequency.
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
-- Check.
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
return self
end
-- Beacon type.
local Type=BEACON.Type.TACAN
-- Beacon system.
local System=BEACON.System.TACAN
-- Check if unit is an aircraft and set system accordingly.
local AA=self.Positionable:IsAir()
if AA then
System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
-- Check if "Y" mode is selected for aircraft.
if Mode~="Y" then
self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable})
end
end
-- Attached unit.
local UnitID=self.Positionable:GetID()
-- Debug.
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))})
-- Start beacon.
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
-- Stop sheduler.
if Duration then
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
-- @param #BEACON self
-- @param #number Channel ICLS channel.
-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon.
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
function BEACON:ActivateICLS(Channel, Callsign, Duration)
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
-- Attached unit.
local UnitID=self.Positionable:GetID()
-- Debug
self:T2({"ICLS BEACON started!"})
-- Start beacon.
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
-- Stop sheduler
if Duration then -- Schedule the stop of the BEACON if asked by the MD
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
--- Activates a TACAN BEACON on an Aircraft.
-- @param #BEACON self
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
-- @param #boolean Bearing Can the BEACON be homed on ?
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
-- @usage
-- -- Let's create a TACAN Beacon for a tanker
-- local myUnit = UNIT:FindByName("MyUnit")
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
--
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
self:F({TACANChannel, Message, Bearing, BeaconDuration})
local IsValid = true
if not self.Positionable:IsAir() then
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
IsValid = false
end
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
IsValid = false
end
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing
-- or 14 (TACAN_AA_MODE_Y) if it does not
local System
if Bearing then
System = 5
else
System = 14
end
if IsValid then -- Starts the BEACON
self:T2({"AA TACAN BEACON started !"})
self.Positionable:SetCommand({
id = "ActivateBeacon",
params = {
type = 4,
system = System,
callsign = Message,
frequency = Frequency,
}
})
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New(nil,
function()
self:StopAATACAN()
end, {}, BeaconDuration)
end
end
return self
end
--- Stops the AA TACAN BEACON
-- @param #BEACON self
-- @return #BEACON self
function BEACON:StopAATACAN()
self:F()
if not self.Positionable then
self:E({"Start the beacon first before stoping it !"})
else
self.Positionable:SetCommand({
id = 'DeactivateBeacon',
params = {
}
})
end
end
--- Activates a general pupose Radio Beacon
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
-- They can home in on these specific frequencies :
-- * **Mi8**
-- * R-828 -> 20-60MHz
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
-- * ARK9 -> 150-1300KHz
-- * **Huey**
-- * AN/ARC-131 -> 30-76 Mhz FM
-- @param #BEACON self
-- @param #string FileName The name of the audio file
-- @param #number Frequency in MHz
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
-- @param #number Power in W
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
-- @usage
-- -- Let's create a beacon for a unit in distress.
-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131)
-- -- The beacon they use is battery-powered, and only lasts for 5 min
-- local UnitInDistress = UNIT:FindByName("Unit1")
-- local UnitBeacon = UnitInDistress:GetBeacon()
--
-- -- Set the beacon and start it
-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60)
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
local IsValid = false
-- Check the filename
if type(FileName) == "string" then
if FileName:find(".ogg") or FileName:find(".wav") then
if not FileName:find("l10n/DEFAULT/") then
FileName = "l10n/DEFAULT/" .. FileName
end
IsValid = true
end
end
if not IsValid then
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
end
-- Check the Frequency
if type(Frequency) ~= "number" and IsValid then
self:E({"Frequency invalid. ", Frequency})
IsValid = false
end
Frequency = Frequency * 1000000 -- Conversion to Hz
-- Check the modulation
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
IsValid = false
end
-- Check the Power
if type(Power) ~= "number" and IsValid then
self:E({"Power is invalid. ", Power})
IsValid = false
end
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
if IsValid then
self:T2({"Activating Beacon on ", Frequency, Modulation})
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New( nil,
function()
self:StopRadioBeacon()
end, {}, BeaconDuration)
end
end
end
--- Stops the AA TACAN BEACON
-- @param #BEACON self
-- @return #BEACON self
function BEACON:StopRadioBeacon()
self:F()
-- The unique name of the transmission is the class ID
trigger.action.stopRadioTransmission(tostring(self.ID))
return self
end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
-- @param #BEACON self
-- @param #number TACANChannel
-- @param #string TACANMode
-- @return #number Frequecy
-- @return #nil if parameters are invalid
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
self:F3({TACANChannel, TACANMode})
if type(TACANChannel) ~= "number" then
if TACANMode ~= "X" and TACANMode ~= "Y" then
return nil -- error in arguments
end
end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
-- I have no idea what it does but it seems to work
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
if TACANChannel < 64 then
B = 1
end
if TACANMode == 'Y' then
A = 1025
if TACANChannel < 64 then
A = 1088
end
else -- 'X'
if TACANChannel < 64 then
A = 962
end
end
return (A + TACANChannel - B) * 1000000
end

View File

@ -236,6 +236,7 @@ do -- SETTINGS
--- SETTINGS constructor.
-- @param #SETTINGS self
-- @param #string PlayerName (Optional) Set settings for this player.
-- @return #SETTINGS
function SETTINGS:Set( PlayerName )

View File

@ -1,5 +1,4 @@
-- The order of the declarations is important here. Don't touch it.
--- GLOBALS: The order of the declarations is important here. Don't touch it.
--- Declare the event dispatcher based on the EVENT class
_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT
@ -10,9 +9,38 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU
--- Declare the main database object, which is used internally by the MOOSE classes.
_DATABASE = DATABASE:New() -- Core.Database#DATABASE
--- Settings
_SETTINGS = SETTINGS:Set()
_SETTINGS:SetPlayerMenuOn()
--- Register cargos.
_DATABASE:_RegisterCargos()
--- Register zones.
_DATABASE:_RegisterZones()
--- Check if os etc is available.
BASE:I("Checking de-sanitization of os, io and lfs (Check <DCS install folder>/Scripts/MissionScripting.lua and commend out sanitizeModule(''). Use at your own risk!)")
local __na=false
if os then
BASE:I("- os available")
else
BASE:I("- os NOT available! Some functions may not work.")
__na=true
end
if io then
BASE:I("- io available")
else
BASE:I("- io NOT available! Some functions may not work.")
__na=true
end
if lfs then
BASE:I("- lfs available")
else
BASE:I("- lfs NOT available! Some functions may not work.")
__na=true
end
if __na then
BASE:I("Check <DCS install folder>/Scripts/MissionScripting.lua and commend out the lines with sanitizeModule(''). Use at your own risk!)")
end

View File

@ -3,8 +3,10 @@ __Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' )
__Moose.Include( 'Scripts/Moose/Core/Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/Beacon.lua' )
__Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' )
__Moose.Include( 'Scripts/Moose/Core/Report.lua' )
__Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' )
@ -110,7 +112,7 @@ __Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' )
__Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' )
__Moose.Include( 'Scripts/Moose/Sound/UserSound.lua' )
__Moose.Include( 'Scripts/Moose/Sound/SoundFile.lua' )
__Moose.Include( 'Scripts/Moose/Sound/SoundOutput.lua' )
__Moose.Include( 'Scripts/Moose/Sound/Radio.lua' )
__Moose.Include( 'Scripts/Moose/Sound/RadioQueue.lua' )
__Moose.Include( 'Scripts/Moose/Sound/RadioSpeech.lua' )

View File

@ -88,6 +88,8 @@
-- @field #boolean usemarker Use mark on the F10 map.
-- @field #number markerid Numerical ID of the F10 map mark point.
-- @field #number relHumidity Relative humidity (used to approximately calculate the dew point).
-- @field #boolean useSRS If true, use SRS for transmission.
-- @field Sound.SRS#MSRS msrs Moose SRS object.
-- @extends Core.Fsm#FSM
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
@ -1100,6 +1102,22 @@ function ATIS:MarkRunways(markall)
end
end
--- Use SRS Simple-Text-To-Speech for transmissions. No sound files necessary.
-- @param #ATIS self
-- @param #string PathToSRS Path to SRS directory.
-- @param #string Gender Gender: "male" or "female" (default).
-- @param #string Culture Culture, e.g. "en-GB" (default).
-- @param #string Voice Specific voice. Overrides `Gender` and `Culture`.
-- @return #ATIS self
function ATIS:SetSTTS(PathToSRS, Gender, Culture, Voice, Port)
self.useSRS=true
self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation)
self.msrs:SetGender(Gender)
self.msrs:SetCulture(Culture)
self.msrs:SetVoice(Voice)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Start & Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -1188,15 +1206,25 @@ end
-- @param #string To To state.
function ATIS:onafterCheckQueue(From, Event, To)
if #self.radioqueue.queue==0 then
self:T(self.lid..string.format("Radio queue empty. Repeating message."))
if self.useSRS then
self:Broadcast()
self:__CheckQueue(-120)
else
self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue))
if #self.radioqueue.queue==0 then
self:T(self.lid..string.format("Radio queue empty. Repeating message."))
self:Broadcast()
else
self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue))
end
-- Check back in 5 seconds.
self:__CheckQueue(-5)
end
-- Check back in 5 seconds.
self:__CheckQueue(-5)
end
--- Broadcast ATIS radio message.
@ -1591,36 +1619,46 @@ function ATIS:onafterBroadcast(From, Event, To)
if self.airbasename:find("AFB")==nil and self.airbasename:find("Airport")==nil and self.airbasename:find("Airstrip")==nil and self.airbasename:find("airfield")==nil and self.airbasename:find("AB")==nil then
subtitle=subtitle.." Airport"
end
self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, self.subduration)
if not self.useSRS then
self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, self.subduration)
end
local alltext=subtitle
-- Information tag
subtitle=string.format("Information %s", NATO)
local _INFORMATION=subtitle
self:Transmission(ATIS.Sound.Information, 0.5, subtitle)
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
if not self.useSRS then
self:Transmission(ATIS.Sound.Information, 0.5, subtitle)
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
end
alltext=alltext..";\n"..subtitle
-- Zulu Time
subtitle=string.format("%s Zulu", ZULU)
self.radioqueue:Number2Transmission(ZULU, nil, 0.5)
self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle)
if not self.useSRS then
self.radioqueue:Number2Transmission(ZULU, nil, 0.5)
self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle)
end
alltext=alltext..";\n"..subtitle
if not self.zulutimeonly then
-- Sunrise Time
subtitle=string.format("Sunrise at %s local time", SUNRISE)
self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle)
self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2)
self:Transmission(ATIS.Sound.TimeLocal, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle)
self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2)
self:Transmission(ATIS.Sound.TimeLocal, 0.2)
end
alltext=alltext..";\n"..subtitle
-- Sunset Time
subtitle=string.format("Sunset at %s local time", SUNSET)
self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle)
self.radioqueue:Number2Transmission(SUNSET, nil, 0.5)
self:Transmission(ATIS.Sound.TimeLocal, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle)
self.radioqueue:Number2Transmission(SUNSET, nil, 0.5)
self:Transmission(ATIS.Sound.TimeLocal, 0.2)
end
alltext=alltext..";\n"..subtitle
end
@ -1634,17 +1672,19 @@ function ATIS:onafterBroadcast(From, Event, To)
subtitle=subtitle..", gusting"
end
local _WIND=subtitle
self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle)
self.radioqueue:Number2Transmission(WINDFROM)
self:Transmission(ATIS.Sound.At, 0.2)
self.radioqueue:Number2Transmission(WINDSPEED)
if self.metric then
self:Transmission(ATIS.Sound.MetersPerSecond, 0.2)
else
self:Transmission(ATIS.Sound.Knots, 0.2)
end
if turbulence>0 then
self:Transmission(ATIS.Sound.Gusting, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle)
self.radioqueue:Number2Transmission(WINDFROM)
self:Transmission(ATIS.Sound.At, 0.2)
self.radioqueue:Number2Transmission(WINDSPEED)
if self.metric then
self:Transmission(ATIS.Sound.MetersPerSecond, 0.2)
else
self:Transmission(ATIS.Sound.Knots, 0.2)
end
if turbulence>0 then
self:Transmission(ATIS.Sound.Gusting, 0.2)
end
end
alltext=alltext..";\n"..subtitle
@ -1654,12 +1694,14 @@ function ATIS:onafterBroadcast(From, Event, To)
else
subtitle=string.format("Visibility %s SM", VISIBILITY)
end
self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle)
self.radioqueue:Number2Transmission(VISIBILITY)
if self.metric then
self:Transmission(ATIS.Sound.Kilometers, 0.2)
else
self:Transmission(ATIS.Sound.StatuteMiles, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle)
self.radioqueue:Number2Transmission(VISIBILITY)
if self.metric then
self:Transmission(ATIS.Sound.Kilometers, 0.2)
else
self:Transmission(ATIS.Sound.StatuteMiles, 0.2)
end
end
alltext=alltext..";\n"..subtitle
@ -1699,57 +1741,63 @@ function ATIS:onafterBroadcast(From, Event, To)
-- Actual output
if wp then
subtitle=string.format("Weather phenomena:%s", wpsub)
self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle)
if precepitation==1 then
self:Transmission(ATIS.Sound.Rain, 0.5)
elseif precepitation==2 then
self:Transmission(ATIS.Sound.ThunderStorm, 0.5)
elseif precepitation==3 then
self:Transmission(ATIS.Sound.Snow, 0.5)
elseif precepitation==4 then
self:Transmission(ATIS.Sound.SnowStorm, 0.5)
end
if fog then
self:Transmission(ATIS.Sound.Fog, 0.5)
end
if dust then
self:Transmission(ATIS.Sound.Dust, 0.5)
if not self.useSRS then
self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle)
if precepitation==1 then
self:Transmission(ATIS.Sound.Rain, 0.5)
elseif precepitation==2 then
self:Transmission(ATIS.Sound.ThunderStorm, 0.5)
elseif precepitation==3 then
self:Transmission(ATIS.Sound.Snow, 0.5)
elseif precepitation==4 then
self:Transmission(ATIS.Sound.SnowStorm, 0.5)
end
if fog then
self:Transmission(ATIS.Sound.Fog, 0.5)
end
if dust then
self:Transmission(ATIS.Sound.Dust, 0.5)
end
end
alltext=alltext..";\n"..subtitle
end
-- Cloud base
self:Transmission(CloudCover, 1.0, CLOUDSsub)
if not self.useSRS then
self:Transmission(CloudCover, 1.0, CLOUDSsub)
end
if CLOUDBASE and static then
-- Base
if self.metric then
subtitle=string.format("Cloudbase %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL)
subtitle=string.format("Cloud base %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL)
else
subtitle=string.format("Cloudbase %s, ceiling %s ft", CLOUDBASE, CLOUDCEIL)
subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL)
end
self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle)
if tonumber(CLOUDBASE1000)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
end
if tonumber(CLOUDBASE0100)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
-- Ceiling
self:Transmission(ATIS.Sound.CloudCeiling, 0.5)
if tonumber(CLOUDCEIL1000)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
end
if tonumber(CLOUDCEIL0100)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters, 0.1)
else
self:Transmission(ATIS.Sound.Feet, 0.1)
if not self.useSRS then
self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle)
if tonumber(CLOUDBASE1000)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
end
if tonumber(CLOUDBASE0100)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
-- Ceiling
self:Transmission(ATIS.Sound.CloudCeiling, 0.5)
if tonumber(CLOUDCEIL1000)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
end
if tonumber(CLOUDCEIL0100)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters, 0.1)
else
self:Transmission(ATIS.Sound.Feet, 0.1)
end
end
end
alltext=alltext..";\n"..subtitle
@ -1769,15 +1817,17 @@ function ATIS:onafterBroadcast(From, Event, To)
end
end
local _TEMPERATURE=subtitle
self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle)
if temperature<0 then
self:Transmission(ATIS.Sound.Minus, 0.2)
end
self.radioqueue:Number2Transmission(TEMPERATURE)
if self.TDegF then
self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2)
else
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle)
if temperature<0 then
self:Transmission(ATIS.Sound.Minus, 0.2)
end
self.radioqueue:Number2Transmission(TEMPERATURE)
if self.TDegF then
self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2)
else
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
end
end
alltext=alltext..";\n"..subtitle
@ -1796,15 +1846,17 @@ function ATIS:onafterBroadcast(From, Event, To)
end
end
local _DEWPOINT=subtitle
self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle)
if dewpoint<0 then
self:Transmission(ATIS.Sound.Minus, 0.2)
end
self.radioqueue:Number2Transmission(DEWPOINT)
if self.TDegF then
self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2)
else
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle)
if dewpoint<0 then
self:Transmission(ATIS.Sound.Minus, 0.2)
end
self.radioqueue:Number2Transmission(DEWPOINT)
if self.TDegF then
self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2)
else
self:Transmission(ATIS.Sound.DegreesCelsius, 0.2)
end
end
alltext=alltext..";\n"..subtitle
@ -1813,51 +1865,53 @@ function ATIS:onafterBroadcast(From, Event, To)
if self.qnhonly then
subtitle=string.format("Altimeter %s.%s mmHg", QNH[1], QNH[2])
else
subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2])
subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2])
end
else
if self.metric then
if self.qnhonly then
subtitle=string.format("Altimeter %s.%s hPa", QNH[1], QNH[2])
else
subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2])
subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2])
end
else
if self.qnhonly then
subtitle=string.format("Altimeter %s.%s inHg", QNH[1], QNH[2])
else
subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s inHg", QNH[1], QNH[2], QFE[1], QFE[2])
subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s inHg", QNH[1], QNH[2], QFE[1], QFE[2])
end
end
end
local _ALTIMETER=subtitle
self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle)
if not self.qnhonly then
self:Transmission(ATIS.Sound.QNH, 0.5)
end
self.radioqueue:Number2Transmission(QNH[1])
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then
self:Transmission(ATIS.Sound.Decimal, 0.2)
end
self.radioqueue:Number2Transmission(QNH[2])
if not self.qnhonly then
self:Transmission(ATIS.Sound.QFE, 0.75)
self.radioqueue:Number2Transmission(QFE[1])
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then
self:Transmission(ATIS.Sound.Decimal, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle)
if not self.qnhonly then
self:Transmission(ATIS.Sound.QNH, 0.5)
end
self.radioqueue:Number2Transmission(QFE[2])
end
self.radioqueue:Number2Transmission(QNH[1])
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then
self:Transmission(ATIS.Sound.Decimal, 0.2)
end
self.radioqueue:Number2Transmission(QNH[2])
if self.PmmHg then
self:Transmission(ATIS.Sound.MillimetersOfMercury, 0.1)
else
if self.metric then
self:Transmission(ATIS.Sound.HectoPascal, 0.1)
if not self.qnhonly then
self:Transmission(ATIS.Sound.QFE, 0.75)
self.radioqueue:Number2Transmission(QFE[1])
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then
self:Transmission(ATIS.Sound.Decimal, 0.2)
end
self.radioqueue:Number2Transmission(QFE[2])
end
if self.PmmHg then
self:Transmission(ATIS.Sound.MillimetersOfMercury, 0.1)
else
self:Transmission(ATIS.Sound.InchesOfMercury, 0.1)
if self.metric then
self:Transmission(ATIS.Sound.HectoPascal, 0.1)
else
self:Transmission(ATIS.Sound.InchesOfMercury, 0.1)
end
end
end
alltext=alltext..";\n"..subtitle
@ -1870,12 +1924,14 @@ function ATIS:onafterBroadcast(From, Event, To)
subtitle=subtitle.." Right"
end
local _RUNACT=subtitle
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
self.radioqueue:Number2Transmission(runway)
if rwyLeft==true then
self:Transmission(ATIS.Sound.Left, 0.2)
elseif rwyLeft==false then
self:Transmission(ATIS.Sound.Right, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
self.radioqueue:Number2Transmission(runway)
if rwyLeft==true then
self:Transmission(ATIS.Sound.Left, 0.2)
elseif rwyLeft==false then
self:Transmission(ATIS.Sound.Right, 0.2)
end
end
alltext=alltext..";\n"..subtitle
@ -1900,21 +1956,22 @@ function ATIS:onafterBroadcast(From, Event, To)
end
-- Transmit.
self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle)
if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
if not self.useSRS then
self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle)
if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
end
if tonumber(L0100)>0 then
self.radioqueue:Number2Transmission(L0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters, 0.1)
else
self:Transmission(ATIS.Sound.Feet, 0.1)
end
end
if tonumber(L0100)>0 then
self.radioqueue:Number2Transmission(L0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters, 0.1)
else
self:Transmission(ATIS.Sound.Feet, 0.1)
end
alltext=alltext..";\n"..subtitle
end
@ -1937,22 +1994,23 @@ function ATIS:onafterBroadcast(From, Event, To)
subtitle=subtitle.." feet"
end
-- Transmitt.
self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle)
if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
-- Transmit.
if not self.useSRS then
self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle)
if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000)
self:Transmission(ATIS.Sound.Thousand, 0.1)
end
if tonumber(L0100)>0 then
self.radioqueue:Number2Transmission(L0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters, 0.1)
else
self:Transmission(ATIS.Sound.Feet, 0.1)
end
end
if tonumber(L0100)>0 then
self.radioqueue:Number2Transmission(L0100)
self:Transmission(ATIS.Sound.Hundred, 0.1)
end
if self.metric then
self:Transmission(ATIS.Sound.Meters, 0.1)
else
self:Transmission(ATIS.Sound.Feet, 0.1)
end
alltext=alltext..";\n"..subtitle
end
@ -1966,9 +2024,47 @@ function ATIS:onafterBroadcast(From, Event, To)
end
end
subtitle=string.format("Tower frequency %s", freqs)
self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle)
for _,freq in pairs(self.towerfrequency) do
local f=string.format("%.3f", freq)
if not self.useSRS then
self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle)
for _,freq in pairs(self.towerfrequency) do
local f=string.format("%.3f", freq)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
end
alltext=alltext..";\n"..subtitle
end
-- ILS
local ils=self:GetNavPoint(self.ils, runway, rwyLeft)
if ils then
subtitle=string.format("ILS frequency %.2f MHz", ils.frequency)
if not self.useSRS then
self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle)
local f=string.format("%.2f", ils.frequency)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
alltext=alltext..";\n"..subtitle
end
-- Outer NDB
local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft)
if ndb then
subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency)
if not self.useSRS then
self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle)
local f=string.format("%.2f", ndb.frequency)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
@ -1977,41 +2073,6 @@ function ATIS:onafterBroadcast(From, Event, To)
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
alltext=alltext..";\n"..subtitle
end
-- ILS
local ils=self:GetNavPoint(self.ils, runway, rwyLeft)
if ils then
subtitle=string.format("ILS frequency %.2f MHz", ils.frequency)
self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle)
local f=string.format("%.2f", ils.frequency)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
alltext=alltext..";\n"..subtitle
end
-- Outer NDB
local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft)
if ndb then
subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency)
self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle)
local f=string.format("%.2f", ndb.frequency)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
alltext=alltext..";\n"..subtitle
end
@ -2019,51 +2080,55 @@ function ATIS:onafterBroadcast(From, Event, To)
local ndb=self:GetNavPoint(self.ndbinner, runway, rwyLeft)
if ndb then
subtitle=string.format("Inner NDB frequency %.2f MHz", ndb.frequency)
self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle)
local f=string.format("%.2f", ndb.frequency)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle)
local f=string.format("%.2f", ndb.frequency)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
alltext=alltext..";\n"..subtitle
end
-- VOR
if self.vor then
subtitle=string.format("VOR frequency %.2f MHz", self.vor)
self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle)
local f=string.format("%.2f", self.vor)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
if not self.useSRS then
self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle)
local f=string.format("%.2f", self.vor)
f=UTILS.Split(f, ".")
self.radioqueue:Number2Transmission(f[1], nil, 0.5)
if tonumber(f[2])>0 then
self:Transmission(ATIS.Sound.Decimal, 0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
end
self:Transmission(ATIS.Sound.MegaHertz, 0.2)
alltext=alltext..";\n"..subtitle
end
-- TACAN
if self.tacan then
subtitle=string.format("TACAN channel %dX", self.tacan)
self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2)
self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2)
self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2)
end
alltext=alltext..";\n"..subtitle
end
-- RSBN
if self.rsbn then
subtitle=string.format("RSBN channel %d", self.rsbn)
self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2)
if not self.useSRS then
self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2)
end
alltext=alltext..";\n"..subtitle
end
@ -2071,17 +2136,19 @@ function ATIS:onafterBroadcast(From, Event, To)
local ndb=self:GetNavPoint(self.prmg, runway, rwyLeft)
if ndb then
subtitle=string.format("PRMG channel %d", ndb.frequency)
self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5)
if not self.useSRS then
self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle)
self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5)
end
alltext=alltext..";\n"..subtitle
end
-- Advice on initial...
subtitle=string.format("Advise on initial contact, you have information %s", NATO)
self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle)
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
if not self.useSRS then
self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle)
self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath)
end
alltext=alltext..";\n"..subtitle
-- Report ATIS text.
@ -2103,12 +2170,31 @@ end
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)
if self.useSRS and self.msrs then
local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, Modulation)
msrs:PlayText(text)
-- Remove line breaks
local text=string.gsub(Text, "[\r\n]", "")
-- Replace other stuff.
local text=string.gsub(text, "SM", "statute miles")
local text=string.gsub(text, "°C", "degrees Celsius")
local text=string.gsub(text, "°F", "degrees Fahrenheit")
local text=string.gsub(text, "inHg", "inches of Mercury")
local text=string.gsub(text, "mmHg", "millimeters of Mercury")
local text=string.gsub(text, "hPa", "hecto Pascals")
local text=string.gsub(text, "m/s", "meters per second")
-- Replace ";" by "."
local text=string.gsub(text, ";", ". ")
env.info("FF: "..text)
--local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, Modulation)
--msrs:PlayText(text)
-- Play text-to-speech report.
self.msrs:PlayText(text)
end
end

View File

@ -1,13 +1,10 @@
--- **Core** - Is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, Radio transmissions.
--- **Sound** - Radio transmissions.
--
-- ===
--
-- ## Features:
--
-- * Provide radio functionality to broadcast radio transmissions.
-- * Provide beacon functionality to assist pilots.
--
-- The Radio contains 2 classes : RADIO and BEACON
--
-- What are radio communications in DCS?
--
@ -35,13 +32,13 @@
--
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
--
-- @module Core.Radio
-- @module Sound.Radio
-- @image Core_Radio.JPG
--- Models the radio capability.
--- *It's not true I had nothing on, I had the radio on.* -- Marilyn Monroe
--
-- ## RADIO usage
-- # RADIO usage
--
-- There are 3 steps to a successful radio transmission.
--
@ -87,15 +84,15 @@
-- @field #string alias Name of the radio transmitter.
-- @extends Core.Base#BASE
RADIO = {
ClassName = "RADIO",
FileName = "",
Frequency = 0,
Modulation = radio.modulation.AM,
Subtitle = "",
ClassName = "RADIO",
FileName = "",
Frequency = 0,
Modulation = radio.modulation.AM,
Subtitle = "",
SubtitleDuration = 0,
Power = 100,
Loop = false,
alias=nil,
Power = 100,
Loop = false,
alias = nil,
}
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast.
@ -395,438 +392,3 @@ function RADIO:StopBroadcast()
end
return self
end
--- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon.
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
-- attach to a cargo crate, for exemple.
--
-- ## AA TACAN Beacon usage
--
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon.
-- Use @#BEACON:StopAATACAN}() to stop it.
--
-- ## General Purpose Radio Beacon usage
--
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon.
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
--
-- @type BEACON
-- @field #string ClassName Name of the class "BEACON".
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
-- @extends Core.Base#BASE
BEACON = {
ClassName = "BEACON",
Positionable = nil,
name=nil,
}
--- Beacon types supported by DCS.
-- @type BEACON.Type
-- @field #number NULL
-- @field #number VOR
-- @field #number DME
-- @field #number VOR_DME
-- @field #number TACAN TACtical Air Navigation system.
-- @field #number VORTAC
-- @field #number RSBN
-- @field #number BROADCAST_STATION
-- @field #number HOMER
-- @field #number AIRPORT_HOMER
-- @field #number AIRPORT_HOMER_WITH_MARKER
-- @field #number ILS_FAR_HOMER
-- @field #number ILS_NEAR_HOMER
-- @field #number ILS_LOCALIZER
-- @field #number ILS_GLIDESLOPE
-- @field #number PRMG_LOCALIZER
-- @field #number PRMG_GLIDESLOPE
-- @field #number ICLS Same as ICLS glideslope.
-- @field #number ICLS_LOCALIZER
-- @field #number ICLS_GLIDESLOPE
-- @field #number NAUTICAL_HOMER
BEACON.Type={
NULL = 0,
VOR = 1,
DME = 2,
VOR_DME = 3,
TACAN = 4,
VORTAC = 5,
RSBN = 128,
BROADCAST_STATION = 1024,
HOMER = 8,
AIRPORT_HOMER = 4104,
AIRPORT_HOMER_WITH_MARKER = 4136,
ILS_FAR_HOMER = 16408,
ILS_NEAR_HOMER = 16424,
ILS_LOCALIZER = 16640,
ILS_GLIDESLOPE = 16896,
PRMG_LOCALIZER = 33024,
PRMG_GLIDESLOPE = 33280,
ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE
ICLS_LOCALIZER = 131328,
ICLS_GLIDESLOPE = 131584,
NAUTICAL_HOMER = 65536,
}
--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon
-- @type BEACON.System
-- @field #number PAR_10 ?
-- @field #number RSBN_5 Russian VOR/DME system.
-- @field #number TACAN TACtical Air Navigation system on ground.
-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band.
-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band.
-- @field #number VOR Very High Frequency Omni-Directional Range
-- @field #number ILS_LOCALIZER ILS localizer
-- @field #number ILS_GLIDESLOPE ILS glideslope.
-- @field #number PRGM_LOCALIZER PRGM localizer.
-- @field #number PRGM_GLIDESLOPE PRGM glideslope.
-- @field #number BROADCAST_STATION Broadcast station.
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band.
-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band.
-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME).
-- @field #number ICLS_LOCALIZER Carrier landing system.
-- @field #number ICLS_GLIDESLOPE Carrier landing system.
BEACON.System={
PAR_10 = 1,
RSBN_5 = 2,
TACAN = 3,
TACAN_TANKER_X = 4,
TACAN_TANKER_Y = 5,
VOR = 6,
ILS_LOCALIZER = 7,
ILS_GLIDESLOPE = 8,
PRMG_LOCALIZER = 9,
PRMG_GLIDESLOPE = 10,
BROADCAST_STATION = 11,
VORTAC = 12,
TACAN_AA_MODE_X = 13,
TACAN_AA_MODE_Y = 14,
VORDME = 15,
ICLS_LOCALIZER = 16,
ICLS_GLIDESLOPE = 17,
}
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
-- @param #BEACON self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @return #BEACON Beacon object or #nil if the positionable is invalid.
function BEACON:New(Positionable)
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) --#BEACON
-- Debug.
self:F(Positionable)
-- Set positionable.
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
self.Positionable = Positionable
self.name=Positionable:GetName()
self:I(string.format("New BEACON %s", tostring(self.name)))
return self
end
self:E({"The passed positionable is invalid, no BEACON created", Positionable})
return nil
end
--- Activates a TACAN BEACON.
-- @param #BEACON self
-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
-- @usage
-- -- Let's create a TACAN Beacon for a tanker
-- local myUnit = UNIT:FindByName("MyUnit")
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
--
-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
-- Get frequency.
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
-- Check.
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
return self
end
-- Beacon type.
local Type=BEACON.Type.TACAN
-- Beacon system.
local System=BEACON.System.TACAN
-- Check if unit is an aircraft and set system accordingly.
local AA=self.Positionable:IsAir()
if AA then
System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
-- Check if "Y" mode is selected for aircraft.
if Mode~="Y" then
self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable})
end
end
-- Attached unit.
local UnitID=self.Positionable:GetID()
-- Debug.
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))})
-- Start beacon.
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
-- Stop sheduler.
if Duration then
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
-- @param #BEACON self
-- @param #number Channel ICLS channel.
-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon.
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
function BEACON:ActivateICLS(Channel, Callsign, Duration)
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
-- Attached unit.
local UnitID=self.Positionable:GetID()
-- Debug
self:T2({"ICLS BEACON started!"})
-- Start beacon.
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
-- Stop sheduler
if Duration then -- Schedule the stop of the BEACON if asked by the MD
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
--- Activates a TACAN BEACON on an Aircraft.
-- @param #BEACON self
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
-- @param #boolean Bearing Can the BEACON be homed on ?
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
-- @usage
-- -- Let's create a TACAN Beacon for a tanker
-- local myUnit = UNIT:FindByName("MyUnit")
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
--
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
self:F({TACANChannel, Message, Bearing, BeaconDuration})
local IsValid = true
if not self.Positionable:IsAir() then
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
IsValid = false
end
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
IsValid = false
end
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing
-- or 14 (TACAN_AA_MODE_Y) if it does not
local System
if Bearing then
System = 5
else
System = 14
end
if IsValid then -- Starts the BEACON
self:T2({"AA TACAN BEACON started !"})
self.Positionable:SetCommand({
id = "ActivateBeacon",
params = {
type = 4,
system = System,
callsign = Message,
frequency = Frequency,
}
})
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New(nil,
function()
self:StopAATACAN()
end, {}, BeaconDuration)
end
end
return self
end
--- Stops the AA TACAN BEACON
-- @param #BEACON self
-- @return #BEACON self
function BEACON:StopAATACAN()
self:F()
if not self.Positionable then
self:E({"Start the beacon first before stoping it !"})
else
self.Positionable:SetCommand({
id = 'DeactivateBeacon',
params = {
}
})
end
end
--- Activates a general pupose Radio Beacon
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
-- They can home in on these specific frequencies :
-- * **Mi8**
-- * R-828 -> 20-60MHz
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
-- * ARK9 -> 150-1300KHz
-- * **Huey**
-- * AN/ARC-131 -> 30-76 Mhz FM
-- @param #BEACON self
-- @param #string FileName The name of the audio file
-- @param #number Frequency in MHz
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
-- @param #number Power in W
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
-- @return #BEACON self
-- @usage
-- -- Let's create a beacon for a unit in distress.
-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131)
-- -- The beacon they use is battery-powered, and only lasts for 5 min
-- local UnitInDistress = UNIT:FindByName("Unit1")
-- local UnitBeacon = UnitInDistress:GetBeacon()
--
-- -- Set the beacon and start it
-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60)
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
local IsValid = false
-- Check the filename
if type(FileName) == "string" then
if FileName:find(".ogg") or FileName:find(".wav") then
if not FileName:find("l10n/DEFAULT/") then
FileName = "l10n/DEFAULT/" .. FileName
end
IsValid = true
end
end
if not IsValid then
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
end
-- Check the Frequency
if type(Frequency) ~= "number" and IsValid then
self:E({"Frequency invalid. ", Frequency})
IsValid = false
end
Frequency = Frequency * 1000000 -- Conversion to Hz
-- Check the modulation
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
IsValid = false
end
-- Check the Power
if type(Power) ~= "number" and IsValid then
self:E({"Power is invalid. ", Power})
IsValid = false
end
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
if IsValid then
self:T2({"Activating Beacon on ", Frequency, Modulation})
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New( nil,
function()
self:StopRadioBeacon()
end, {}, BeaconDuration)
end
end
end
--- Stops the AA TACAN BEACON
-- @param #BEACON self
-- @return #BEACON self
function BEACON:StopRadioBeacon()
self:F()
-- The unique name of the transmission is the class ID
trigger.action.stopRadioTransmission(tostring(self.ID))
return self
end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
-- @param #BEACON self
-- @param #number TACANChannel
-- @param #string TACANMode
-- @return #number Frequecy
-- @return #nil if parameters are invalid
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
self:F3({TACANChannel, TACANMode})
if type(TACANChannel) ~= "number" then
if TACANMode ~= "X" and TACANMode ~= "Y" then
return nil -- error in arguments
end
end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
-- I have no idea what it does but it seems to work
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
if TACANChannel < 64 then
B = 1
end
if TACANMode == 'Y' then
A = 1025
if TACANChannel < 64 then
A = 1088
end
else -- 'X'
if TACANChannel < 64 then
A = 962
end
end
return (A + TACANChannel - B) * 1000000
end

View File

@ -77,7 +77,7 @@ RADIOQUEUE = {
--- Create a new RADIOQUEUE object for a given radio frequency/modulation.
-- @param #RADIOQUEUE self
-- @param #number frequency The radio frequency in MHz.
-- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM.
-- @param #number modulation (Optional) The radio modulation. Default `radio.modulation.AM` (=0).
-- @param #string alias (Optional) Name of the radio queue.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:New(frequency, modulation, alias)
@ -245,8 +245,6 @@ end
-- @return #RADIOQUEUE.Transmission Radio transmission table.
function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration)
env.info("FF new transmission.")
-- Sanity checks.
if not filename then
self:E(self.lid.."ERROR: No filename specified.")
@ -288,7 +286,7 @@ end
--- Create a new transmission and add it to the radio queue.
-- @param #RADIOQUEUE self
-- @param Sound.SoundFile#SOUNDFILE soundfile Sound file object to be added.
-- @param Sound.SoundOutput#SOUNDFILE soundfile Sound file object to be added.
-- @param #number tstart Start time (abs) seconds. Default now.
-- @param #number interval Interval in seconds after the last transmission finished.
-- @return #RADIOQUEUE self

View File

@ -71,7 +71,7 @@ MSRS = {
modulations = {},
coalition = 0,
gender = "female",
culture = "en-GB",
culture = nil,
voice = nil,
volume = 1,
speed = 1,
@ -81,15 +81,19 @@ MSRS = {
altitude = nil,
}
--- Counter.
_MSRSuuid=0
--- MSRS class version.
-- @field #string version
MSRS.version="0.0.2"
MSRS.version="0.0.3"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot.
-- TODO: Add functions to add/remove freqs and modulations.
-- TODO: Add coordinate.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
@ -144,7 +148,7 @@ function MSRS:SetPath(Path)
n=n+1
end
self.path=self.path.."/"
self.path=self.path --.."/"
self:I(string.format("SRS path=%s", self:GetPath()))
@ -225,16 +229,40 @@ end
-- @param #string Gender Gender: "male" or "female" (default).
-- @return #MSRS self
function MSRS:SetGender(Gender)
self:I("Input gender to "..tostring(Gender))
Gender=Gender or "female"
Gender=Gender:lower()
self.gender=Gender:lower()
self.gender=Gender
self:I("Setting gender to "..tostring(self.gender))
return self
end
--- Set culture.
-- @param #MSRS self
-- @param #string Culture Culture, e.g. "en-GB" (default).
-- @return #MSRS self
function MSRS:SetCulture(Culture)
self.culture=Culture
return self
end
--- Set to use a specific voice. Will override gender and culture settings.
-- @param #MSRS self
-- @param #string Voice Voice.
-- @return #MSRS self
function MSRS:SetVoice(Voice)
self.voice=Voice
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Transmission Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -250,16 +278,20 @@ function MSRS:PlaySoundFile(Soundfile, Delay)
self:ScheduleOnce(Delay, MSRS.PlaySoundFile, self, Soundfile, 0)
else
-- Sound file name.
local soundfile=Soundfile:GetName()
-- Get command.
local command=self:_GetCommand()
-- Append file.
command=command.." --file="..tostring(soundfile)
env.info(string.format("FF PlaySoundfile command=%s", command))
-- Debug output.
self:I(string.format("MSRS PlaySoundfile command=%s", command))
-- Execute SRS command.
os.execute(command)
local x=os.execute(command)
end
@ -277,14 +309,17 @@ function MSRS:PlaySoundText(SoundText, Delay)
self:ScheduleOnce(Delay, MSRS.PlaySoundText, self, SoundText, 0)
else
-- Get command.
local command=self:_GetCommand(nil, nil, nil, SoundText.gender, SoundText.voice, SoundText.culture, SoundText.volume, SoundText.speed)
-- Append text.
command=command..string.format(" --text=\"%s\"", tostring(SoundText.text))
env.info(string.format("FF PlaySoundfile command=%s", command))
-- Debug putput.
self:I(string.format("MSRS PlaySoundfile command=%s", command))
-- Execute SRS command.
os.execute(command)
local x=os.execute(command)
end
@ -302,17 +337,79 @@ function MSRS:PlayText(Text, Delay)
self:ScheduleOnce(Delay, MSRS.PlayText, self, Text, 0)
else
local text=string.format("\"%s\"", Text)
-- Get command line.
local command=self:_GetCommand()
-- Append text.
command=command..string.format(" --text=\"%s\"", tostring(Text))
env.info(string.format("FF Text command=%s", command))
-- Check that length of command is max 255 chars or os.execute() will not work!
if string.len(command)>255 then
-- Create a tmp file.
local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat"
local script = io.open(filename, "w+")
script:write(command.." && exit")
script:close()
-- Play command.
command=string.format("\"%s\"", filename)
-- Play file in 0.05 seconds
timer.scheduleFunction(os.execute, command, timer.getTime()+0.05)
-- Remove file in 1 second.
timer.scheduleFunction(os.remove, filename, timer.getTime()+1)
else
-- Debug output.
self:I(string.format("MSRS Text command=%s", command))
-- Execute SRS command.
local x=os.execute(command)
end
end
return self
end
--- Play text file via STTS.
-- @param #MSRS self
-- @param #string TextFile Full path to the file.
-- @param #number Delay Delay in seconds, before the message is played.
-- @return #MSRS self
function MSRS:PlayTextFile(TextFile, Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MSRS.PlayTextFile, self, TextFile, 0)
else
-- First check if text file exists!
local exists=UTILS.FileExists(TextFile)
if not exists then
self:E("ERROR: MSRS Text file does not exist! File="..tostring(TextFile))
return self
end
-- Get command line.
local command=self:_GetCommand()
-- Append text file.
command=command..string.format(" --textFile=\"%s\"", tostring(TextFile))
-- Debug output.
self:I(string.format("MSRS TextFile command=%s", command))
-- Count length of command.
local l=string.len(command)
-- Execute SRS command.
local x=os.execute(command)
env.info(x)
end
@ -338,8 +435,8 @@ end
-- @return #string Command.
function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port)
local exe=self:GetPath().."DCS-SR-ExternalAudio.exe"
local path=self:GetPath() or STTS.DIRECTORY
local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe"
freqs=table.concat(freqs or self.frequencies, ",")
modus=table.concat(modus or self.modulations, ",")
coal=coal or self.coalition
@ -348,19 +445,34 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
culture=culture or self.culture
volume=volume or self.volume
speed=speed or self.speed
port=port or self.port
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)
env.info("FF gender="..tostring(gender))
env.info("FF gender="..tostring(self.gender))
-- This did not work well. Stopped if the transmission was a bit longer with no apparent error.
--local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, volume, speed)
-- Command from orig STTS script. Works better for some unknown reason!
local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\"", path, exe, freqs, modus, coal, port, "ROBOT")
-- Set voice or gender/culture.
if voice then
-- Use a specific voice (no need for gender and/or culture.
command=command..string.format(" --voice=\"%s\"", tostring(voice))
else
-- Add gender.
if gender and gender~="female" then
command=command..string.format(" --gender=%s", tostring(gender))
end
-- Add culture.
if culture and culture~="en-GB" then
command=command..string.format(" -l %s", tostring(culture))
end
end
if culture then
command=command.." --culture="..tostring(culture)
end
env.info("FF command="..command)
-- Debug output.
self:I("MSRS command="..command)
return command
end

View File

@ -4,7 +4,8 @@
--
-- ## Features:
--
-- * Add a sound file to the
-- * Create a SOUNDFILE object (mp3 or ogg) to be played via DCS or SRS transmissions
-- * Create a SOUNDTEXT object for text-to-speech output
--
-- ===
--
@ -12,22 +13,19 @@
--
-- ===
--
-- @module Sound.Soundfile
-- @image Sound_Soundfile.png
--
-- @module Sound.SoundOutput
-- @image Sound_SoundOutput.png
do -- Sound Base
--- @type SOUNDBASE
-- @field #string ClassName Name of the class
-- @field #string ClassName Name of the class.
-- @extends Core.Base#BASE
--- Sound files used by other classes.
--- Basic sound output inherited by other classes.
--
-- # 1. USERFLAG constructor
--
-- * @{#USERFLAG.New}(): Creates a new USERFLAG object.
-- This class is **not** meant to be used by "ordinary" users.
--
-- @field #SOUNDBASE
SOUNDBASE={
@ -40,7 +38,7 @@ do -- Sound Base
function SOUNDBASE:New()
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE
local self=BASE:Inherit(self, BASE:New()) -- #SOUNDBASE
@ -94,10 +92,11 @@ do -- Sound File
-- Set file name.
self:SetFileName(FileName)
-- Set path
-- Set path.
self:SetPath(Path)
self.duration=Duration or 3
-- Set duration.
self:SetDuration(Duration)
-- Debug info:
self:I(string.format("New SOUNDFILE: file name=%s, path=%s", self.filename, self.path))
@ -137,11 +136,11 @@ do -- Sound File
--- Set sound file name. This must be a .ogg or .mp3 file!
-- @param #SOUNDFILE self
-- @param #string FileName Name of the file.
-- @param #string FileName Name of the file. Default is "Hello World.mp3".
-- @return #SOUNDFILE self
function SOUNDFILE:SetFileName(FileName)
--TODO: check that sound file is really .ogg or .mp3
self.filename=FileName or "HelloWorld.mp3"
self.filename=FileName or "Hello World.mp3"
return self
end
@ -187,17 +186,27 @@ do -- Text-To-Speech
-- @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.
-- @field #string gender Gender: "male", "female".
-- @field #string culture Culture, e.g. "en-GB".
-- @field #string voice Specific voice to use. Overrules `gender` and `culture` settings.
-- @extends Core.Base#BASE
--- Sound files used by other classes.
--- Text-to-speech objects for other classes.
--
-- # 1. USERFLAG constructor
-- # Constructor
--
-- * @{#USERFLAG.New}(): Creates a new USERFLAG object.
-- * @{#SOUNDTEXT.New}(*Text, Duration*): Creates a new SOUNDTEXT object.
--
-- Name: Microsoft Hazel Desktop, Culture: en-GB, Gender: Female, Age: Adult, Desc: Microsoft Hazel Desktop - English (Great Britain)
-- Name: Microsoft David Desktop, Culture: en-US, Gender: Male, Age: Adult, Desc: Microsoft David Desktop - English (United States)
-- Name: Microsoft Zira Desktop, Culture: en-US, Gender: Female, Age: Adult, Desc: Microsoft Zira Desktop - English (United States)
-- Name: Microsoft Hedda Desktop, Culture: de-DE, Gender: Female, Age: Adult, Desc: Microsoft Hedda Desktop - German
-- Name: Microsoft Helena Desktop, Culture: es-ES, Gender: Female, Age: Adult, Desc: Microsoft Helena Desktop - Spanish (Spain)
-- Name: Microsoft Hortense Desktop, Culture: fr-FR, Gender: Female, Age: Adult, Desc: Microsoft Hortense Desktop - French
-- Name: Microsoft Elsa Desktop, Culture: it-IT, Gender: Female, Age: Adult, Desc: Microsoft Elsa Desktop - Italian (Italy)
-- Name: Microsoft Irina Desktop, Culture: ru-RU, Gender: Female, Age: Adult, Desc: Microsoft Irina Desktop - Russian
-- Name: Microsoft Huihui Desktop, Culture: zh-CN, Gender: Female, Age: Adult, Desc: Microsoft Huihui Desktop - Chinese (Simplified)
--
-- @field #SOUNDTEXT
SOUNDTEXT={
@ -216,8 +225,8 @@ do -- Text-To-Speech
self:SetText(Text)
self:SetDuration(Duration)
self:SetGender()
self:SetCulture()
--self:SetGender()
--self:SetCulture()
-- Debug info:
self:I(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration))
@ -258,9 +267,10 @@ do -- Text-To-Speech
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
--- Set to use a specific voice name.
-- See the list from `DCS-SR-ExternalAudio.exe --help` or if using google see https://cloud.google.com/text-to-speech/docs/voices
-- @param #SOUNDTEXT self
-- @param #string Voice
-- @param #string Voice Voice name. Note that this will overrule `Gender` and `Culture`.
-- @return #SOUNDTEXT self
function SOUNDTEXT:SetVoice(Voice)

View File

@ -0,0 +1,256 @@
--- **Utilities** DCS Simple Text-To-Speech (STTS).
--
--
--
-- @module Utils.STTS
-- @image MOOSE.JPG
--- [DCS Enum world](https://wiki.hoggitworld.com/view/DCS_enum_world)
-- @type STTS
-- @field #string DIRECTORY Path of the SRS directory.
--- Simple Text-To-Speech
--
-- Version 0.4 - Compatible with SRS version 1.9.6.0+
--
-- # DCS Modification Required
--
-- You will need to edit MissionScripting.lua in DCS World/Scripts/MissionScripting.lua and remove the sanitisation.
-- To do this remove all the code below the comment - the line starts "local function sanitizeModule(name)"
-- Do this without DCS running to allow mission scripts to use os functions.
--
-- *You WILL HAVE TO REAPPLY AFTER EVERY DCS UPDATE*
--
-- # USAGE:
--
-- Add this script into the mission as a DO SCRIPT or DO SCRIPT FROM FILE to initialise it
-- Make sure to edit the STTS.SRS_PORT and STTS.DIRECTORY to the correct values before adding to the mission.
-- Then its as simple as calling the correct function in LUA as a DO SCRIPT or in your own scripts.
--
-- Example calls:
--
-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2)
--
-- Arguments in order are:
--
-- * Message to say, make sure not to use a newline (\n) !
-- * Frequency in MHz
-- * Modulation - AM/FM
-- * Volume - 1.0 max, 0.5 half
-- * Name of the transmitter - ATC, RockFM etc
-- * Coalition - 0 spectator, 1 red 2 blue
-- * OPTIONAL - Vec3 Point i.e Unit.getByName("A UNIT"):getPoint() - needs Vec3 for Height! OR null if not needed
-- * OPTIONAL - Speed -10 to +10
-- * OPTIONAL - Gender male, female or neuter
-- * OPTIONAL - Culture - en-US, en-GB etc
-- * OPTIONAL - Voice - a specfic voice by name. Run DCS-SR-ExternalAudio.exe with --help to get the ones you can use on the command line
-- * OPTIONAL - Google TTS - Switch to Google Text To Speech - Requires STTS.GOOGLE_CREDENTIALS path and Google project setup correctly
--
--
-- ## Example
--
-- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only
--
-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,null,-5,"male","en-GB")
--
-- ## Example
--
--This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only centered on the position of the Unit called "A UNIT"
--
-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,Unit.getByName("A UNIT"):getPoint(),-5,"male","en-GB")
--
-- Arguments in order are:
--
-- * FULL path to the MP3 OR OGG to play
-- * Frequency in MHz - to use multiple separate with a comma - Number of frequencies MUST match number of Modulations
-- * Modulation - AM/FM - to use multiple
-- * Volume - 1.0 max, 0.5 half
-- * Name of the transmitter - ATC, RockFM etc
-- * Coalition - 0 spectator, 1 red 2 blue
--
-- ## Example
--
-- This will play that MP3 on 255MHz AM & 31 FM at half volume with a client called "Multiple" and to Spectators only
--
-- STTS.PlayMP3("C:\\Users\\Ciaran\\Downloads\\PR-Music.mp3","255,31","AM,FM","0.5","Multiple",0)
--
-- @field #STTS
STTS={
ClassName="STTS",
DIRECTORY="",
SRS_PORT=5002,
GOOGLE_CREDENTIALS="C:\\Users\\Ciaran\\Downloads\\googletts.json",
EXECUTABLE="DCS-SR-ExternalAudio.exe",
}
--- FULL Path to the FOLDER containing DCS-SR-ExternalAudio.exe - EDIT TO CORRECT FOLDER
STTS.DIRECTORY = "D:/DCS/_SRS"
--- LOCAL SRS PORT - DEFAULT IS 5002
STTS.SRS_PORT = 5002
--- Google credentials file
STTS.GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json"
--- DONT CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING
STTS.EXECUTABLE = "DCS-SR-ExternalAudio.exe"
--- Function for UUID.
function STTS.uuid()
local random = math.random
local template ='yxxx-xxxxxxxxxxxx'
return string.gsub(template, '[xy]', function (c)
local v = (c == 'x') and random(0, 0xf) or random(8, 0xb)
return string.format('%x', v)
end)
end
--- Round a number.
-- @param #number x Number.
-- @param #number n Precision.
function STTS.round(x, n)
n = math.pow(10, n or 0)
x = x * n
if x >= 0 then x = math.floor(x + 0.5) else x = math.ceil(x - 0.5) end
return x / n
end
--- Function returns estimated speech time in seconds.
-- Assumptions for time calc: 100 Words per min, avarage of 5 letters for english word so
--
-- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second
--
-- So lengh of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function:
--
-- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
--
function STTS.getSpeechTime(length,speed,isGoogle)
local maxRateRatio = 3
speed = speed or 1.0
isGoogle = isGoogle or false
local speedFactor = 1.0
if isGoogle then
speedFactor = speed
else
if speed ~= 0 then
speedFactor = math.abs(speed) * (maxRateRatio - 1) / 10 + 1
end
if speed < 0 then
speedFactor = 1/speedFactor
end
end
local wpm = math.ceil(100 * speedFactor)
local cps = math.floor((wpm * 5)/60)
if type(length) == "string" then
length = string.len(length)
end
return math.ceil(length/cps)
end
--- Text to speech function.
function STTS.TextToSpeech(message, freqs, modulations, volume, name, coalition, point, speed, gender, culture, voice, googleTTS)
if os == nil or io == nil then
env.info("[DCS-STTS] LUA modules os or io are sanitized. skipping. ")
return
end
speed = speed or 1
gender = gender or "female"
culture = culture or ""
voice = voice or ""
coalition=coalition or "0"
name=name or "ROBOT"
volume=1
speed=1
message = message:gsub("\"","\\\"")
local cmd = string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs or "305", modulations or "AM", coalition, STTS.SRS_PORT, name)
if voice ~= "" then
cmd = cmd .. string.format(" -V \"%s\"",voice)
else
if culture ~= "" then
cmd = cmd .. string.format(" -l %s",culture)
end
if gender ~= "" then
cmd = cmd .. string.format(" -g %s",gender)
end
end
if googleTTS == true then
cmd = cmd .. string.format(" -G \"%s\"",STTS.GOOGLE_CREDENTIALS)
end
if speed ~= 1 then
cmd = cmd .. string.format(" -s %s",speed)
end
if volume ~= 1.0 then
cmd = cmd .. string.format(" -v %s",volume)
end
if point and type(point) == "table" and point.x then
local lat, lon, alt = coord.LOtoLL(point)
lat = STTS.round(lat,4)
lon = STTS.round(lon,4)
alt = math.floor(alt)
cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt)
end
cmd = cmd ..string.format(" -t \"%s\"",message)
if string.len(cmd) > 255 then
local filename = os.getenv('TMP') .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat"
local script = io.open(filename,"w+")
script:write(cmd .. " && exit" )
script:close()
cmd = string.format("\"%s\"",filename)
timer.scheduleFunction(os.remove, filename, timer.getTime() + 1)
end
if string.len(cmd) > 255 then
env.info("[DCS-STTS] - cmd string too long")
env.info("[DCS-STTS] TextToSpeech Command :\n" .. cmd.."\n")
end
os.execute(cmd)
return STTS.getSpeechTime(message,speed,googleTTS)
end
--- Play mp3 function.
-- @param #string pathToMP3 Path to the sound file.
-- @param #string freqs Frequencies, e.g. "305, 256".
-- @param #string modulations Modulations, e.g. "AM, FM".
-- @param #string volume Volume, e.g. "0.5".
function STTS.PlayMP3(pathToMP3, freqs, modulations, volume, name, coalition, point)
local cmd = string.format("start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h",
STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs or "305", modulations or "AM", coalition or "0", STTS.SRS_PORT, name or "ROBOT", volume or "1")
if point and type(point) == "table" and point.x then
local lat, lon, alt = coord.LOtoLL(point)
lat = STTS.round(lat,4)
lon = STTS.round(lon,4)
alt = math.floor(alt)
cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt)
end
env.info("[DCS-STTS] MP3/OGG Command :\n" .. cmd.."\n")
os.execute(cmd)
end

View File

@ -4,8 +4,10 @@ Utilities/Utils.lua
Utilities/Enums.lua
Utilities/Profiler.lua
Utilities/Templates.lua
Utilities/STTS.lua
Core/Base.lua
Core/Beacon.lua
Core/UserFlag.lua
Core/Report.lua
Core/Scheduler.lua
@ -111,7 +113,7 @@ Actions/Act_Account.lua
Actions/Act_Assist.lua
Sound/UserSound.lua
Sound/SoundFile.lua
Sound/SoundOutput.lua
Sound/Radio.lua
Sound/RadioQueue.lua
Sound/RadioSpeech.lua