diff --git a/AUTHORS b/AUTHORS index 1b1b3c47da..4e351af1d0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -31,6 +31,7 @@ Also thanks to: * Daniel Derejvanik (Harisson) * Danny Keary (Dan9550) * Erasmus Schroder (rasco) + * Fahrradkette * Frank Razenberg (zzattack) * Igor Popov (ihptru) * Iran diff --git a/OpenRA.Game/GameRules/Settings.cs b/OpenRA.Game/GameRules/Settings.cs index 7d6d88ba2f..7cea47e7d3 100644 --- a/OpenRA.Game/GameRules/Settings.cs +++ b/OpenRA.Game/GameRules/Settings.cs @@ -104,7 +104,8 @@ namespace OpenRA.GameRules public bool Repeat = false; public bool MapMusic = true; public string Engine = "AL"; - + public string Device = null; + public SoundCashTicks SoundCashTickType = SoundCashTicks.Extreme; } diff --git a/OpenRA.Game/Sound.cs b/OpenRA.Game/Sound.cs index 5dc5e4f3d4..83b709ff45 100644 --- a/OpenRA.Game/Sound.cs +++ b/OpenRA.Game/Sound.cs @@ -77,6 +77,20 @@ namespace OpenRA video = null; } + public static SoundDevice[] AvailableDevices() + { + var defaultDevices = new [] + { + new SoundDevice("AL", null, "Default Output"), + new SoundDevice("Null", null, "Output Disabled") + }; + + var alDevices = OpenAlSoundEngine.AvailableDevices() + .Select(d => new SoundDevice("AL", d, d)); + + return defaultDevices.Concat(alDevices).ToArray(); + } + public static void SetListenerPosition(float2 position) { soundEngine.SetListenerPosition(position); } static ISound Play(Player player, string name, bool headRelative, WPos pos, float volumeModifier) @@ -348,6 +362,24 @@ namespace OpenRA void SetSoundVolume(float volume, ISound music, ISound video); } + public class SoundDevice + { + public readonly string Engine; + public readonly string Device; + public readonly string Label; + + public SoundDevice(string engine, string device, string label) + { + Engine = engine; + Device = device; + Label = label; + + // Limit label to 32 characters + if (Label.Length > 32) + Label = "..." + Label.Substring(Label.Length - 32); + } + } + interface ISoundSource { } public interface ISound @@ -372,13 +404,51 @@ namespace OpenRA Dictionary sourcePool = new Dictionary(); const int POOL_SIZE = 32; + static string[] QueryDevices(string label, int type) + { + // Clear error bit + Al.alGetError(); + + var devices = Alc.alcGetStringv(IntPtr.Zero, type); + if (Al.alGetError() != Al.AL_NO_ERROR) + { + Log.Write("sound", "Failed to query OpenAL device list using {0}", label); + return new string[] {}; + } + + return devices; + } + + public static string[] AvailableDevices() + { + // Returns all devices under windows vista and newer + if (Alc.alcIsExtensionPresent(IntPtr.Zero, "ALC_ENUMERATE_ALL_EXT") == Alc.ALC_TRUE) + return QueryDevices("ALC_ENUMERATE_ALL_EXT", Alc.ALC_ALL_DEVICES_SPECIFIER); + + if (Alc.alcIsExtensionPresent(IntPtr.Zero, "ALC_ENUMERATION_EXT") == Alc.ALC_TRUE) + return QueryDevices("ALC_ENUMERATION_EXT", Alc.ALC_DEVICE_SPECIFIER); + + return new string[] {}; + } + public OpenAlSoundEngine() { Console.WriteLine("Using OpenAL sound engine"); - //var str = Alc.alcGetString(IntPtr.Zero, Alc.ALC_DEFAULT_DEVICE_SPECIFIER); - var dev = Alc.alcOpenDevice(null); + + if (Game.Settings.Sound.Device != null) + Console.WriteLine("Using device `{0}`", Game.Settings.Sound.Device); + else + Console.WriteLine("Using default device"); + + var dev = Alc.alcOpenDevice(Game.Settings.Sound.Device); if (dev == IntPtr.Zero) - throw new InvalidOperationException("Can't create OpenAL device"); + { + Console.WriteLine("Failed to open device. Falling back to default"); + dev = Alc.alcOpenDevice(null); + if (dev == IntPtr.Zero) + throw new InvalidOperationException("Can't create OpenAL device"); + } + var ctx = Alc.alcCreateContext(dev, IntPtr.Zero); if (ctx == IntPtr.Zero) throw new InvalidOperationException("Can't create OpenAL context"); diff --git a/OpenRA.Mods.Cnc/Widgets/Logic/CncSettingsLogic.cs b/OpenRA.Mods.Cnc/Widgets/Logic/CncSettingsLogic.cs index 27113f545e..392e15c176 100644 --- a/OpenRA.Mods.Cnc/Widgets/Logic/CncSettingsLogic.cs +++ b/OpenRA.Mods.Cnc/Widgets/Logic/CncSettingsLogic.cs @@ -26,6 +26,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic { enum PanelType { General, Input } + SoundDevice soundDevice; PanelType Settings = PanelType.General; ColorPreviewManagerWidget colorPreview; World world; @@ -112,6 +113,13 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic shellmapMusicCheckbox.IsChecked = () => soundSettings.MapMusic; shellmapMusicCheckbox.OnClick = () => soundSettings.MapMusic ^= true; + var devices = Sound.AvailableDevices(); + soundDevice = devices.FirstOrDefault(d => d.Engine == soundSettings.Engine && d.Device == soundSettings.Device) ?? devices.First(); + + var audioDeviceDropdown = generalPane.Get("AUDIO_DEVICE"); + audioDeviceDropdown.OnMouseDown = _ => ShowAudioDeviceDropdown(audioDeviceDropdown, soundSettings, devices); + audioDeviceDropdown.GetText = () => soundDevice.Label; + // Input pane var inputPane = panel.Get("INPUT_CONTROLS"); inputPane.IsVisible = () => Settings == PanelType.Input; @@ -147,6 +155,8 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic int.TryParse(windowWidth.Text, out x); int.TryParse(windowHeight.Text, out y); graphicsSettings.WindowedSize = new int2(x,y); + soundSettings.Device = soundDevice.Device; + soundSettings.Engine = soundDevice.Engine; Game.Settings.Save(); Ui.CloseWindow(); onExit(); @@ -194,5 +204,24 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 500, options.Keys, setupItem); return true; } + + bool ShowAudioDeviceDropdown(DropDownButtonWidget dropdown, SoundSettings s, SoundDevice[] devices) + { + var i = 0; + var options = devices.ToDictionary(d => (i++).ToString(), d => d); + + Func setupItem = (o, itemTemplate) => + { + var item = ScrollItemWidget.Setup(itemTemplate, + () => soundDevice == options[o], + () => soundDevice = options[o]); + + item.Get("LABEL").GetText = () => options[o].Label; + return item; + }; + + dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 500, options.Keys, setupItem); + return true; + } } } diff --git a/OpenRA.Mods.RA/Widgets/Logic/SettingsMenuLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/SettingsMenuLogic.cs index 17d13e4465..bf23d15a6a 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/SettingsMenuLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/SettingsMenuLogic.cs @@ -20,6 +20,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic public class SettingsMenuLogic { Widget bg; + SoundDevice soundDevice; [ObjectCreator.UseCtor] public SettingsMenuLogic(Action onExit) @@ -103,12 +104,13 @@ namespace OpenRA.Mods.RA.Widgets.Logic mapMusicCheckbox.IsChecked = () => Game.Settings.Sound.MapMusic; mapMusicCheckbox.OnClick = () => Game.Settings.Sound.MapMusic ^= true; - var soundEngineDropdown = audio.Get("SOUND_ENGINE"); - soundEngineDropdown.OnMouseDown = _ => ShowSoundEngineDropdown(soundEngineDropdown, soundSettings); - soundEngineDropdown.GetText = () => soundSettings.Engine == "AL" ? - "OpenAL" : soundSettings.Engine == "Null" ? "None" : "OpenAL"; + var devices = Sound.AvailableDevices(); + soundDevice = devices.FirstOrDefault(d => d.Engine == soundSettings.Engine && d.Device == soundSettings.Device) ?? devices.First(); + + var audioDeviceDropdown = audio.Get("AUDIO_DEVICE"); + audioDeviceDropdown.OnMouseDown = _ => ShowAudioDeviceDropdown(audioDeviceDropdown, soundSettings, devices); + audioDeviceDropdown.GetText = () => soundDevice.Label; - // Display var display = bg.Get("DISPLAY_PANE"); var gs = Game.Settings.Graphics; @@ -244,6 +246,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic int.TryParse(windowHeight.Text, out y); gs.WindowedSize = new int2(x,y); int.TryParse(maxFrameRate.Text, out gs.MaxFramerate); + soundSettings.Device = soundDevice.Device; + soundSettings.Engine = soundDevice.Engine; Game.Settings.Save(); Ui.CloseWindow(); onExit(); @@ -325,7 +329,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic textBox.OnEnterKey = () => { textBox.YieldKeyboardFocus(); return true; }; } - public static bool ShowRendererDropdown(DropDownButtonWidget dropdown, GraphicSettings s) + static bool ShowRendererDropdown(DropDownButtonWidget dropdown, GraphicSettings s) { var options = new Dictionary() { @@ -346,20 +350,18 @@ namespace OpenRA.Mods.RA.Widgets.Logic return true; } - public static bool ShowSoundEngineDropdown(DropDownButtonWidget dropdown, SoundSettings s) + bool ShowAudioDeviceDropdown(DropDownButtonWidget dropdown, SoundSettings s, SoundDevice[] devices) { - var options = new Dictionary() - { - { "OpenAL", "AL" }, - { "None", "Null" }, - }; + var i = 0; + var options = devices.ToDictionary(d => (i++).ToString(), d => d); Func setupItem = (o, itemTemplate) => { var item = ScrollItemWidget.Setup(itemTemplate, - () => s.Engine == options[o], - () => s.Engine = options[o]); - item.Get("LABEL").GetText = () => o; + () => soundDevice == options[o], + () => soundDevice = options[o]); + + item.Get("LABEL").GetText = () => options[o].Label; return item; }; diff --git a/mods/cnc/chrome/settings.yaml b/mods/cnc/chrome/settings.yaml index 6d92e735c1..e1a9da1647 100644 --- a/mods/cnc/chrome/settings.yaml +++ b/mods/cnc/chrome/settings.yaml @@ -50,30 +50,44 @@ Container@SETTINGS_PANEL: Y:6 Width:PARENT_RIGHT-35 Height:PARENT_BOTTOM-12 + Checkbox@SHOW_SHELLMAP: + X:15 + Y:80 + Width:200 + Height:20 + Font:Regular + Text:Show Shellmap + Checkbox@SHELLMAP_MUSIC: + X:15 + Y:110 + Width:200 + Height:20 + Font:Regular + Text:Shellmap Music Label@DEBUG_TITLE: X:15 - Y:100 + Y:150 Width:340 Font:Bold Text:Debug Align:Center Checkbox@PERFTEXT_CHECKBOX: X:15 - Y:120 + Y:170 Width:300 Height:20 Font:Regular Text:Show Performance Text Checkbox@PERFGRAPH_CHECKBOX: X:15 - Y:150 + Y:200 Width:300 Height:20 Font:Regular Text:Show Performance Graph Checkbox@CHECKUNSYNCED_CHECKBOX: X:15 - Y:180 + Y:230 Width:300 Height:20 Font:Regular @@ -130,7 +144,7 @@ Container@SETTINGS_PANEL: MaxLength:5 Label@VIDEO_DESC: X:375 - Y:65 + Y:68 Width:340 Height:25 Font:Tiny @@ -138,58 +152,65 @@ Container@SETTINGS_PANEL: Text:Mode/Resolution changes will be applied after the game is restarted Checkbox@PIXELDOUBLE_CHECKBOX: X:375 - Y:90 + Y:110 Width:200 Height:20 Font:Regular Text:Enable Pixel Doubling - Checkbox@SHOW_SHELLMAP: - X:375 - Y:120 - Width:200 - Height:20 - Font:Regular - Text:Show Shellmap Label@AUDIO_TITLE: X:375 - Y:160 + Y:150 Width:340 Font:Bold Text:Sound Align:Center Label@SOUND_LABEL: X:375 - Y:175 + Y:164 Width:95 Height:25 Align:Right Text:Sound Volume: Slider@SOUND_SLIDER: X:475 - Y:180 + Y:170 Width:240 Height:20 Ticks:5 Label@MUSIC_LABEL: X:375 - Y:205 + Y:194 Width:95 Height:25 Align:Right Text:Music Volume: Slider@MUSIC_SLIDER: X:475 - Y:210 + Y:200 Width:240 Height:20 Ticks:5 - Checkbox@SHELLMAP_MUSIC: + Label@AUDIO_DEVICE_LABEL: X:375 - Y:240 - Width:200 + Y:229 + Width:75 Height:20 + Text:Audio Device: + DropDownButton@AUDIO_DEVICE: + X:475 + Y:230 + Width:240 + Height:25 Font:Regular - Text:Shellmap Music + Text:Default Device + Label@AUDIO_DESC: + X:375 + Y:258 + Width:340 + Height:25 + Font:Tiny + Align:Center + Text:Device changes will be applied after the game is restarted Background@INPUT_CONTROLS: Width:740 Height:290 diff --git a/mods/ra/chrome/settings.yaml b/mods/ra/chrome/settings.yaml index 8ccca04add..bb65bed75a 100644 --- a/mods/ra/chrome/settings.yaml +++ b/mods/ra/chrome/settings.yaml @@ -182,19 +182,24 @@ Background@SETTINGS_MENU: Width:200 Height:20 Text: Autoplay Music After Map Load - Label@SOUND_ENGINE_LABEL: + Label@AUDIO_DEVICE_LABEL: X:0 Y:150 Width:75 Height:25 - Text:Sound Engine: - DropDownButton@SOUND_ENGINE: + Text:Audio Device: + DropDownButton@AUDIO_DEVICE: X:100 Y:150 - Width:120 + Width:250 Height:25 Font:Regular - Text:OpenAL + Label@AUDIO_DESC: + Y:175 + Width:200 + Height:25 + Font:Tiny + Text:Device changes will be applied after the game is restarted. Container@DISPLAY_PANE: X:37 Y:100