HamBook – Rev 14

Subversion Repositories:
Rev:
using Configuration;
using FftSharp;
using HamBook.Utilities;
using NAudio.Utils;
using NAudio.Wave;
using Org.BouncyCastle.Math.EC.Multiplier;
using Spectrogram;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;

namespace HamBook
{
    public partial class SpectrogramForm : Form
    {
        #region Natives
        [DllImport("user32.dll")]
        private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

        private static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
        private static readonly IntPtr HWND_TOP = new IntPtr(0);
        private const UInt32 SWP_NOSIZE = 0x0001;
        private const UInt32 SWP_NOMOVE = 0x0002;
        private const UInt32 SWP_NOACTIVATE = 0x0010;

        #endregion

        private Configuration.Configuration _configuration;
        private CancellationToken _cancellationToken;
        SpectrogramGenerator _spectrogramGenerator;
        private WaveIn _waveIn;
        private CancellationTokenSource _renderCancellationTokenSource;
        private CancellationToken _renderCancellationToken;
        private ScheduledContinuation _windowZOrderScheduledContinuation;
        private volatile bool _mouseDown;
        private Point _formLocation;

        public SpectrogramForm()
        {
            InitializeComponent();

            _renderCancellationTokenSource = new CancellationTokenSource();
            _renderCancellationToken = _renderCancellationTokenSource.Token;
            _windowZOrderScheduledContinuation = new ScheduledContinuation();
            
        }

        public SpectrogramForm(Configuration.Configuration configuration, CancellationToken cancellationToken) : this()
        {
            _configuration = configuration;
            _cancellationToken = cancellationToken;

            _spectrogramGenerator = new SpectrogramGenerator(_configuration.Visualisations.Spectrogram.SampleRate, fftSize: _configuration.Visualisations.Spectrogram.FFTSamples, stepSize: _configuration.Visualisations.Spectrogram.FFTSamples / _configuration.Visualisations.Spectrogram.AudioBufferTimespan);
            _spectrogramGenerator.Colormap = Colormap.Viridis;

            pictureBox2.Image = _spectrogramGenerator.GetVerticalScale(pictureBox2.Width);
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                if(_waveIn != null)
                {
                    if (_renderCancellationTokenSource != null)
                    {
                        _renderCancellationTokenSource.Cancel();
                    }

                    _waveIn.DataAvailable -= _waveIn_DataAvailable;
                    _waveIn.StopRecording();
                    _waveIn.Dispose();
                    _waveIn = null;
                }

                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void SpectrogramForm_Load(object sender, EventArgs e)
        {
            pinToDesktopToolStripMenuItem.Checked = _configuration.Visualisations.Spectrogram.PinToDesktop;

            if (_configuration.Visualisations.Spectrogram.PinToDesktop)
            {
                _windowZOrderScheduledContinuation.Schedule(TimeSpan.FromSeconds(1), () =>
                {
                    this.InvokeIfRequired(form =>
                    {
                        SetWindowPos(Handle, HWND_BOTTOM, 0, 0, Location.X, Location.Y, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
                    });
                }, _renderCancellationToken);
            }

            foreach (var deviceNumber in Enumerable.Range(0, WaveIn.DeviceCount))
            {
                var capabilities = WaveIn.GetCapabilities(deviceNumber);
                if(!string.Equals(capabilities.ProductName, _configuration.Audio.InputDeviceFriendlyName))
                {
                    continue;
                }

                _waveIn = new WaveIn();
                _waveIn.DeviceNumber = deviceNumber;
                _waveIn.BufferMilliseconds = _configuration.Visualisations.Spectrogram.AudioBufferTimespan;

                _waveIn.DataAvailable += _waveIn_DataAvailable;
                _waveIn.StartRecording();
                break;
            }
        }

        private void _waveIn_DataAvailable(object sender, WaveInEventArgs e)
        {
            int bytesPerSample = _waveIn.WaveFormat.BitsPerSample / 8;
            int newSampleCount = e.BytesRecorded / bytesPerSample;
            double[] buffer = new double[newSampleCount];
            double peak = 0;
            for (int i = 0; i < newSampleCount; i++)
            {
                buffer[i] = BitConverter.ToInt16(e.Buffer, i * bytesPerSample);
                peak = Math.Max(peak, buffer[i]);
            }

            var AmplitudeFrac = peak / (1 << 15);

            _spectrogramGenerator.Add(buffer, false);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if(_spectrogramGenerator.FftsToProcess == 0)
            {
                return;
            }

            _spectrogramGenerator.Process();
            _spectrogramGenerator.SetFixedWidth(pictureBox1.Width);
            var bmpSpec = new Bitmap(_spectrogramGenerator.Width, _spectrogramGenerator.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
            using (var bmpSpecIndexed = _spectrogramGenerator.GetBitmap(_configuration.Visualisations.Spectrogram.SpectrumIntensity))
            using (var gfx = Graphics.FromImage(bmpSpec))
            using (var pen = new Pen(Color.White))
            {
                gfx.DrawImage(bmpSpecIndexed, 0, 0);
                if (false)
                {
                    //gfx.DrawLine(pen, spec.NextColumnIndex, 0, spec.NextColumnIndex, pbSpectrogram.Height);
                }
            }

            pictureBox1.Image?.Dispose();
            pictureBox1.Image = bmpSpec;
        }

        private void SpectrogramForm_MouseClick(object sender, MouseEventArgs e)
        {
            switch(e.Button)
            {
                case MouseButtons.Right:
                    var control = (Control)sender;
                    var screenPoint = control.PointToScreen(e.Location);
                    contextMenuStrip1.Show(screenPoint);
                    break;
                case MouseButtons.Left:
                    break;
            }
        }

        private void closeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void SpectrogramForm_MouseDown(object sender, MouseEventArgs e)
        {
            if(e.Button != MouseButtons.Left)
            {
                return;
            }

            if(e.Clicks != 1)
            {
                return;
            }

            _mouseDown = true;
            _formLocation = new Point(e.X, e.Y);
        }

        private void SpectrogramForm_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Left)
            {
                return;
            }

            if (!_mouseDown)
            {
                return;
            }

            var control = (Control)sender;
            var screenPoint = control.PointToScreen(e.Location);

            Location = new Point(screenPoint.X - _formLocation.X, screenPoint.Y - _formLocation.Y);

        }

        private void SpectrogramForm_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Left)
            {
                return;
            }

