using System; using System.Linq; using System.Threading.Tasks; using Swan; namespace Unosquare.RaspberryIO.Computer { /// /// Settings for audio device. /// public class AudioSettings : SingletonBase { private const String DefaultControlName = "PCM"; private const Int32 DefaultCardNumber = 0; private readonly String[] _errorMess = { "Invalid", "Unable" }; /// /// Gets the current audio state. /// /// The card number. /// Name of the control. /// An object. /// Invalid command, card number or control name. public async Task GetState(Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) { String volumeInfo = await ProcessRunner.GetProcessOutputAsync("amixer", $"-c {cardNumber} get {controlName}").ConfigureAwait(false); String[] lines = volumeInfo.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); if(!lines.Any()) { throw new InvalidOperationException("Invalid command."); } if(this._errorMess.Any(x => lines[0].Contains(x))) { throw new InvalidOperationException(lines[0]); } String volumeLine = lines.FirstOrDefault(x => x.Trim().StartsWith("Mono:", StringComparison.OrdinalIgnoreCase)); if(volumeLine == null) { throw new InvalidOperationException("Unexpected output from 'amixer'."); } String[] sections = volumeLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); Int32 level = Int32.Parse(sections[3][1..^2], System.Globalization.NumberFormatInfo.InvariantInfo); Single decibels = Single.Parse(sections[4][1..^3], System.Globalization.NumberFormatInfo.InvariantInfo); Boolean isMute = sections[5].Equals("[off]", StringComparison.CurrentCultureIgnoreCase); return new AudioState(cardNumber, controlName, level, decibels, isMute); } /// /// Sets the volume percentage. /// /// The percentage level. /// The card number. /// Name of the control. /// A representing the asynchronous operation. /// Invalid card number or control name. public Task SetVolumePercentage(Int32 level, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) => SetAudioCommand($"{level}%", cardNumber, controlName); /// /// Sets the volume by decibels. /// /// The decibels. /// The card number. /// Name of the control. /// A representing the asynchronous operation. /// Invalid card number or control name. public Task SetVolumeByDecibels(Single decibels, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) => SetAudioCommand($"{decibels}dB", cardNumber, controlName); /// /// Increments the volume by decibels. /// /// The decibels to increment or decrement. /// The card number. /// Name of the control. /// A representing the asynchronous operation. /// Invalid card number or control name. public Task IncrementVolume(Single decibels, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) => SetAudioCommand($"{decibels}dB{(decibels < 0 ? "-" : "+")}", cardNumber, controlName); /// /// Toggles the mute state. /// /// if set to true, mutes the audio. /// The card number. /// Name of the control. /// A representing the asynchronous operation. /// Invalid card number or control name. public Task ToggleMute(Boolean mute, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) => SetAudioCommand(mute ? "mute" : "unmute", cardNumber, controlName); private static async Task SetAudioCommand(String command, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) { String taskResult = await ProcessRunner.GetProcessOutputAsync("amixer", $"-q -c {cardNumber} -- set {controlName} {command}").ConfigureAwait(false); if(!String.IsNullOrWhiteSpace(taskResult)) { throw new InvalidOperationException(taskResult.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).First()); } return taskResult; } } }