使用C++在运行时生成声音

5
我已经有很久没有学习C++了,现在我想创造一个程序,在运行时基于数字序列生成音乐(受到一些人使用Pi的作曲的启发),最终目标是创建一种类似流程式音乐生成软件。
到目前为止,通过Beep()函数以及将Pi的头几个数字输入进行测试,我已经能够制作出一个非常原始的版本。效果不错。
现在我想让它更进一步,制作出更高质量的声音(因为Beep()真的是最原始的声音……),但我完全不知道如何做到这一点。我需要的是某个库或者API,可以实现以下功能:
1)生成无需预先存在的文件的声音。我希望结果完全由代码生成,不依赖任何样本。
2)如果我能够同时播放多个声音,例如能够弹奏和弦或旋律与节拍,那会更好。

3) 如果我能以某种方式控制波形的播放(就像chiptune混音器一样)通过方程或其他数据,那将非常有帮助。

我不知道这是否是一个奇怪的请求,或者我只是使用了错误的术语进行研究,但我没有找到任何类似的东西,或者至少没有任何很好记录的资料。 :/

如果有人能帮忙,我会非常感激。

编辑:另外,显然我不习惯在论坛上提问,我的目标平台是Windows(特别是Windows 7,尽管我认为这并不重要)。


所以,我假设你使用的是Windows平台,因为你提到了beep(),但你应该真正告诉我们你要针对哪个平台。C ++没有音频概念,任何你使用的都将取决于平台(除非你找到一个可以为你抽象这个问题的库,但即使如此,这也可能不是必需的)。 - Ed S.
它将在Windows上运行,谢谢指出,我会进行编辑。 - AniMerrill
@AniMerrill,我不知道有什么简单的方法(在Windows API中)可以让你的程序生成要播放的声音数据。使用库可能是你最好的选择。 - chris
使用一个免费的VSTi,创建一个非常简单的VST宿主应该不难。 - Karoly Horvath
这不是C++,但你可能想要查看Pure Data(http://puredata.info/)。 - Emile Cormier
显示剩余2条评论
2个回答

2
我使用portaudio(http://www.portaudio.com/),它可以以可移植的方式创建PCM流。然后,您只需将采样推送到流中即可播放。
@edit:使用PortAudio非常容易。您需要初始化库。我使用浮点采样使其非常简单。我是这样做的:
PaError err = Pa_Initialize();
if ( err != paNoError ) 
   return false;

mPaParams.device = Pa_GetDefaultOutputDevice();
if ( mPaParams.device == paNoDevice ) 
   return false;

mPaParams.channelCount = NUM_CHANNELS;
mPaParams.sampleFormat = paFloat32;
mPaParams.suggestedLatency = 
   Pa_GetDeviceInfo( mPaParams.device )->defaultLowOutputLatency;
mPaParams.hostApiSpecificStreamInfo = NULL;

之后,当您想要播放声音时,您可以创建一个流,立体声使用2个通道,采用44khz的采样率,适合mp3音频:

PaError err = Pa_OpenStream( &mPaStream,
                             NULL, // no input
                             &mPaParams,
                             44100, // params
                             NUM_FRAMES, // frames per buffer
                             0,
                             sndCallback,
                             this
                           );

然后您需要实现回调函数来填充PCM音频流。回调函数是一个C函数,但我只需调用我的C++类来处理音频即可。我从我的代码中提取了这部分内容,因为您可能不关心其中的大量细节,所以它可能现在不是100%正确的。但它的工作原理类似于这样:

static int sndCallback( const void*                     inputBuffer, 
                        void*                           outputBuffer,
                        unsigned long                   framesPerBuffer,
                        const PaStreamCallbackTimeInfo* timeInfo,
                        PaStreamCallbackFlags           statusFlags,
                        void*                           userData )
{
  Snd* snd = (Snd*)userData;
  return snd->callback( (float*)outputBuffer, framesPerBuffer );
}

u32 Snd::callback( float* outbuf, u32 nFrames )
{
   mPlayMutex.lock(); // use mutexes because this is asyc code!

   // clear the output buffer
   memset( outbuf, 0, nFrames * NUM_CHANNELS * sizeof( float ));

   // mix all the sounds.
   if ( mChannels.size() ) 
   {   
      // I have multiple audio sources I'm mixing. That's what mChannels is.
      for ( s32 i = mChannels.size(); i > 0; i-- ) 
      {
         for ( u32 j = 0; j < frameCount * NUM_CHANNELS; j++ ) 
         {
             float f = outbuf[j] + getNextSample( i ) // <------------------- your code here!!!
             if ( f >  1.0 ) f = 1.0;     // clamp it so you don't get clipping.
             if ( f < -1.0 ) f = -1.0;
             outbuf[j] = f;
         }
      }
   }
   mPlayMutex.unlock_p();
   return 1; // when you are done playing audio return zero.
}

我在几个与这个相关的话题中看到了PortAudio的出现,所以我想我可能会试一试。下载了最新版本,唯一让我困惑的是所有的代码都是用C而不是C++编写的。我知道这是可以做到的,但我没有跨越这两种语言的经验。你有任何关于这方面的建议吗?我目前正在使用Code::Blocks IDE和MinGW GCC编译器,如果这有帮助的话。 - AniMerrill
我从我的一个应用程序中提取了关键部分。 - Rafael Baptista
谢谢你在这方面提供的所有帮助,我只是想知道你是否可以再帮我一件事。由于某种原因,我无论如何都无法完全将PortAudio与我的IDE链接起来。我已经编译了它,尝试在网上搜索可能缺少的内容,但似乎无论我怎么做,都会缺少重要函数的定义。就像我可以让初始化和终止工作,但它不认识其他任何东西。 - AniMerrill
忽略上一个评论...我已经解决了 XD 感谢你的所有帮助。 - AniMerrill

0

我在本周早些时候回答了一个非常类似的问题:音符合成、谐波(小提琴、钢琴、吉他、低音提琴)、频率、MIDI。如果您不想依赖样本,那么波表方法就行不通了。因此,您最简单的选择是随时间动态变化正弦波的频率和振幅,这很容易实现,但听起来会非常糟糕(像廉价的电子琴)。您唯一真正的选择是使用更复杂的合成算法,例如物理建模算法之一(例如Karplus-Strong)。这将是一个有趣的项目,但请注意,它确实需要一定的数学背景。

正如Rafael所提到的,您确实可以使用像Portaudio这样的东西将声音从PC中输出,事实上我认为Portaudio是最好的选择。但是,生成数据使其听起来像音乐是迄今为止您面临的最大挑战。


现在,拥有听起来像真正的乐器或比廉价的电子琴更好的东西,对我来说并不是最重要的事情,因为至少在这个项目变得更加复杂之前,我有点喜欢让歌曲听起来像廉价的Atari音乐。我的目标是基本的作曲,并希望最终能够实现某种可移植性,使我能够使用更复杂的东西,甚至放弃并使用样本。我认为Portaudio听起来很不错,现在只需要让它工作就可以了。 - AniMerrill
好的。但请记住,Portaudio实际上只是一种从声卡获取原始音频信号的方法 - 它不会帮助你生成任何频率。你必须手动完成这个工作或者找到一个实现这些底层函数的信号处理库。 - the_mandrill
我对此也感到满意。整个项目更多地是关于实验而非生产任何商业价值的东西之类的。只是最近引起了我的兴趣。 - AniMerrill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接