C++ 用C+绘制音频波形+/Qt
我有一个大学作业,用C++/Qt显示音频文件的波形。我们应该能够修改用于显示它的比例(以每个屏幕像素的音频样本表示) 到目前为止,我能够:C++ 用C+绘制音频波形+/Qt,c++,qt,plot,real-time,C++,Qt,Plot,Real Time,我有一个大学作业,用C++/Qt显示音频文件的波形。我们应该能够修改用于显示它的比例(以每个屏幕像素的音频样本表示) 到目前为止,我能够: 打开音频文件 阅读样本 以给定的比例绘制样本 为了以给定的比例绘制样本,我尝试了两种策略。假设N是刻度的值: 对于从0到窗口宽度的i,在屏幕像素i处绘制第i*n个音频样本。这是非常快速且时间恒定的,因为我们总是访问相同数量的音频数据点。 但是,它不能正确表示波形,因为我们仅使用1个点的值来表示N个点。 对于从0到N*width的i,在屏幕位置i/(N*
- 打开音频文件
- 阅读样本
- 以给定的比例绘制样本
- 对于从0到窗口宽度的i,在屏幕像素i处绘制第i*n个音频样本。这是非常快速且时间恒定的,因为我们总是访问相同数量的音频数据点。
但是,它不能正确表示波形,因为我们仅使用1个点的值来表示N个点。 - 对于从0到N*width的i,在屏幕位置i/(N*width)绘制第i个音频样本,让Qt找出如何在物理屏幕像素上正确表示该音频样本。
绘制出非常漂亮的波形,但访问数据需要花费大量时间。例如,如果我想每像素显示500个样本,窗口宽度为100px,我必须访问50000个点,然后Qt将这些点绘制为100个物理点(像素)。
换句话说,当Qt/Matplotlib/Matlab/etc将数千个数据点绘制到数量非常有限的物理像素上时,会涉及什么样的操作?仅仅因为我知道如何操作,并且我已经询问了stackoverflow的类似问题,我将参考。稍后我将提供代码 绘制波形是一个真正的问题。我花了半年多的时间试图解决这个问题! 总而言之: 根据报告: 波形视图使用两种蓝色,一种较暗,一种较亮
- 波形的深蓝色部分显示像素所代表区域的最高峰值。在默认缩放级别,Audacity将 显示该像素宽度内的多个样本,因此该像素表示 组中声音最大的样本的值
- 波形的浅蓝色部分显示同一组样本的平均RMS(均方根)值。这是一个艰难的过程 这个区域的声音有多大的指南,但没有办法 单独提取或使用波形的RMS部分
template<typename T>
class CacheHandler {
public:
std::vector<T> data;
vector2d<T> min, max, rms;
CacheHandler(std::vector<T>& data) throw(std::exception);
void addData(std::vector<T>& samples);
/*
irreversible removes data.
Fails if end index is greater than data length
*/
void removeData(int endIndex);
void removeData(int startIndex, int endIndex);
};
模板
类缓存处理程序{
公众:
std::矢量数据;
矢量2D最小值、最大值、均方根值;
CacheHandler(std::vector&data)抛出(std::exception);
void addData(标准::向量和样本);
/*
不可逆删除数据。
如果结束索引大于数据长度,则失败
*/
无效删除数据(内部索引);
void removeData(intstartindex,intendindex);
};
使用此选项:
template<typename T>
inline WaveformPane::CacheHandler<T>::CacheHandler(std::vector<T>& data, int sampleSizeInBits) throw(std::exception)
{
this->data = data;
this->sampleSizeInBits = sampleSizeInBits;
int N = log(data.size()) / log(2);
rms.resize(N); min.resize(N); max.resize(N);
rms[0] = calcRMSSegments(data, 2);
min[0] = getMinPitchSegments(data, 2);
max[0] = getMaxPitchSegments(data, 2);
for (int i = 1; i < N; i++) {
rms[i] = calcRMSSegments(rms[i - 1], 2);
min[i] = getMinPitchSegments(min[i - 1], 2);
max[i] = getMaxPitchSegments(max[i - 1], 2);
}
}
模板
内联waveormpane::CacheHandler::CacheHandler(std::vector&data,int sampleSizeInBits)抛出(std::exception)
{
这->数据=数据;
此->sampleSizeInBits=sampleSizeInBits;
int N=log(data.size())/log(2);
rms.resize(N);min.resize(N);max.resize(N);
rms[0]=计算单元(数据,2);
min[0]=getMinPitchSegments(数据,2);
max[0]=getMaxPitchSegments(数据,2);
对于(int i=1;i
我的建议如下:
给定音频文件中的totalNumSamples
音频样本,以及显示小部件中宽度为widgetWidth
的像素,您可以计算每个像素代表的样本:
// Given an x value (in pixels), returns the appropriate corresponding
// offset into the audio-samples array that represents the
// first sample that should be included in that pixel.
int GetFirstSampleIndexForPixel(int x, int widgetWidth, int totalNumSamples)
{
return (totalNumSamples*x)/widgetWidth;
}
virtual void paintEvent(QPaintEvent * e)
{
QPainter p(this);
for (int x=0; x<widgetWidth; x++)
{
const int firstSampleIndexForPixel = GetFirstSampleIndexForPixel(x, widgetWidth, totalNumSamples);
const int lastSampleIndexForPixel = GetFirstSampleIndexForPixel(x+1, widgetWidth, totalNumSamples)-1;
const int largestSampleValueForPixel = GetMaximumSampleValueInRange(firstSampleIndexForPixel, lastSampleIndexForPixel);
const int smallestSampleValueForPixel = GetMinimumSampleValueInRange(firstSampleIndexForPixel, lastSampleIndexForPixel);
// draw a vertical line spanning all sample values that are contained in this pixel
p.drawLine(x, GetYValueForSampleValue(largestSampleValueForPixel), x, GetYValueForSampleValue(smallestSampleValueForPixel));
}
}
有了它,您只需为
sampleIndexAleftedgeofWidget
和sampleIndexAfterRightEdgeOfWidget
输入不同的值,即可进行缩放/平移,这两个值一起指示要显示的文件的子范围。子采样是将音频信号的频率大幅降低的正常方法。(由于别名的存在,少量使用会更加困难)。平均出一个窗口或滑动窗口会改变数据。这与我问的一个问题非常相似。您可能会发现这里的讨论很有趣:
int GetFirstSampleIndexForPixel(int x, int widgetWidth, int sampleIndexAtLeftEdgeOfWidget, int sampleIndexAfterRightEdgeOfWidget)
{
int numSamplesToDisplay = sampleIndexAfterRightEdgeOfWidget-sampleIndexAtLeftEdgeOfWidget;
return sampleIndexAtLeftEdgeOfWidget+((numSamplesToDisplay*x)/widgetWidth);
}