5

So I'm picking up C++ after a long hiatus and I had the idea to create a program which can generate music based upon strings of numbers at runtime (was inspired by the composition of Pi done by some people) with the eventual goal being some sort of procedural music generation software.

So far I have been able to make a really primitive version of this with the Beep() function and just feeding through the first so and so digits of Pi as a test. Works like a charm.

What I'm looking for now is how I could kick it up a notch and get some higher quality sound being made (because Beep() literally is the most primitive sound... ever) and I realized I have absolutely no idea how to do this. What I need is either a library or some sort of API that can:

1) Generate sound without pre-existing file. I want the result to be 100% generated by code and not rely on any samples, optimally.

2) If I could get something going that would be capable of playing multiple sounds at a time, like be able to play chords or a melody with a beat, that would be nice.

3) and If I could in any way control the wave it plays (kinda like chiptune mixers can) via equation or some other sort of data, that'd be super helpful.

I don't know if this is a weird request or I just researched it using the wrong terms, but I just wasn't able to find anything along these lines or at least nothing that was well documented at all. :/

If anyone can help, I'd really appreciate it.

EDIT: Also, apparently I'm just super not used to asking stuff on forums, my target platform is Windows (7, specifically, although I wouldn't think that matters).

AniMerrill
  • 63
  • 1
  • 6
  • So, I'm assuming windows because you mentioned `beep()`, but you should really tell us what platform you are targeting. C++ has no notion of audio, anything you use will be platform dependent (unless you find a library which abstracts that away from you, but still, that may not be a requirement). – Ed S. Jul 26 '12 at 21:17
  • It will be on Windows, thanks for pointing that out I'll edit it. – AniMerrill Jul 26 '12 at 21:18
  • @AniMerrill, I know of no easy way (in the Windows API) for your program to generate sound data to be played. A library to do it is probably your best bet. – chris Jul 26 '12 at 21:22
  • [FMOD Ex](http://www.fmod.org/products/fmodex.html) should fit your reqirements (custom DSPs, [playback from back from mem buffer](http://stackoverflow.com/questions/4125981/c-fmod-ex-playing-a-pcm-array-buffer-in-real-time)). – Georg Fritzsche Jul 26 '12 at 21:22
  • You can use Windows' waveOut API which is fairly simple. Or you could opt for a more full-fledged library like BASS. – Michael Jul 26 '12 at 21:24
  • use a free VSTi, it's shouldn't be too difficult to create a very simple VST host. – Karoly Horvath Jul 26 '12 at 21:31
  • It's not C++, but you may want to check out Pure Data (http://puredata.info/). – Emile Cormier Jul 26 '12 at 22:26

2 Answers2

2

I use portaudio (http://www.portaudio.com/). It will let you create PCM streams in a portable way. Then you just push the samples into the stream, and they will play.

@edit: using PortAudio is pretty easy. You initialize the library. I use floating point samples to make it super easy. I do it like this:

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;

Then later when you want to play sounds you create a stream, 2 channels for stereo, at 44khz, good for mp3 audio:

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

Then you implement the callback to fill the PCM audio stream. The callback is a c function, but I just call through to my C++ class to handle the audio. I ripped this from my code, and it may not be 100% correct now as I removed a ton of stuff you won't care about. But its works kind of like this:

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.
}
Rafael Baptista
  • 11,181
  • 5
  • 39
  • 59
  • I've seen PortAudio pop up in a few different topics related to this one, so I think I might give it a shot. Downloaded the latest version and the one thing that really stumps me is that all the code is in C rather than C++. I know it can be done, but I have no experience crossing the two languages. Do you have any advice about this? I'm currently using Code::Blocks IDE with a MinGW GCC compiler, if that helps any. – AniMerrill Jul 26 '12 at 22:29
  • I ripped the critical bits from one of my apps. – Rafael Baptista Jul 26 '12 at 22:43
  • Thank you for all the help with this, I was just wondering if you could help me with one thing more. For some reason, I can't for the life of me figure out how to completely link PortAudio with my IDE. I've compiled it, tried scouring the web for what I might be missing, but no matter what I seem to always come up missing the definitions of important functions. Like I can get like... initialization and terminate to work for some reason, but it doesn't recognize anything else. – AniMerrill Jul 29 '12 at 01:08
  • Disregard that last comment... figured it out XD Thanks for all your help. – AniMerrill Jul 29 '12 at 02:39
0

I answered a very similar question on this earlier this week: Note Synthesis, Harmonics (Violin, Piano, Guitar, Bass), Frequencies, MIDI . In your case if you don't want to rely on samples then the wavetable method is out. So your simplest option would be to dynamically vary the frequency and amplitude of sinusoids over time, which is easy but will sound pretty terrible (like a cheap Theremin). Your only real option would be a more sophisticated synthesis algorithm such as one of the Physical Modelling ones (eg Karplus-Strong). That would be an interesting project, but be warned that it does require something of a mathematical background.

You can indeed use something like Portaudio as Rafael has mentioned to physically get the sound out of the PC, in fact I think Portaudio is the best option for that. But generating the data so that it sounds musical is by far your biggest challenge.

Community
  • 1
  • 1
the_mandrill
  • 29,792
  • 6
  • 64
  • 93
  • Right now having something that sounds like real instruments, or really anything better than a cheap theremin, is the least of my concerns since- at least until this thing is more sophisticated -I kinda like the idea of having songs that sound like cheesy atari music. What I'm going for is basic composition and hopefully some sort of portability that will eventually allow me to use more complex stuff, or even surrender and use samples. I'm thinkin Portaudio sounds pretty good, I just need to get it to work now. – AniMerrill Jul 26 '12 at 22:57
  • Ok. Bear in mind though that Portaudio is really just a means of getting raw audio signals out of the soundcard - it won't help you generate any frequencies. You'll have to do that by hand or or find a signal processing library that implements these low level functions. – the_mandrill Jul 27 '12 at 10:06
  • I am also fine with this. All of this project is more about experimentation than producing anything commercial worthy or something like that. Just something that intrigued me lately. – AniMerrill Jul 29 '12 at 01:11