// Note: Actually no longer used because instead of disabling playback with no music,
 // we load an EmptySampleProvider instead.
 public void Clear()
 {
     Stop();
     this.sampleProvider       = null;
     lastKnownSeekSample       = 0;
     continuationOnStoppedHook = null;
 }
        public void Init(ISeekableSampleProvider sampleProvider)
        {
            Stop();
            EnsureDeviceCreated();

            this.sampleProvider = sampleProvider;
            lastKnownSeekSample = sampleProvider.Position;
            playbackDevice.Init(sampleProvider);
        }
        public static Task <Waveform> CreateWaveformAsync(ISeekableSampleProvider sampleProvider, float scaleInPixelsPerSecond,
                                                          double fromTime, double toTime,
                                                          CancellationToken cancellation = default(CancellationToken))
        {
            if (scaleInPixelsPerSecond <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(scaleInPixelsPerSecond), scaleInPixelsPerSecond, "must be positive");
            }
            if (fromTime < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(fromTime), fromTime, "must be non-negative");
            }

            return(Task.Run(() => CreateWaveform(sampleProvider, scaleInPixelsPerSecond, fromTime, toTime, cancellation), cancellation));
        }
        private static Waveform CreateWaveform(ISeekableSampleProvider sampleProvider, float scaleInPixelsPerSecond,
                                               double fromTime, double toTime,
                                               CancellationToken cancellation = default(CancellationToken))
        {
            var sw = new Stopwatch();

            sw.Start();

            int   channels   = sampleProvider.WaveFormat.Channels;
            float sampleRate = sampleProvider.WaveFormat.SampleRate;

            scaleInPixelsPerSecond /= WAVEFORM_PIXEL_INTERVAL;
            // It does not make sense to have more pixels than samples.
            if (scaleInPixelsPerSecond > sampleRate)
            {
                scaleInPixelsPerSecond = sampleRate;
            }

            List <float> minValues = new List <float>();
            List <float> maxValues = new List <float>();

            // the first sample to include in the waveform - aligned with the pixel interval to prevent jittering when scrolling
            long alignIntervalFactor = (long)Math.Round(sampleRate / scaleInPixelsPerSecond);
            long firstSample         = (long)(fromTime * sampleRate) / alignIntervalFactor * alignIntervalFactor;
            long lastSample          = (long)Math.Ceiling(toTime * sampleRate);

            sampleProvider.Seek((int)firstSample * channels);

            long  c          = firstSample; // global sample counter (for all channels)
            int   lastX      = 0;           // current render position
            float currentMin = float.PositiveInfinity;
            float currentMax = float.NegativeInfinity;

            float[] buffer = new float[READ_BUFFER_SIZE];
            int     numRead;

            do
            {
                cancellation.ThrowIfCancellationRequested();

                numRead = sampleProvider.Read(buffer, 0, READ_BUFFER_SIZE);
                for (int i = 0; i < numRead && c <= lastSample; i++)
                {
                    float renderPosition = (c - firstSample) / sampleRate * scaleInPixelsPerSecond;
                    int   x = (int)renderPosition;
                    if (x > lastX)
                    {
                        // we advanced a pixel, add new values with aggregate
                        minValues.Add(currentMin);
                        maxValues.Add(currentMax);
                        lastX++;
                        // reset
                        currentMin = float.PositiveInfinity;
                        currentMax = float.NegativeInfinity;
                    }

                    // aggregate
                    float value = buffer[i];
                    currentMin = Math.Min(currentMin, value);
                    currentMax = Math.Max(currentMax, value);

                    // only count up total when we cycled through all channels
                    if ((i + 1) % channels == 0)
                    {
                        c++;
                    }
                }
            } while (numRead > 0 && c <= lastSample);

            double actualFromTime = (double)firstSample / sampleRate;
            double timePerSample  = 1.0 / scaleInPixelsPerSecond;
            var    wf             = new Waveform(actualFromTime, timePerSample, minValues.ToArray(), maxValues.ToArray());

            sw.Stop();
            Debug.WriteLine($"generated waveform with {minValues.Count} data points for {toTime - fromTime} s in {sw.ElapsedMilliseconds} ms");

            return(wf);
        }