swharden / Spectrogram

.NET library for creating spectrograms (visual representations of frequency spectrum over time)
https://nuget.org/packages/Spectrogram
MIT License
308 stars 56 forks source link

Range selection for file view #56

Closed vrdriver closed 2 months ago

vrdriver commented 1 year ago

Hi Scott, I've been playing with this recently, and still with scottplot, and have been able to capture an audio file spectral view and place it in a Scott plot and it works well. But, I've set it to be about 3000px wide or similar, but the more I zoom in, the more pixelized the image gets. I was wondering, (and I'm planning on doing this with my Scottplot waveform render to minimise memory use) if we could have a range specified on the date to read.

So, to keep things simple, say I have a wave file with 1000 samples, but the scottplot view is only 100pixels wide. Therefore, do you think that I could just read that audio file, and take steps at 10 sample intervals to still get a 100pixel wide image. Then, say I zoom in, and it then needs to fill the image, but starting at 300samples in, and the last point of the scottplot window for the file is at 800 samples, and therefore rereading the samples and only still show 100 samples (based on the scottplot width again), and having to reread the file. I hope I'm making sense, but while this is just an idea, does the spectral library for files allow for this? Or is it primarily for reading the entire file and just specifying a specific width of pixels it will render? I'm just trying to think of ways to lower the memory use. I don't mind if it reads the file every time it has to update a Windows paint, cause it'd be doing skipping samples a way.

Anyway. Just ideas. Cheers, Steve.

vrdriver commented 1 year ago

Sorry, just late night rambling thoughts while watching the F1s. 😂

vrdriver commented 2 months ago

I'd like to close this, because I have a way of doing this. Appreciate all your work on this Scott. Thanks. (Blog appreciation post)

using System;
using System.Drawing;
using System.Windows.Forms;
using NAudio.Wave;
using System.Numerics;
using Spectrogram;
using System.Linq;

namespace WinFormsApp1_test_something
{
    public partial class Form1 : Form
    {
        public static SpectrogramGenerator sg;

        // private string audioFilePath = "C:\\path_to_your_audio_file.wav";
        private string audioFilePath = "C:\\test\\test.wav"; 

        private PictureBox pictureBox;
        private int canvasWidth = 800;
        private int canvasHeight = 800;
        private WaveFileReader reader;
        private float[] audioSamples;

        public Form1()
        {
            InitializeComponent();
            InitializePictureBox();

            double startTime = 10.2; // Start time in seconds
            double endTime = 12.5;   // End time in seconds

            (double[] audio, int sampleRate) = ReadWavMono(audioFilePath);

            // Calculate sample range
            int startSample = (int)(startTime * sampleRate);
            int endSample = (int)(endTime * sampleRate);

            // Ensure the end sample does not exceed the audio length
            endSample = Math.Min(endSample, audio.Length);

            // Extract the partial audio
            double[] partialAudio = audio.Skip(startSample).Take(endSample - startSample).ToArray();

            int fftSize = 16384; // a weird way of doing the height
            int targetWidthPx = 3000;
            int stepSize = partialAudio.Length / targetWidthPx;

            sg = new SpectrogramGenerator(sampleRate, fftSize, stepSize, maxFreq: 2200);
            sg.Add(partialAudio);
            sg.SaveImage("partial_song.png", intensity: 5, dB: true);
        }

        (double[] audio, int sampleRate) ReadWavMono(string filePath, double multiplier = 16_000)
        {
            using var afr = new NAudio.Wave.AudioFileReader(filePath);
            int sampleRate = afr.WaveFormat.SampleRate;
            int bytesPerSample = afr.WaveFormat.BitsPerSample / 8;
            int sampleCount = (int)(afr.Length / bytesPerSample);
            int channelCount = afr.WaveFormat.Channels;
            var audio = new System.Collections.Generic.List<double>(sampleCount);
            var buffer = new float[sampleRate * channelCount];
            int samplesRead = 0;
            while ((samplesRead = afr.Read(buffer, 0, buffer.Length)) > 0)
                audio.AddRange(buffer.Take(samplesRead).Select(x => x * multiplier));
            return (audio.ToArray(), sampleRate);
        }

        private void InitializePictureBox()
        {
            pictureBox = new PictureBox
            {
                Location = new Point(10, 10),
                Size = new Size(canvasWidth, canvasHeight),
                ImageLocation = "partial_song.png"
            };
            Controls.Add(pictureBox);
        }
    }
}