private async Task RenderWaveformAsync(bool withDelay)
        {
            // Note: This function does not set CurrentWaveform to null to allow the old
            // one to stay visible until the new calculation is complete (when called by InvalidateWaveform).

            renderWaveformCts?.Cancel();
            renderWaveformCts?.Dispose();
            renderWaveformCts = new CancellationTokenSource();

            try
            {
                IsLoading = true;
                if (withDelay)
                {
                    await Task.Delay(300, renderWaveformCts.Token);
                }
                if (audioFile == null) // no longer available
                {
                    IsLoading = false;
                    return;
                }

                double viewLeft      = sequencer.CurrentViewLeftPositionTime - MusicTimeOffset;
                double viewRight     = sequencer.CurrentViewRightPositionTime - MusicTimeOffset;
                double padding       = (viewRight - viewLeft) / 4; // use the width of one viewport on each side as padding
                double waveformLeft  = Math.Max(0, viewLeft - padding);
                double waveformRight = viewRight + padding;

                Waveform result = await WaveformGenerator.CreateWaveformAsync(audioFile.CreateStream(),
                                                                              sequencer.TimePixelScale,
                                                                              waveformLeft,
                                                                              waveformRight,
                                                                              renderWaveformCts.Token);

                Debug.WriteLine($"Time per sample: {result.TimePerSample}, Sample count: {result.Minimums.Length}");

                CurrentWaveform = result;
                IsLoading       = false;
            }
            catch (OperationCanceledException) { }
        }