我最近发现在 iOS 中可以使用 OpenAL 进行音高转换。
我在看 Hollance 的声音银行播放器。它接收分布在整个范围内的 15 个左右的钢琴音符,并通过确定它最接近哪个样本来播放任何音符,并将该样本的音高移动适当的量。这是执行此操作的代码:
- (void) noteOn: (int) midiNoteNumber
gain: (float) gain
{
if (!initialized)
{
NSLog(@"SoundBankPlayer is not initialized yet");
return;
}
int sourceIndex = [self findAvailableSource];
if (sourceIndex != -1)
{
alGetError(); // clear any errors
Note* note = notes + midiNoteNumber;
if (note->bufferIndex != -1)
{
Buffer* buffer = buffers + note->bufferIndex;
Source* source = sources + sourceIndex;
source->noteIndex = midiNoteNumber;
alSourcef(source->sourceId, AL_PITCH, note->pitch / buffer->pitch);
alSourcei(source->sourceId, AL_LOOPING, AL_FALSE);
alSourcef(source->sourceId, AL_REFERENCE_DISTANCE, 100.0f);
alSourcef(source->sourceId, AL_GAIN, gain);
float sourcePos[] = { note->panning, 0.0f, 0.0f };
alSourcefv(source->sourceId, AL_POSITION, sourcePos);
alSourcei(source->sourceId, AL_BUFFER, AL_NONE);
alSourcei(source->sourceId, AL_BUFFER, buffer->bufferId);
ALenum error;
if ((error = alGetError()) != AL_NO_ERROR)
{
NSLog(@"Error attaching buffer to source: %x", error);
return;
}
alSourcePlay(source->sourceId);
if ((error = alGetError()) != AL_NO_ERROR)
{
NSLog(@"Error starting source: %x", error);
return;
}
}
}
}
你可以看到这条线做了音高转换:
alSourcef(source->sourceId, AL_PITCH, note->pitch / buffer->pitch);
不幸的是,这对于同时演奏一捆音符没有好处,因为它占用了太多的 CPU。它是动态的音调变化。
我想要的是为每个钢琴音符创建一个缓冲区,并使用这种音高转换技术填充这些缓冲区。但我看不到如何让 openAL 将声音播放到缓冲区中,而不是通过扬声器播放出来。
有什么方法可以输出 alSourcePlay(source->sourceId);
进入缓冲区?
如果我不能这样做,我有什么选择?我曾尝试使用 DSPDimension 文章中的 smbPitchShift,但它并没有提供良好的保真度:钢琴音符的起音阶段确实丢失了。我想我可以使用免费版的 Dirac3...(我目前没有钱购买完整版,但我认为免费版允许单声道处理,所以我可以破解它)。还有其他选择吗?
编辑:我已经测试了 Dirac3,它也有同样的问题。它似乎包围了攻击。似乎 OpenAL 的音调转换器以某种方式做了 Dirac3 没有做的事情。