绘制声波,可以放大/缩小

发布于 2024-08-22 18:03:53 字数 731 浏览 14 评论 0原文

我正在为我的毕业典礼编写一个声音编辑器。我正在使用 BASS 从 MP3、WAV、OGG 等文件中提取样本并添加 DSP 效果像回声,镶边等。简单地说,我制作了我的框架,将效果从位置1应用到位置2,剪切/粘贴管理。

现在我的问题是我想创建一个与 Cool Edit Pro< 中的控件类似的控件/a> 绘制歌曲的波形表示,并能够放大/缩小波形的选择部分等。选择后我可以做类似的事情:

TInterval EditZone = WaveForm->GetSelection();

其中 TInterval 有这种形式:

struct TInterval
{
    long Start;
    long End;
}

我是初学者它涉及复杂的绘图,因此任何关于如何使用 BASS 返回的样本数据创建歌曲的波形表示形式的提示,以及放大/缩小的能力将不胜感激。

我正在用 C++ 编写我的项目,但我可以理解 C#、Delphi 代码,因此如果您愿意,您也可以用最后两种语言发布代码片段:)

感谢 DrOptix

I'm writing a sound editor for my graduation. I'm using BASS to extract samples from MP3, WAV, OGG etc files and add DSP effects like echo, flanger etc. Simply speaching I made my framework that apply an effect from position1 to position2, cut/paste management.

Now my problem is that I want to create a control similar with this one from Cool Edit Pro that draw a wave form representation of the song and have the ability to zoom in/out select portions of the wave form etc. After a selection i can do something like:

TInterval EditZone = WaveForm->GetSelection();

where TInterval have this form:

struct TInterval
{
    long Start;
    long End;
}

I'm a beginner when it comes to sophisticated drawing so any hint on how to create a wave form representation of a song, using sample data returned by BASS, with ability to zoom in/out would be appreciated.

I'm writing my project in C++ but I can understand C#, Delphi code so if you want you can post snippets in last two languages as well :)

Thanx DrOptix

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

彼岸花ソ最美的依靠 2024-08-29 18:03:54

我最近自己做了这个。正如 Marius 建议的那样,您需要计算出每列像素有多少个样本。然后计算出最小值和最大值,然后绘制从最大值到最小值的垂直线。

作为第一遍,这看起来效果很好。您将遇到的问题是,当您缩小时,从磁盘检索样本将开始花费太长时间。作为解决方案,我在音频文件旁边构建了一个“峰值”文件。峰值文件存储 n 个样本组的最小/最大对。玩 n 直到获得正确的数量取决于你。我个人发现 128 个样本是大小和速度之间的一个很好的权衡。还值得记住的是,除非您绘制的控件大小大于 65536 像素,否则无需将此峰值信息存储为超过 16 位值,这样可以节省一点空间。

I've recently done this myself. As Marius suggests you need to work out how many samples are at each column of pixels. You then work out the minimum and maximum and then plot a vertical line from the maximum to the minimum.

As a first pass this seemingly works fine. The problem you'll get is that as you zoom out it will start to take too long to retrieve the samples from disk. As a solution to this I built a "peak" file alongside the audio file. The peak file stores the minimum/maximum pairs for groups of n samples. PLaying with n till you get the right amount is up to uyou. Personally I found 128 samples to be a good tradeoff between size and speed. Its also worth remembering that, unless you are drawing a control larger than 65536 pixels in size that you needn't store this peak information as anything more than 16-bit values which saves a bit of space.

