1

I am trying to make a simple app to generate composite waveform signal audio files. I want to display a preview of what the final waveform of the audio file will look like. My issue is that I can't get the image to display in the Image control.

I'm using CommunityToolkit.Maui.Markup

Here's my code

MainViewModel.cs

using NAudio.Wave;
using NAudio.Wave.SampleProviders;
using SkiaSharp;

namespace WaveformGenerator.ViewModels
{
   public partial class MainViewModel : BaseViewModel
   {
      private string tempPath = FileSystem.CacheDirectory == null ? Path.GetTempPath() : FileSystem.CacheDirectory;

      [ObservableProperty]
      public string sineFreqs = "10, 1";

      [ObservableProperty]
      public int sampleLength = 1;

      [ObservableProperty]
      public ImageSource sampleImage;

      public MainViewModel()
      {
         //_ = GenerateSample();
         sampleImage = ImageSource.FromFile(@"dotnet_bot.svg");   <-----------------------
      }

      [RelayCommand]
      public async Task GenerateSample()
      {
         await Task.Run(() =>
         {
            var frequencies = SineFreqs.Split(',');

            foreach (var frequency in frequencies)
            {
               if (double.TryParse(frequency, out double freq) && freq > 0)
               {
                  GenerateWaveform(freq, SampleLength);
               }
            }
         });
      }

      private async Task GenerateWaveform(double frequency, int lengthInSeconds)
      {
         try
         {
            // Define the file paths
            var audioFilePath = Path.Combine(tempPath, "audio.wav");
            var imageFilePath = Path.Combine(tempPath, "waveform.png");

            // Generate the sine wave
            var signalGenerator = new SignalGenerator()
            {
               Gain = 0.1,
               Frequency = frequency,
               Type = SignalGeneratorType.Sin
            };
            var wave = signalGenerator.Take(TimeSpan.FromSeconds(lengthInSeconds));

            // Save the audio to a file
            WaveFileWriter.CreateWaveFile16(audioFilePath, wave);

            // Convert the audio file to a WaveFileReader
            using var reader = new AudioFileReader(audioFilePath);
            var samples = new float[reader.Length];
            reader.Read(samples, 0, samples.Length);

            // Create a blank image
            using var surface = SKSurface.Create(640, 480, SKColorType.Rgba8888, SKAlphaType.Premul);
            var canvas = surface.Canvas;
            canvas.Clear(SKColors.White);

            // Scale the samples to fit within the image
            var scale = 480 / (2 * GetMaxMagnitude(samples));

            // Draw the waveform
            for (var i = 0; i < samples.Length - 1; i++)
            {
               var x1 = i * (640 / (float)samples.Length);
               var y1 = 240 + samples[i] * scale;
               var x2 = (i + 1) * (640 / (float)samples.Length);
               var y2 = 240 + samples[i + 1] * scale;

               canvas.DrawLine(x1, y1, x2, y2, new SKPaint { Color = SKColors.Black });
            }

            // Save the image to a file
            using var image = surface.Snapshot();
            using var data = image.Encode(SKEncodedImageFormat.Png, 100);
            using var fileStream = File.OpenWrite(imageFilePath);
            data.SaveTo(fileStream);

            // Update the ImageSource
            SampleImage = ImageSource.FromFile(imageFilePath);
         }
         catch (Exception ex)
         {
            App.AlertSvc.ShowAlert("Error generating waveform", $"An error occurred while generating the waveform: {ex.Message}");
         }
      }


      private float GetMaxMagnitude(float[] samples)
      {
         var max = 0f;
         for (var i = 0; i < samples.Length; i++)
         {
            var abs = Math.Abs(samples[i]);
            if (abs > max)
               max = abs;
         }
         return max;
      }
   }
}

MainPage.cs

namespace WaveformGenerator.Views;


using static CommunityToolkit.Maui.Markup.GridRowsColumns;
public partial class MainPage : ContentPage
{
   public MainPage(MainViewModel viewModel)
   {
      BindingContext = viewModel;

      Content = new Grid
      {
         RowDefinitions = Rows.Define(Auto, Star, Star, Star, Star),
         ColumnDefinitions = Columns.Define(Star, Star),

         Children =
         {
            new Image {}
            .BackgroundColor(Colors.LightGray)
            .MinHeight(120).MinWidth(640)
            .Bind(Image.SourceProperty, nameof(viewModel.SampleImage), BindingMode.OneWay)
            .Row(0).Column(0).ColumnSpan(2),

            new VerticalStackLayout
            {
               Children =
               {
                  new Label {Text = "Sine"},
                  new Entry {}.Bind(Entry.TextProperty, nameof(viewModel.SineFreqs))
               }
            }
            .Row(1).Column(0),

            
         }
      };
   }
}

I know the code is probably not the best, but it's a wip.

master_ruko
  • 629
  • 1
  • 4
  • 22

1 Answers1

1

from the docs

.NET MAUI converts SVG files to PNG files. Therefore, when adding an SVG file to your .NET MAUI app project, it should be referenced from XAML or C# with a .png extension. The only reference to the SVG file should be in your project file.

Jason
  • 86,222
  • 15
  • 131
  • 146