Draft audio spectrum visualizer.
authorStephaneLenclud
Mon, 02 Jan 2017 18:43:45 +0100
changeset 273e5f85a895a62
parent 272 10de0c7c2fed
child 274 920fea7a6427
Draft audio spectrum visualizer.
Server/FormMain.cs
Server/SharpDisplayManager.csproj
Server/Spectrum/BasicSpectrumProvider.cs
Server/Spectrum/ISpectrumProvider.cs
Server/Spectrum/LineSpectrum.cs
Server/Spectrum/ScalingStrategy.cs
Server/Spectrum/SpectrumBase.cs
     1.1 --- a/Server/FormMain.cs	Mon Jan 02 15:50:50 2017 +0100
     1.2 +++ b/Server/FormMain.cs	Mon Jan 02 18:43:45 2017 +0100
     1.3 @@ -37,8 +37,14 @@
     1.4  using System.Runtime.InteropServices;
     1.5  using System.Security;
     1.6  //CSCore
     1.7 +using CSCore;
     1.8 +using CSCore.Win32;
     1.9 +using CSCore.DSP;
    1.10 +using CSCore.Streams;
    1.11  using CSCore.CoreAudioAPI;
    1.12 -
    1.13 +using CSCore.SoundIn;
    1.14 +// Visualization
    1.15 +using Visualization;
    1.16  // CEC
    1.17  using CecSharp;
    1.18  //Network
    1.19 @@ -49,7 +55,7 @@
    1.20  using MiniDisplayInterop;
    1.21  using SharpLib.Display;
    1.22  using Ear = SharpLib.Ear;
    1.23 -using CSCore.Win32;
    1.24 +
    1.25  
    1.26  namespace SharpDisplayManager
    1.27  {
    1.28 @@ -105,9 +111,15 @@
    1.29          //Function pointer for pixel Y coordinate intercept
    1.30          CoordinateTranslationDelegate iScreenY;
    1.31          //CSCore
    1.32 +        // Volume management
    1.33          private MMDeviceEnumerator iMultiMediaDeviceEnumerator;
    1.34          private MMDevice iMultiMediaDevice;
    1.35          private AudioEndpointVolume iAudioEndpointVolume;
    1.36 +        // Audio visualization
    1.37 +        private WasapiCapture iSoundIn;
    1.38 +        private IWaveSource iWaveSource;
    1.39 +        private LineSpectrum iLineSpectrum;
    1.40 +
    1.41          //Network
    1.42          private NetworkManager iNetworkManager;
    1.43  
    1.44 @@ -641,6 +653,106 @@
    1.45          /// <summary>
    1.46          /// 
    1.47          /// </summary>
    1.48 +        private void StartAudioVisualization()
    1.49 +        {
    1.50 +            StopAudioVisualization();
    1.51 +            //Open the default device 
    1.52 +            iSoundIn = new WasapiLoopbackCapture();
    1.53 +            //Our loopback capture opens the default render device by default so the following is not needed
    1.54 +            //iSoundIn.Device = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Console);
    1.55 +            iSoundIn.Initialize();
    1.56 +
    1.57 +            SoundInSource soundInSource = new SoundInSource(iSoundIn);
    1.58 +            ISampleSource source = soundInSource.ToSampleSource();
    1.59 +
    1.60 +            const FftSize fftSize = FftSize.Fft4096;
    1.61 +            //create a spectrum provider which provides fft data based on some input
    1.62 +            BasicSpectrumProvider spectrumProvider = new BasicSpectrumProvider(source.WaveFormat.Channels, source.WaveFormat.SampleRate, fftSize);
    1.63 +
    1.64 +            //linespectrum and voiceprint3dspectrum used for rendering some fft data
    1.65 +            //in oder to get some fft data, set the previously created spectrumprovider 
    1.66 +            iLineSpectrum = new LineSpectrum(fftSize)
    1.67 +            {
    1.68 +                SpectrumProvider = spectrumProvider,
    1.69 +                UseAverage = true,
    1.70 +                BarCount = 32,
    1.71 +                BarSpacing = 0,
    1.72 +                IsXLogScale = true,
    1.73 +                ScalingStrategy = ScalingStrategy.Sqrt
    1.74 +            };
    1.75 +
    1.76 +
    1.77 +            //the SingleBlockNotificationStream is used to intercept the played samples
    1.78 +            var notificationSource = new SingleBlockNotificationStream(source);
    1.79 +            //pass the intercepted samples as input data to the spectrumprovider (which will calculate a fft based on them)
    1.80 +            notificationSource.SingleBlockRead += (s, a) => spectrumProvider.Add(a.Left, a.Right);
    1.81 +
    1.82 +            iWaveSource = notificationSource.ToWaveSource(16);
    1.83 +
    1.84 +
    1.85 +            // We need to read from our source otherwise SingleBlockRead is never called and our spectrum provider is not populated
    1.86 +            byte[] buffer = new byte[iWaveSource.WaveFormat.BytesPerSecond / 2];
    1.87 +            soundInSource.DataAvailable += (s, aEvent) =>
    1.88 +            {
    1.89 +                int read;
    1.90 +                while ((read = iWaveSource.Read(buffer, 0, buffer.Length)) > 0) ;
    1.91 +            };
    1.92 +
    1.93 +
    1.94 +            //Start recording
    1.95 +            iSoundIn.Start();
    1.96 +        }
    1.97 +
    1.98 +        /// <summary>
    1.99 +        /// 
   1.100 +        /// </summary>
   1.101 +        private void StopAudioVisualization()
   1.102 +        {
   1.103 +
   1.104 +            if (iSoundIn != null)
   1.105 +            {
   1.106 +                iSoundIn.Stop();
   1.107 +                iSoundIn.Dispose();
   1.108 +                iSoundIn = null;
   1.109 +            }
   1.110 +            if (iWaveSource != null)
   1.111 +            {
   1.112 +                iWaveSource.Dispose();
   1.113 +                iWaveSource = null;
   1.114 +            }
   1.115 +
   1.116 +        }
   1.117 +
   1.118 +
   1.119 +        /// <summary>
   1.120 +        /// 
   1.121 +        /// </summary>
   1.122 +        private void GenerateAudioVisualization()
   1.123 +        {
   1.124 +            // For demo draft purposes just fetch the firt picture box control and update it with current audio spectrum
   1.125 +            foreach (Control ctrl in iTableLayoutPanel.Controls)
   1.126 +            {
   1.127 +                if (ctrl is PictureBox)
   1.128 +                {
   1.129 +                    PictureBox pb = (PictureBox)ctrl;
   1.130 +                    Image image = pb.Image;
   1.131 +                    var newImage = iLineSpectrum.CreateSpectrumLine(pb.Size, Color.Black, Color.Black, Color.White, false);
   1.132 +                    if (newImage != null)
   1.133 +                    {
   1.134 +                        pb.Image = newImage;
   1.135 +                        if (image != null)
   1.136 +                            image.Dispose();
   1.137 +                    }
   1.138 +
   1.139 +                    break;
   1.140 +                }
   1.141 +            }
   1.142 +        }
   1.143 +
   1.144 +
   1.145 +        /// <summary>
   1.146 +        /// 
   1.147 +        /// </summary>
   1.148          private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
   1.149          {
   1.150              if (this.InvokeRequired)
   1.151 @@ -657,6 +769,7 @@
   1.152                  //Get our master volume
   1.153                  iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
   1.154                  iAudioEndpointVolume = AudioEndpointVolume.FromDevice(iMultiMediaDevice);
   1.155 +                
   1.156                  //Update our label
   1.157                  labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
   1.158  
   1.159 @@ -667,7 +780,9 @@
   1.160                  AudioEndpointVolumeCallback callback = new AudioEndpointVolumeCallback();
   1.161                  callback.NotifyRecived += OnVolumeNotificationThreadSafe;
   1.162                  // Do we need to unregister?
   1.163 -                iAudioEndpointVolume.RegisterControlChangeNotify(callback);                       
   1.164 +                iAudioEndpointVolume.RegisterControlChangeNotify(callback);
   1.165 +                //
   1.166 +                StartAudioVisualization();
   1.167                  //
   1.168                  trackBarMasterVolume.Enabled = true;
   1.169              }
   1.170 @@ -1105,8 +1220,10 @@
   1.171                  }
   1.172              }
   1.173  
   1.174 +            GenerateAudioVisualization();
   1.175 +
   1.176              //Compute instant FPS
   1.177 -            toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " +
   1.178 +      toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " +
   1.179                                             (1000/iTimerDisplay.Interval).ToString() + " FPS";
   1.180  
   1.181              LastTickTime = NewTickTime;
   1.182 @@ -1441,6 +1558,8 @@
   1.183  
   1.184          private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
   1.185          {
   1.186 +            //TODO: discard other CSCore audio objects
   1.187 +            StopAudioVisualization();
   1.188              iCecManager.Stop();
   1.189              iNetworkManager.Dispose();
   1.190              CloseDisplayConnection();
     2.1 --- a/Server/SharpDisplayManager.csproj	Mon Jan 02 15:50:50 2017 +0100
     2.2 +++ b/Server/SharpDisplayManager.csproj	Mon Jan 02 18:43:45 2017 +0100
     2.3 @@ -174,11 +174,15 @@
     2.4      <Compile Include="Actions\ActionCecUserControlReleased.cs" />
     2.5      <Compile Include="Actions\ActionDisplayMessage.cs" />
     2.6      <Compile Include="Actions\ActionHarmonyCommand.cs" />
     2.7 +    <Compile Include="Spectrum\BasicSpectrumProvider.cs" />
     2.8      <Compile Include="CbtHook.cs" />
     2.9      <Compile Include="CecClient.cs" />
    2.10      <Compile Include="ConsumerElectronicControl.cs" />
    2.11      <Compile Include="ClientData.cs" />
    2.12      <Compile Include="EarManager.cs" />
    2.13 +    <Compile Include="Spectrum\ISpectrumProvider.cs" />
    2.14 +    <Compile Include="Spectrum\LineSpectrum.cs" />
    2.15 +    <Compile Include="Spectrum\ScalingStrategy.cs" />
    2.16      <Compile Include="Secure.cs" />
    2.17      <Compile Include="Events\EventHid.cs" />
    2.18      <Compile Include="FormEditObject.cs">
    2.19 @@ -223,6 +227,7 @@
    2.20      <Compile Include="RichTextBoxTraceListener.cs" />
    2.21      <Compile Include="Session.cs" />
    2.22      <Compile Include="Settings.cs" />
    2.23 +    <Compile Include="Spectrum\SpectrumBase.cs" />
    2.24      <Compile Include="StartupManager.cs" />
    2.25      <Compile Include="TaskScheduler.cs" />
    2.26      <Compile Include="Win32API.cs" />
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/Server/Spectrum/BasicSpectrumProvider.cs	Mon Jan 02 18:43:45 2017 +0100
     3.3 @@ -0,0 +1,54 @@
     3.4 +using System;
     3.5 +using System.Collections.Generic;
     3.6 +using CSCore.DSP;
     3.7 +
     3.8 +namespace Visualization
     3.9 +{
    3.10 +    /// <summary>
    3.11 +    ///     BasicSpectrumProvider
    3.12 +    /// </summary>
    3.13 +    public class BasicSpectrumProvider : FftProvider, ISpectrumProvider
    3.14 +    {
    3.15 +        private readonly int _sampleRate;
    3.16 +        private readonly List<object> _contexts = new List<object>();
    3.17 +
    3.18 +        public BasicSpectrumProvider(int channels, int sampleRate, FftSize fftSize)
    3.19 +            : base(channels, fftSize)
    3.20 +        {
    3.21 +            if (sampleRate <= 0)
    3.22 +                throw new ArgumentOutOfRangeException("sampleRate");
    3.23 +            _sampleRate = sampleRate;
    3.24 +        }
    3.25 +
    3.26 +        public int GetFftBandIndex(float frequency)
    3.27 +        {
    3.28 +            int fftSize = (int)FftSize;
    3.29 +            double f = _sampleRate / 2.0;
    3.30 +            // ReSharper disable once PossibleLossOfFraction
    3.31 +            return (int)((frequency / f) * (fftSize / 2));
    3.32 +        }
    3.33 +
    3.34 +        public bool GetFftData(float[] fftResultBuffer, object context)
    3.35 +        {
    3.36 +            if (_contexts.Contains(context))
    3.37 +                return false;
    3.38 +
    3.39 +            _contexts.Add(context);
    3.40 +            GetFftData(fftResultBuffer);
    3.41 +            return true;
    3.42 +        }
    3.43 +
    3.44 +        public override void Add(float[] samples, int count)
    3.45 +        {
    3.46 +            base.Add(samples, count);
    3.47 +            if (count > 0)
    3.48 +                _contexts.Clear();
    3.49 +        }
    3.50 +
    3.51 +        public override void Add(float left, float right)
    3.52 +        {
    3.53 +            base.Add(left, right);
    3.54 +            _contexts.Clear();
    3.55 +        }
    3.56 +    }
    3.57 +}
    3.58 \ No newline at end of file
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/Server/Spectrum/ISpectrumProvider.cs	Mon Jan 02 18:43:45 2017 +0100
     4.3 @@ -0,0 +1,8 @@
     4.4 +namespace Visualization
     4.5 +{
     4.6 +    public interface ISpectrumProvider
     4.7 +    {
     4.8 +        bool GetFftData(float[] fftBuffer, object context);
     4.9 +        int GetFftBandIndex(float frequency);
    4.10 +    }
    4.11 +}
    4.12 \ No newline at end of file
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/Server/Spectrum/LineSpectrum.cs	Mon Jan 02 18:43:45 2017 +0100
     5.3 @@ -0,0 +1,166 @@
     5.4 +using System;
     5.5 +using System.ComponentModel;
     5.6 +using System.Drawing;
     5.7 +using System.Drawing.Drawing2D;
     5.8 +using System.Drawing.Text;
     5.9 +using CSCore.DSP;
    5.10 +
    5.11 +namespace Visualization
    5.12 +{
    5.13 +    public class LineSpectrum : SpectrumBase
    5.14 +    {
    5.15 +        private int _barCount;
    5.16 +        private double _barSpacing;
    5.17 +        private double _barWidth;
    5.18 +        private Size _currentSize;
    5.19 +
    5.20 +        public LineSpectrum(FftSize fftSize)
    5.21 +        {
    5.22 +            FftSize = fftSize;
    5.23 +        }
    5.24 +
    5.25 +        [Browsable(false)]
    5.26 +        public double BarWidth
    5.27 +        {
    5.28 +            get { return _barWidth; }
    5.29 +        }
    5.30 +
    5.31 +        public double BarSpacing
    5.32 +        {
    5.33 +            get { return _barSpacing; }
    5.34 +            set
    5.35 +            {
    5.36 +                if (value < 0)
    5.37 +                    throw new ArgumentOutOfRangeException("value");
    5.38 +                _barSpacing = value;
    5.39 +                UpdateFrequencyMapping();
    5.40 +
    5.41 +                RaisePropertyChanged("BarSpacing");
    5.42 +                RaisePropertyChanged("BarWidth");
    5.43 +            }
    5.44 +        }
    5.45 +
    5.46 +        public int BarCount
    5.47 +        {
    5.48 +            get { return _barCount; }
    5.49 +            set
    5.50 +            {
    5.51 +                if (value <= 0)
    5.52 +                    throw new ArgumentOutOfRangeException("value");
    5.53 +                _barCount = value;
    5.54 +                SpectrumResolution = value;
    5.55 +                UpdateFrequencyMapping();
    5.56 +
    5.57 +                RaisePropertyChanged("BarCount");
    5.58 +                RaisePropertyChanged("BarWidth");
    5.59 +            }
    5.60 +        }
    5.61 +
    5.62 +        [BrowsableAttribute(false)]
    5.63 +        public Size CurrentSize
    5.64 +        {
    5.65 +            get { return _currentSize; }
    5.66 +            protected set
    5.67 +            {
    5.68 +                _currentSize = value;
    5.69 +                RaisePropertyChanged("CurrentSize");
    5.70 +            }
    5.71 +        }
    5.72 +
    5.73 +        public Bitmap CreateSpectrumLine(Size size, Brush brush, Color background, bool highQuality)
    5.74 +        {
    5.75 +            if (!UpdateFrequencyMappingIfNessesary(size))
    5.76 +                return null;
    5.77 +
    5.78 +            var fftBuffer = new float[(int)FftSize];
    5.79 +
    5.80 +            //get the fft result from the spectrum provider
    5.81 +            if (SpectrumProvider.GetFftData(fftBuffer, this))
    5.82 +            {
    5.83 +                using (var pen = new Pen(brush, (float)_barWidth))
    5.84 +                {
    5.85 +                    var bitmap = new Bitmap(size.Width, size.Height);
    5.86 +
    5.87 +                    using (Graphics graphics = Graphics.FromImage(bitmap))
    5.88 +                    {
    5.89 +                        PrepareGraphics(graphics, highQuality);
    5.90 +                        graphics.Clear(background);
    5.91 +
    5.92 +                        CreateSpectrumLineInternal(graphics, pen, fftBuffer, size);
    5.93 +                    }
    5.94 +
    5.95 +                    return bitmap;
    5.96 +                }
    5.97 +            }
    5.98 +            return null;
    5.99 +        }
   5.100 +
   5.101 +        public Bitmap CreateSpectrumLine(Size size, Color color1, Color color2, Color background, bool highQuality)
   5.102 +        {
   5.103 +            if (!UpdateFrequencyMappingIfNessesary(size))
   5.104 +                return null;
   5.105 +
   5.106 +            using (
   5.107 +                Brush brush = new LinearGradientBrush(new RectangleF(0, 0, (float)_barWidth, size.Height), color2,
   5.108 +                    color1, LinearGradientMode.Vertical))
   5.109 +            {
   5.110 +                return CreateSpectrumLine(size, brush, background, highQuality);
   5.111 +            }
   5.112 +        }
   5.113 +
   5.114 +        private void CreateSpectrumLineInternal(Graphics graphics, Pen pen, float[] fftBuffer, Size size)
   5.115 +        {
   5.116 +            int height = size.Height;
   5.117 +            //prepare the fft result for rendering 
   5.118 +            SpectrumPointData[] spectrumPoints = CalculateSpectrumPoints(height, fftBuffer);
   5.119 +
   5.120 +            //connect the calculated points with lines
   5.121 +            for (int i = 0; i < spectrumPoints.Length; i++)
   5.122 +            {
   5.123 +                SpectrumPointData p = spectrumPoints[i];
   5.124 +                int barIndex = p.SpectrumPointIndex;
   5.125 +                double xCoord = BarSpacing * (barIndex + 1) + (_barWidth * barIndex) + _barWidth / 2;
   5.126 +
   5.127 +                var p1 = new PointF((float)xCoord, height);
   5.128 +                var p2 = new PointF((float)xCoord, height - (float)p.Value - 1);
   5.129 +
   5.130 +                graphics.DrawLine(pen, p1, p2);
   5.131 +            }
   5.132 +        }
   5.133 +
   5.134 +        protected override void UpdateFrequencyMapping()
   5.135 +        {
   5.136 +            _barWidth = Math.Max(((_currentSize.Width - (BarSpacing * (BarCount + 1))) / BarCount), 0.00001);
   5.137 +            base.UpdateFrequencyMapping();
   5.138 +        }
   5.139 +
   5.140 +        private bool UpdateFrequencyMappingIfNessesary(Size newSize)
   5.141 +        {
   5.142 +            if (newSize != CurrentSize)
   5.143 +            {
   5.144 +                CurrentSize = newSize;
   5.145 +                UpdateFrequencyMapping();
   5.146 +            }
   5.147 +
   5.148 +            return newSize.Width > 0 && newSize.Height > 0;
   5.149 +        }
   5.150 +
   5.151 +        private void PrepareGraphics(Graphics graphics, bool highQuality)
   5.152 +        {
   5.153 +            if (highQuality)
   5.154 +            {
   5.155 +                graphics.SmoothingMode = SmoothingMode.AntiAlias;
   5.156 +                graphics.CompositingQuality = CompositingQuality.AssumeLinear;
   5.157 +                graphics.PixelOffsetMode = PixelOffsetMode.Default;
   5.158 +                graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
   5.159 +            }
   5.160 +            else
   5.161 +            {
   5.162 +                graphics.SmoothingMode = SmoothingMode.HighSpeed;
   5.163 +                graphics.CompositingQuality = CompositingQuality.HighSpeed;
   5.164 +                graphics.PixelOffsetMode = PixelOffsetMode.None;
   5.165 +                graphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;
   5.166 +            }
   5.167 +        }
   5.168 +    }
   5.169 +}
   5.170 \ No newline at end of file
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/Server/Spectrum/ScalingStrategy.cs	Mon Jan 02 18:43:45 2017 +0100
     6.3 @@ -0,0 +1,9 @@
     6.4 +namespace Visualization
     6.5 +{
     6.6 +    public enum ScalingStrategy
     6.7 +    {
     6.8 +        Decibel,
     6.9 +        Linear,
    6.10 +        Sqrt
    6.11 +    }
    6.12 +}
    6.13 \ No newline at end of file
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/Server/Spectrum/SpectrumBase.cs	Mon Jan 02 18:43:45 2017 +0100
     7.3 @@ -0,0 +1,228 @@
     7.4 +using System;
     7.5 +using System.Collections.Generic;
     7.6 +using System.ComponentModel;
     7.7 +using System.Diagnostics;
     7.8 +using CSCore;
     7.9 +using CSCore.DSP;
    7.10 +
    7.11 +namespace Visualization
    7.12 +{
    7.13 +    public class SpectrumBase : INotifyPropertyChanged
    7.14 +    {
    7.15 +        private const int ScaleFactorLinear = 9;
    7.16 +        protected const int ScaleFactorSqr = 2;
    7.17 +        protected const double MinDbValue = -90;
    7.18 +        protected const double MaxDbValue = 0;
    7.19 +        protected const double DbScale = (MaxDbValue - MinDbValue);
    7.20 +
    7.21 +        private int _fftSize;
    7.22 +        private bool _isXLogScale;
    7.23 +        private int _maxFftIndex;
    7.24 +        private int _maximumFrequency = 20000;
    7.25 +        private int _maximumFrequencyIndex;
    7.26 +        private int _minimumFrequency = 20; //Default spectrum from 20Hz to 20kHz
    7.27 +        private int _minimumFrequencyIndex;
    7.28 +        private ScalingStrategy _scalingStrategy;
    7.29 +        private int[] _spectrumIndexMax;
    7.30 +        private int[] _spectrumLogScaleIndexMax;
    7.31 +        private ISpectrumProvider _spectrumProvider;
    7.32 +
    7.33 +        protected int SpectrumResolution;
    7.34 +        private bool _useAverage;
    7.35 +
    7.36 +        public int MaximumFrequency
    7.37 +        {
    7.38 +            get { return _maximumFrequency; }
    7.39 +            set
    7.40 +            {
    7.41 +                if (value <= MinimumFrequency)
    7.42 +                {
    7.43 +                    throw new ArgumentOutOfRangeException("value",
    7.44 +                        "Value must not be less or equal the MinimumFrequency.");
    7.45 +                }
    7.46 +                _maximumFrequency = value;
    7.47 +                UpdateFrequencyMapping();
    7.48 +
    7.49 +                RaisePropertyChanged("MaximumFrequency");
    7.50 +            }
    7.51 +        }
    7.52 +
    7.53 +        public int MinimumFrequency
    7.54 +        {
    7.55 +            get { return _minimumFrequency; }
    7.56 +            set
    7.57 +            {
    7.58 +                if (value < 0)
    7.59 +                    throw new ArgumentOutOfRangeException("value");
    7.60 +                _minimumFrequency = value;
    7.61 +                UpdateFrequencyMapping();
    7.62 +
    7.63 +                RaisePropertyChanged("MinimumFrequency");
    7.64 +            }
    7.65 +        }
    7.66 +
    7.67 +        [BrowsableAttribute(false)]
    7.68 +        public ISpectrumProvider SpectrumProvider
    7.69 +        {
    7.70 +            get { return _spectrumProvider; }
    7.71 +            set
    7.72 +            {
    7.73 +                if (value == null)
    7.74 +                    throw new ArgumentNullException("value");
    7.75 +                _spectrumProvider = value;
    7.76 +
    7.77 +                RaisePropertyChanged("SpectrumProvider");
    7.78 +            }
    7.79 +        }
    7.80 +
    7.81 +        public bool IsXLogScale
    7.82 +        {
    7.83 +            get { return _isXLogScale; }
    7.84 +            set
    7.85 +            {
    7.86 +                _isXLogScale = value;
    7.87 +                UpdateFrequencyMapping();
    7.88 +                RaisePropertyChanged("IsXLogScale");
    7.89 +            }
    7.90 +        }
    7.91 +
    7.92 +        public ScalingStrategy ScalingStrategy
    7.93 +        {
    7.94 +            get { return _scalingStrategy; }
    7.95 +            set
    7.96 +            {
    7.97 +                _scalingStrategy = value;
    7.98 +                RaisePropertyChanged("ScalingStrategy");
    7.99 +            }
   7.100 +        }
   7.101 +
   7.102 +        public bool UseAverage
   7.103 +        {
   7.104 +            get { return _useAverage; }
   7.105 +            set
   7.106 +            {
   7.107 +                _useAverage = value;
   7.108 +                RaisePropertyChanged("UseAverage");
   7.109 +            }
   7.110 +        }
   7.111 +
   7.112 +        [BrowsableAttribute(false)]
   7.113 +        public FftSize FftSize
   7.114 +        {
   7.115 +            get { return (FftSize) _fftSize; }
   7.116 +            protected set
   7.117 +            {
   7.118 +                if ((int) Math.Log((int) value, 2) % 1 != 0)
   7.119 +                    throw new ArgumentOutOfRangeException("value");
   7.120 +
   7.121 +                _fftSize = (int) value;
   7.122 +                _maxFftIndex = _fftSize / 2 - 1;
   7.123 +
   7.124 +                RaisePropertyChanged("FFTSize");
   7.125 +            }
   7.126 +        }
   7.127 +
   7.128 +        public event PropertyChangedEventHandler PropertyChanged;
   7.129 +
   7.130 +        protected virtual void UpdateFrequencyMapping()
   7.131 +        {
   7.132 +            _maximumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MaximumFrequency) + 1, _maxFftIndex);
   7.133 +            _minimumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MinimumFrequency), _maxFftIndex);
   7.134 +
   7.135 +            int actualResolution = SpectrumResolution;
   7.136 +
   7.137 +            int indexCount = _maximumFrequencyIndex - _minimumFrequencyIndex;
   7.138 +            double linearIndexBucketSize = Math.Round(indexCount / (double) actualResolution, 3);
   7.139 +
   7.140 +            _spectrumIndexMax = _spectrumIndexMax.CheckBuffer(actualResolution, true);
   7.141 +            _spectrumLogScaleIndexMax = _spectrumLogScaleIndexMax.CheckBuffer(actualResolution, true);
   7.142 +
   7.143 +            double maxLog = Math.Log(actualResolution, actualResolution);
   7.144 +            for (int i = 1; i < actualResolution; i++)
   7.145 +            {
   7.146 +                int logIndex =
   7.147 +                    (int) ((maxLog - Math.Log((actualResolution + 1) - i, (actualResolution + 1))) * indexCount) +
   7.148 +                    _minimumFrequencyIndex;
   7.149 +
   7.150 +                _spectrumIndexMax[i - 1] = _minimumFrequencyIndex + (int) (i * linearIndexBucketSize);
   7.151 +                _spectrumLogScaleIndexMax[i - 1] = logIndex;
   7.152 +            }
   7.153 +
   7.154 +            if (actualResolution > 0)
   7.155 +            {
   7.156 +                _spectrumIndexMax[_spectrumIndexMax.Length - 1] =
   7.157 +                    _spectrumLogScaleIndexMax[_spectrumLogScaleIndexMax.Length - 1] = _maximumFrequencyIndex;
   7.158 +            }
   7.159 +        }
   7.160 +
   7.161 +        protected virtual SpectrumPointData[] CalculateSpectrumPoints(double maxValue, float[] fftBuffer)
   7.162 +        {
   7.163 +            var dataPoints = new List<SpectrumPointData>();
   7.164 +
   7.165 +            double value0 = 0, value = 0;
   7.166 +            double lastValue = 0;
   7.167 +            double actualMaxValue = maxValue;
   7.168 +            int spectrumPointIndex = 0;
   7.169 +
   7.170 +            for (int i = _minimumFrequencyIndex; i <= _maximumFrequencyIndex; i++)
   7.171 +            {
   7.172 +                switch (ScalingStrategy)
   7.173 +                {
   7.174 +                    case ScalingStrategy.Decibel:
   7.175 +                        value0 = (((20 * Math.Log10(fftBuffer[i])) - MinDbValue) / DbScale) * actualMaxValue;
   7.176 +                        break;
   7.177 +                    case ScalingStrategy.Linear:
   7.178 +                        value0 = (fftBuffer[i] * ScaleFactorLinear) * actualMaxValue;
   7.179 +                        break;
   7.180 +                    case ScalingStrategy.Sqrt:
   7.181 +                        value0 = ((Math.Sqrt(fftBuffer[i])) * ScaleFactorSqr) * actualMaxValue;
   7.182 +                        break;
   7.183 +                }
   7.184 +
   7.185 +                bool recalc = true;
   7.186 +
   7.187 +                value = Math.Max(0, Math.Max(value0, value));
   7.188 +
   7.189 +                while (spectrumPointIndex <= _spectrumIndexMax.Length - 1 &&
   7.190 +                       i ==
   7.191 +                       (IsXLogScale
   7.192 +                           ? _spectrumLogScaleIndexMax[spectrumPointIndex]
   7.193 +                           : _spectrumIndexMax[spectrumPointIndex]))
   7.194 +                {
   7.195 +                    if (!recalc)
   7.196 +                        value = lastValue;
   7.197 +
   7.198 +                    if (value > maxValue)
   7.199 +                        value = maxValue;
   7.200 +
   7.201 +                    if (_useAverage && spectrumPointIndex > 0)
   7.202 +                        value = (lastValue + value) / 2.0;
   7.203 +
   7.204 +                    dataPoints.Add(new SpectrumPointData {SpectrumPointIndex = spectrumPointIndex, Value = value});
   7.205 +
   7.206 +                    lastValue = value;
   7.207 +                    value = 0.0;
   7.208 +                    spectrumPointIndex++;
   7.209 +                    recalc = false;
   7.210 +                }
   7.211 +
   7.212 +                //value = 0;
   7.213 +            }
   7.214 +
   7.215 +            return dataPoints.ToArray();
   7.216 +        }
   7.217 +
   7.218 +        protected void RaisePropertyChanged(string propertyName)
   7.219 +        {
   7.220 +            if (PropertyChanged != null && !String.IsNullOrEmpty(propertyName))
   7.221 +                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
   7.222 +        }
   7.223 +
   7.224 +        [DebuggerDisplay("{Value}")]
   7.225 +        protected struct SpectrumPointData
   7.226 +        {
   7.227 +            public int SpectrumPointIndex;
   7.228 +            public double Value;
   7.229 +        }
   7.230 +    }
   7.231 +}
   7.232 \ No newline at end of file