            _mouseDown = false;
        }

        private void SpectrogramForm_Activated(object sender, EventArgs e)
        {
            // Make form bottom most.
            //SetWindowPos(Handle, HWND_BOTTOM, 0, 0, Location.X, Location.Y, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
        }

        private void SpectrogramForm_MouseEnter(object sender, EventArgs e)
        {
            if (_configuration.Visualisations.Spectrogram.PinToDesktop)
            {
                _windowZOrderScheduledContinuation.Schedule(TimeSpan.FromSeconds(1), () =>
                {
                    this.InvokeIfRequired(form =>
                    {
                        SetWindowPos(Handle, HWND_TOP, 0, 0, Location.X, Location.Y, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
                    });
                }, _renderCancellationToken);
            }
        }

        private void SpectrogramForm_MouseLeave(object sender, EventArgs e)
        {
            if (_configuration.Visualisations.Spectrogram.PinToDesktop)
            {
                _windowZOrderScheduledContinuation.Schedule(TimeSpan.FromSeconds(1), () =>
                {
                    this.InvokeIfRequired(form =>
                    {
                        SetWindowPos(form.Handle, HWND_BOTTOM, 0, 0, Location.X, Location.Y, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
                    });

                }, _renderCancellationToken);
            }
        }

        private void pinToDesktopToolStripMenuItem_CheckStateChanged(object sender, EventArgs e)
        {
            var toolStripMenuItem = (ToolStripMenuItem)sender;

            switch(toolStripMenuItem.CheckState)
            {
                case CheckState.Checked:
                    _configuration.Visualisations.Spectrogram.PinToDesktop = true;
                    break;
                case CheckState.Unchecked:
                    _configuration.Visualisations.Spectrogram.PinToDesktop = false;
                    break;
            }
        }
    }
}