我正在为毕业写一个声音编辑器。我正在使用BASS从MP3,WAV,OGG等文件中提取样本,并添加回声,镶边等DSP效果。简单地说,我制作了我的框架,应用了从position1到position2,剪切/粘贴管理的效果。
现在我的问题是我想要创建一个与Cool Edit Pro类似的控件,用于绘制歌曲的波形表示,并且能够放大/缩小波形的选定部分等。选择之后,我可以做类似的事情:
TInterval EditZone = WaveForm->GetSelection();
其中TInterval有这种形式:
struct TInterval
{
long Start;
long End;
}
对于复杂的绘图,我是初学者所以任何关于如何使用BASS返回的样本数据创建歌曲的波形表示的提示,能够放大/缩小都将受到赞赏。
我正在用C ++编写我的项目,但我可以理解C#,Delphi代码,所以如果你想要,你也可以用最后两种语言发布片段:)
Thanx DrOptix
答案 0 :(得分:5)
通过缩放,我认为你的意思是水平缩放而不是垂直。音频编辑器这样做的方法是扫描波形,将其分解为时间窗口,其中X中的每个像素代表一定数量的样本。它可以是一个小数,但你可以通过dis-allow分数缩放比例而不会过多地扰乱用户。缩小一点后,最大值始终为正整数,最小值始终为负整数。
对于屏幕上的每个像素,您需要知道该像素的最小样本值和最大样本值。因此,您需要一个以块的形式扫描波形数据的功能,并跟踪该块的累积最大值和最小值。
这是一个缓慢的过程,因此专业音频编辑器会以某个固定的缩放比率保留预先计算的最小值和最大值表。它可能是512/1或1024/1。当您使用>的缩放比例绘图时1024个样本/像素,然后使用预先计算的表。如果您低于该比率,则直接从文件中获取数据。如果你不这样做,你会发现缩小时绘图代码会变得太慢。
在执行此扫描时编写处理文件所有通道的代码是值得的,这里的缓慢会使整个程序感觉迟钝,这是重要的磁盘IO,CPU没有问题保持如此简单的C ++代码可以用于构建最小/最大表,但是您不希望多次浏览该文件并且您希望按顺序执行它。
一旦你有了最小/最大表,就把它们放在一边。您希望尽可能少地返回磁盘,并且想要重新绘制窗口的许多原因不需要您重新扫描最小/最大表。与首先构建它们的磁盘成本相比,保留它们的内存成本并不高。
然后通过在该像素表示的时间的最大值和最小值之间绘制一系列1像素宽的垂直线来绘制波形。如果您从预先构建的最小/最大表中绘制,这应该非常快。
答案 1 :(得分:2)
我最近自己这样做了。正如Marius建议您需要确定每列像素中有多少样本。然后计算出最小值和最大值,然后绘制从最大值到最小值的垂直线。
作为第一关,这看似很好。您将得到的问题是,当您缩小时,从磁盘检索样本将花费太长时间。作为解决方案,我在音频文件旁边建立了一个“峰值”文件。峰值文件存储n个样本组的最小/最大对。玩n直到你得到合适的数量取决于你。就个人而言,我发现128个样本在大小和速度之间是一个很好的权衡。还值得记住的是,除非您正在绘制大于65536像素的控件,否则您不需要将此峰值信息存储为超过16位值的值,这样可以节省一些空间。
答案 2 :(得分:0)
难道你不只是在2画布上绘制样本点吗?您应该知道文件每秒有多少样本(从标题中读取),然后在y轴上绘制值。由于您希望能够放大和缩小,因此需要控制每个像素的样本数(缩放级别)。接下来,您可以获取每个像素的平均采样点数(例如,如果每个像素有5个样本,则取每5个点的平均值。然后,您可以使用2d绘图api在点之间绘制线条。
答案 3 :(得分:0)
使用开源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;
}
}