我正在编写一个程序,以 44.1khz 采样率生成正弦波音频。下面的 2 个图在 X 轴上包含 720 个音频样本,在 Y 轴上包含 +/-4000 之间的正弦波(在本例中 4000 表示幅度/音量)。
720 个样本正弦波(1,440 字节的 PCM 16 签名音频)的内容作为背对背数据报发送到音频应用程序,在该应用程序中应再现纯测试音。为了获得纯音,我需要生成较低的图(我必须手动调整频率,直到获得连续的图)。
我的问题是如何从数学上计算有效频率集以适应上述情况。
这就是我制作上述图的方式:
// check sampleRate, channelCount and sampleFormat
if (rAudioFormat.isValid()) {
const auto sampleFormat = rAudioFormat.sampleFormat();
const auto channelCount = rAudioFormat.channelCount();
const auto bytesPerFrame = rAudioFormat.bytesPerFrame();
const auto volume = aVolumePercent / 100.0f;
// payload must have room for at least 1 frame
if (aAudioSizeBytes >= bytesPerFrame) {
static std::default_random_engine gRandomNumberGenerator;
// each frame contains inputChannelCount audio samples
const qint64 frameCount = aAudioSizeBytes / bytesPerFrame;
// pad size in bytes at the end of the datagram.
const qint64 paddingBytes = aAudioSizeBytes % bytesPerFrame;
// allocate raw data for an unpadded audio sample
const auto payload = std::make_unique<char[]>(aAudioSizeBytes - paddingBytes);
switch (aSignalGenerator) {
case SignalGenerator::Monotone:
{
if ((!rGeneratorParams.empty()) &&
(static_cast<size_t>(rAudioFormat.sampleRate()) > 0u)) {
const auto frequency = rGeneratorParams[0];
for (auto t = 0; t < frameCount; ++t) {
const auto value = qSin(
2 * M_PI * frequency * (t % frameCount) /
rAudioFormat.sampleRate());
switch (sampleFormat) {
. . .
case QAudioFormat::SampleFormat::Int16:
{
constexpr auto gMaxVal = std::numeric_limits<int16_t>::max();
const auto dest = reinterpret_cast<int16_t*>(payload.get());
for (auto i = 0; i < channelCount; i++) {
dest[t * channelCount + i] =
static_cast<int16_t>(gMaxVal * volume * value);
}
}
break;
到目前为止,我已经想出了以下代码,它完成了部分工作(我认为)。我知道我需要得到 720 的偶数(因为它需要适合正弦)除数,但我不太知道如何完成此代码以返回与每个除数对应的频率值。考虑到采样率,我可能还应该考虑奈奎斯特来限制最大可再现音调频率。
//! helper function to calculate produce pure tones
//! given nTotalSamples with given nSampleRate.
std::vector<int> getDivisors(int nSampleRate, int nTotalSamples, int nearestMin) {
std::vector<int> divisors;
if ((nSampleRate> 0) && (nearestMin > 0) &&
(nearestMin <= nTotalSamples)) {
for (int i = nearestMin; i < nTotalSamples; ++i) {
if ((nTotalSamples % i) == 0) {
// calculate the corresponding tone frequency
divisors.emplace_back(i);
}
}
}
return divisors;
}
您的帧持续时间是样本数除以采样率。振荡次数是频率乘以持续时间。您希望它是一个整数。因此,只需计算您将得到的数字,然后将其四舍五入到最接近的整数并转换回频率:
double frameDuration = samplesPerFrame / sampleRate;
double fractionalOscillations = frameDuration * desiredFrequency;
double wholeOscillations = round(fractionalOscillations);
double goodFrequency = wholeOscillations / frameDuration;
注意我正在使用
double
,所以我不必担心一路上的任何舍入效果。这可能会导致每个单独的振荡只采集一小部分样本。您计算个人值的方式 - 通过仅向正弦函数提供参数 - 我不认为这是一个问题。
另请注意,您必须使用兼容的设备。如果您使用采样率作为每秒样本数,频率作为 Hz,则效果很好,因为两者都有单位
1/s
。