/// <summary>
        /// A click is a sharp onset broadband sound of brief duration. Geometrically it is similar to a vertical whistle.
        /// THis method averages dB log values incorrectly but it is faster than doing many log conversions.
        /// This method is used to find acoustic events and is accurate enough for the purpose.
        /// </summary>
        public static (List <AcousticEvent> Events, double[] Intensity) GetClicks(
            SpectrogramStandard sonogram,
            int minHz,
            int maxHz,
            int nyquist,
            double decibelThreshold,
            int minBandwidthHertz,
            int maxBandwidthHertz,
            bool combineProximalSimilarEvents,
            TimeSpan segmentStartOffset)
        {
            var sonogramData = sonogram.Data;
            int frameCount   = sonogramData.GetLength(0);
            int binCount     = sonogramData.GetLength(1);

            double binWidth          = nyquist / (double)binCount;
            int    minBin            = (int)Math.Round(minHz / binWidth);
            int    maxBin            = (int)Math.Round(maxHz / binWidth);
            int    bandwidthBinCount = maxBin - minBin + 1;

            // list of accumulated acoustic events
            var events = new List <AcousticEvent>();
            var temporalIntensityArray = new double[frameCount];

            // for all time frames except 1st and last allowing for edge effects.
            for (int t = 1; t < frameCount - 1; t++)
            {
                // set up an intensity array for all frequency bins in this frame.
                double[] clickIntensity = new double[bandwidthBinCount];

                // buffer zone around click is one frame wide.
                // for all frequency bins except top and bottom in this time frame
                for (int bin = minBin; bin < maxBin; bin++)
                {
                    // THis is where the profile of a click is defined
                    // A click requires sudden onset, with maximum amplitude followed by decay.
                    if (sonogramData[t - 1, bin] > decibelThreshold || sonogramData[t, bin] < sonogramData[t + 1, bin])
                    {
                        continue;
                    }

                    // THis is where the profile of a vertical ridge is defined
                    //if (sonogramData[t, bin] < sonogramData[t - 1, bin] || sonogramData[t, bin] < sonogramData[t + 1, bin])
                    //{
                    //    continue;
                    //}

                    clickIntensity[bin - minBin] = sonogramData[t, bin];
                    //clickIntensity[bin - minBin] = sonogramData[t, bin] - sonogramData[t - 1, bin];
                    clickIntensity[bin - minBin] = Math.Max(0.0, clickIntensity[bin - minBin]);
                }

                if (clickIntensity.Max() < decibelThreshold)
                {
                    continue;
                }

                // Extract the events based on bandwidth and threshhold.
                var acousticEvents = ConvertSpectralArrayToClickEvents(
                    clickIntensity,
                    minHz,
                    sonogram.FramesPerSecond,
                    sonogram.FBinWidth,
                    decibelThreshold,
                    minBandwidthHertz,
                    maxBandwidthHertz,
                    t,
                    segmentStartOffset);

                // add each event score to combined temporal intensity array
                foreach (var ae in acousticEvents)
                {
                    var avClickIntensity = ae.Score;
                    temporalIntensityArray[t] += avClickIntensity;
                }

                // add new events to list of events
                events.AddRange(acousticEvents);
            }

            // combine proximal events that occupy similar frequency band
            var startDifference = TimeSpan.FromSeconds(1.0);
            var hertzDifference = 100;

            if (combineProximalSimilarEvents)
            {
                events = AcousticEvent.CombineSimilarProximalEvents(events, startDifference, hertzDifference);
            }

            return(events, temporalIntensityArray);
        }
        /// <summary>
        /// EXPANATION: A vertical track is a near click or rapidly frequency-modulated tone. A good example is the whip component of the whip-bird call.
        /// They would typically be only a few time-frames duration.
        /// THis method averages dB log values incorrectly but it is faster than doing many log conversions and is accurate enough for the purpose.
        /// </summary>
        public static (List <AcousticEvent> Events, double[] CombinedIntensity) GetVerticalTracks(
            SpectrogramStandard sonogram,
            int minHz,
            int maxHz,
            int nyquist,
            double decibelThreshold,
            int minBandwidthHertz,
            int maxBandwidthHertz,
            bool combineProximalSimilarEvents,
            TimeSpan segmentStartOffset)
        {
            var sonogramData  = sonogram.Data;
            int frameCount    = sonogramData.GetLength(0);
            int binCount      = sonogramData.GetLength(1);
            var frameDuration = sonogram.FrameDuration;
            var frameStep     = sonogram.FrameStep;
            var frameOverStep = frameDuration - frameStep;

            double binWidth = nyquist / (double)binCount;
            int    minBin   = (int)Math.Round(minHz / binWidth);
            int    maxBin   = (int)Math.Round(maxHz / binWidth);

            // list of accumulated acoustic events
            var events = new List <AcousticEvent>();
            var temporalIntensityArray = new double[frameCount];

            //Find all frame peaks and place in peaks matrix
            var peaks = new double[frameCount, binCount];

            for (int row = 1; row < frameCount - 1; row++)
            {
                for (int col = minBin; col < maxBin; col++)
                {
                    if (sonogramData[row, col] < decibelThreshold)
                    {
                        continue;
                    }

                    // if given matrix element is greater than in frame either side
                    bool isPeak = (sonogramData[row, col] > sonogramData[row - 1, col]) && (sonogramData[row, col] > sonogramData[row + 1, col]);
                    if (isPeak)
                    {
                        peaks[row, col] = sonogramData[row, col];
                    }
                }
            }

            // Look for track starts and initialise them as events.
            // Cannot include edge rows & columns because of edge effects.
            // Each row is a time frame which is a spectrum. Each column is a frequency bin
            var combinedIntensityArray = new double[frameCount];

            for (int col = minBin; col < maxBin; col++)
            {
                for (int row = 2; row < frameCount - 2; row++)
                {
                    // Visit each frame peak in order. Each may be start of possible track
                    if (peaks[row, col] < decibelThreshold)
                    {
                        continue;
                    }

                    //have the beginning of a potential track
                    var track = GetVerticalTrack(peaks, row, col, maxBin, decibelThreshold);

                    // calculate first and last of the frame IDs in the original spectrogram
                    int trackStartFrame = track.GetStartFrame();
                    int trackEndFrame   = track.GetEndFrame();

                    // next two for debug purposes
                    //int trackMinBin = track.GetBottomFreqBin();
                    //int trackTopBin = track.GetTopFreqBin();

                    //If track has lies within the correct bandWidth range, then create an event
                    int trackBandWidth = track.GetTrackBandWidthHertz(binWidth);
                    if (trackBandWidth >= minBandwidthHertz && trackBandWidth <= maxBandwidthHertz)
                    {
                        // get the oblong and init an event
                        double trackDuration = ((trackEndFrame - trackStartFrame) * frameStep) + frameOverStep;
                        var    oblong        = new Oblong(trackStartFrame, col, trackEndFrame, track.GetTopFreqBin());
                        var    ae            = new AcousticEvent(segmentStartOffset, oblong, nyquist, binCount, frameDuration, frameStep, frameCount)
                        {
                            // get the track as matrix
                            TheTrack = track,
                        };

                        events.Add(ae);

                        // fill the intensity array
                        var amplitudeArray = track.GetAmplitudeOverTimeFrames();
                        for (int i = 0; i < amplitudeArray.Length; i++)
                        {
                            combinedIntensityArray[row + i] += amplitudeArray[i];
                        }
                    }
                } // rows/frames
            }     // end cols/bins

            // combine proximal events that occupy similar frequency band
            if (combineProximalSimilarEvents)
            {
                TimeSpan startDifference = TimeSpan.FromSeconds(0.5);
                int      hertzDifference = 500;
                events = AcousticEvent.CombineSimilarProximalEvents(events, startDifference, hertzDifference);
            }

            return(events, temporalIntensityArray);
        }