你不会只在 2 个画布上绘制样本点吗?您应该知道文件每秒有多少个样本(从标题中读取),然后在 y 轴上绘制该值。由于您希望能够放大和缩小,因此需要控制每个像素的样本数(缩放级别)。接下来,取每个像素的样本点的平均值(例如,如果每个像素有 5 个样本,则取每 5 个点的平均值。然后,您可以使用 2d 绘图 api 在点之间绘制线条。

Wouldn't you just plot the sample points on a 2 canvas? You should know how many samples there are per second for a file (read it from the header), and then plot the value on the y axis. Since you want to be able to zoom in and out, you need to control the number of samples per pixel (the zoom level). Next you take the average of those sample points per pixel (for example take the average of every 5 points if you have 5 samples per pixel. Then you can use a 2d drawing api to draw lines between the points.

违心° 2024-08-29 18:03:54

使用开源 NAudio 包 -

public class WavReader2
{
    private readonly WaveFileReader _objStream;

    public WavReader2(String sPath)
    {
        _objStream = new WaveFileReader(sPath);
    }

    public List<SampleRangeValue> GetPixelGraph(int iSamplesPerPixel)
    {
        List<SampleRangeValue> colOutputValues = new List<SampleRangeValue>();

        if (_objStream != null)
        {
            _objStream.Position = 0;
            int iBytesPerSample = (_objStream.WaveFormat.BitsPerSample / 8) * _objStream.WaveFormat.Channels;
            int iNumPixels = (int)Math.Ceiling(_objStream.SampleCount/(double)iSamplesPerPixel);

            byte[] aryWaveData = new byte[iSamplesPerPixel * iBytesPerSample];
            _objStream.Position = 0; // startPosition + (e.ClipRectangle.Left * iBytesPerSample * iSamplesPerPixel);

            for (float iPixelNum = 0; iPixelNum < iNumPixels; iPixelNum += 1)
            {
                short iCurrentLowValue = 0;
                short iCurrentHighValue = 0;
                int iBytesRead = _objStream.Read(aryWaveData, 0, iSamplesPerPixel * iBytesPerSample);
                if (iBytesRead == 0)
                    break;

                List<short> colValues = new List<short>();
                for (int n = 0; n < iBytesRead; n += 2)
                {
                    short iSampleValue = BitConverter.ToInt16(aryWaveData, n);
                    colValues.Add(iSampleValue);
                }

                float fLowPercent =  (float)((float)colValues.Min() /ushort.MaxValue);
                float fHighPercent = (float)((float)colValues.Max() / ushort.MaxValue);

                colOutputValues.Add(new SampleRangeValue(fHighPercent, fLowPercent));
            }
        }

        return colOutputValues;
    }
}

public struct SampleRangeValue
{
    public float HighPercent;
    public float LowPercent;
    public SampleRangeValue(float fHigh, float fLow)
    {
        HighPercent = fHigh;
        LowPercent = fLow;
    }
}

Using the open source NAudio Package -

public class WavReader2
{
    private readonly WaveFileReader _objStream;

    public WavReader2(String sPath)
    {
        _objStream = new WaveFileReader(sPath);
    }

    public List<SampleRangeValue> GetPixelGraph(int iSamplesPerPixel)
    {
        List<SampleRangeValue> colOutputValues = new List<SampleRangeValue>();

        if (_objStream != null)
        {
            _objStream.Position = 0;
            int iBytesPerSample = (_objStream.WaveFormat.BitsPerSample / 8) * _objStream.WaveFormat.Channels;
            int iNumPixels = (int)Math.Ceiling(_objStream.SampleCount/(double)iSamplesPerPixel);

            byte[] aryWaveData = new byte[iSamplesPerPixel * iBytesPerSample];
            _objStream.Position = 0; // startPosition + (e.ClipRectangle.Left * iBytesPerSample * iSamplesPerPixel);

            for (float iPixelNum = 0; iPixelNum < iNumPixels; iPixelNum += 1)
            {
                short iCurrentLowValue = 0;
                short iCurrentHighValue = 0;
                int iBytesRead = _objStream.Read(aryWaveData, 0, iSamplesPerPixel * iBytesPerSample);
                if (iBytesRead == 0)
                    break;

                List<short> colValues = new List<short>();
                for (int n = 0; n < iBytesRead; n += 2)
                {
                    short iSampleValue = BitConverter.ToInt16(aryWaveData, n);
                    colValues.Add(iSampleValue);
                }

                float fLowPercent =  (float)((float)colValues.Min() /ushort.MaxValue);
                float fHighPercent = (float)((float)colValues.Max() / ushort.MaxValue);

                colOutputValues.Add(new SampleRangeValue(fHighPercent, fLowPercent));
            }
        }

        return colOutputValues;
    }
}

public struct SampleRangeValue
{
    public float HighPercent;
    public float LowPercent;
    public SampleRangeValue(float fHigh, float fLow)
    {
        HighPercent = fHigh;
        LowPercent = fLow;
    }
}
一梦浮鱼 2024-08-29 18:03:53

我认为您所说的缩放是指水平缩放而不是垂直缩放。音频编辑器执行此操作的方法是扫描波形,将其分解为时间窗口,其中 X 中的每个像素代表一定数量的样本。它可以是小数,但您可以禁止小数缩放比例,而不会太烦扰用户。一旦缩小一点,最大值始终是正整数,最小值始终是负整数。

对于屏幕上的每个像素,您需要知道该像素的最小样本值和最大样本值。因此,您需要一个函数来扫描块中的波形数据并跟踪该块的累积最大值和最小值。

这是一个缓慢的过程,因此专业的音频编辑器会在某个固定的缩放比例下保留一个预先计算的最小值和最大值表。它可能是 512/1 或 1024/1。当您使用 > 的缩放比例绘图时1024 个样本/像素,那么您使用预先计算的表。如果低于该比率,您可以直接从文件中获取数据。如果您不这样做,您会发现缩小时绘制代码变得太慢。

在执行此扫描时,编写一次性处理文件所有通道的代码是值得的,这里的缓慢会让你的整个程序感觉迟缓,这里重要的是磁盘 IO,CPU 可以轻松跟上,所以简单的 C++ 代码适合构建最小/最大表,但您不想多次浏览该文件并且希望按顺序执行。

一旦你有了最小/最大表,就把它们保留在身边。您希望尽可能少地返回磁盘,并且想要重新绘制窗口的许多原因不需要您重新扫描最小/最大表。与最初构建它们的磁盘 io 成本相比,保留它们的内存成本并没有那么高。

然后,通过在该像素表示的时间的最大值和最小值之间绘制一系列 1 像素宽的垂直线来绘制波形。如果您从预先构建的最小/最大表中绘图,这应该相当快。

By Zoom, I presume you mean horizontal zoom rather than vertical. The way audio editors do this is to scan the wavform breaking it up into time windows where each pixel in X represents some number of samples. It can be a fractional number, but you can get away with dis-allowing fractional zoom ratios without annoying the user too much. Once you zoom out a bit the max value is always a positive integer and the min value is always a negative integer.

for each pixel on the screen, you need to have to know the minimum sample value for that pixel and the maximum sample value. So you need a function that scans the waveform data in chunks and keeps track of the accumulated max and min for that chunk.

This is slow process, so professional audio editors keep a pre-calculated table of min and max values at some fixed zoom ratio. It might be at 512/1 or 1024/1. When you are drawing with a zoom ration of > 1024 samples/pixel, then you use the pre-calculated table. if you are below that ratio you get the data directly from the file. If you don't do this you will find that you drawing code gets to be too slow when you zoom out.

Its worthwhile to write code that handles all of the channels of the file in an single pass when doing this scanning, slowness here will make your whole program feel sluggish, it's the disk IO that matters here, the CPU has no trouble keeping up, so straightforward C++ code is fine for building the min/max tables, but you don't want to go through the file more than once and you want to do it sequentially.

Once you have the min/max tables, keep them around. You want to go back to the disk as little as possible and many of the reasons for wanting to repaint your window will not require you to rescan your min/max tables. The memory cost of holding on to them is not that high compared to the disk io cost of building them in the first place.

Then you draw the waveform by drawing a series of 1 pixel wide vertical lines between the max value and the min value for the time represented by that pixel. This should be quite fast if you are drawing from pre built min/max tables.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文