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) { }
        }
        public async Task LoadFileAsync(string fileName)
        {
            try { audioFile = new BufferedAudioFile(fileName); }
            catch (Exception e)
            {
                System.Windows.MessageBox.Show("Could not load music file: " + Path.GetFileName(fileName) + Environment.NewLine + Environment.NewLine + e.Message, "Problem opening file",
                                               System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning);
                return;
            }

            MusicFileName = fileName;

            // TODO progress inidicator for file loading
            audioFile.LoadIntoMemoryAsync(null).Forget();
            audioPlayback.Init(audioFile.CreateStream(infinite: true));

            // Initialize our user-facing value from the playback device (which apparently gets its value from Windows).
            MusicVolume = LoudnessHelper.VolumeFromLoudness(audioPlayback.Volume);

            CurrentWaveform = null;
            await RenderWaveformAsync(false);

            Notify(nameof(MusicDuration));
        }