【问题标题】:Draw sound wave with possibility to zoom in/out绘制具有放大/缩小可能性的声波
【发布时间】:2010-02-21 08:19:35
【问题描述】:

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

现在我的问题是我想创建一个与来自Cool Edit Pro 的控件类似的控件,该控件绘制歌曲的波形表示,并能够放大/缩小波形的选定部分等。选择我可以这样做:

TInterval EditZone = WaveForm->GetSelection();

其中 TInterval 有这种形式:

struct TInterval
{
    long Start;
    long End;
}

在复杂绘图方面,我是一个初学者,因此任何关于如何使用 BASS 返回的样本数据创建歌曲的波形表示并具有放大/缩小功能的提示将不胜感激。

我正在用 C++ 编写我的项目,但我可以理解 C#、Delphi 代码,所以如果你愿意,你也可以用最后两种语言发布 sn-ps :)

感谢 DrOptix

【问题讨论】:

  • 我使用 CodeGear C++ Builder 在 Windows 上工作,所以对于 GUI,我使用 VCL

标签: c++ audio waveform bass


【解决方案1】:

通过缩放,我认为您的意思是水平缩放而不是垂直缩放。音频编辑器这样做的方式是扫描波形,将其分解为时间窗口,其中 X 中的每个像素代表一定数量的样本。它可以是一个小数,但您可以不使用小数缩放比例,而不会过多地惹恼用户。缩小一点后,最大值始终为正整数,最小值始终为负整数。

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

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

在做这个扫描的时候写一次处理文件所有通道的代码是值得的,这里的慢会让你的整个程序感觉迟钝,这里重要的是磁盘IO,CPU保持没有问题向上,如此简单的 C++ 代码可以很好地构建最小/最大表,但您不想多次浏览该文件并且您希望按顺序执行。

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

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

【讨论】:

  • 一个非常完整的答案。这里只有一个小补充:我知道的绘制波形的程序甚至将预先计算的最小/最大表持久地缓存在一个文件中。
  • 是的,它是水平缩放。也谢谢你的回答,我会尝试使用这个想法来实现一个控件。
  • @h0b0: 是峰值文件,它们可以使第二次重新打开文件更便宜,但第一次打开文件时根本没有帮助,用户不喜欢他们在磁盘上乱扔垃圾的方式。这是一个判断它们是否对您的应用有意义。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 好的,我得到了这个部分:> 你需要控制每个像素的样本数量(缩放级别)但是当我使用浮点样本时,你建议使用什么来绘制 (1.40129846432482 E-045, 0, 9224.40234375, 1.7402837732327E-039, 1.74042390307914E-039) 是向下/向上取整解决方案还是我真的必须准确?我的意思是1.40129846432482E-045可以被绘图功能看成1没有问题?感谢您的快速回复
    • 一些绘图 API 支持浮点数。如果没有,那么您可能必须自己添加支持。例如,如果 y 值为 1,则在 1 处绘制一个黑点(在白色背景上)。如果值为 1.5,则在 1 和 2 上绘制一个灰点(50% 色调)。
    • 做 50% 的色调并不那么容易。您没有考虑到色彩空间不是线性的事实。半亮度实际上约为 21% ... TBH 你最好只是四舍五入到最接近的整数并在接受混叠的同时以这种方式处理它。无论如何,它不太可能那么糟糕。
    【解决方案4】:

    使用开源 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;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2016-05-25
      • 1970-01-01
      • 1970-01-01
      • 2023-03-10
      • 2022-11-06
      • 2019-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多