In this post, I'll briefly explain how to generate sound in real
time in a Windows Store C++ app using XAudio2.
I've always liked messing around with synthesizers. Quite some
time ago, I built a simple synthesizer using Silverlight. It was
cool, but very processor intensive. All the sound was generated in
real time, and was then pushed into buffers which were read by the
media pipeline. The pipeline was never meant for that type of sound
generation so although it worked well, it had a fair bit of
lag.
For a project I'm working on in my spare time, I need to be able
to generate sound in real time for a Windows Store app. The user
interface for this app also has to be really intuitive. It may have
some interesting visualizations, but it'll be mostly standard UI
type controls. I figured this was a good time to try out C++ with
XAML in the Windows Store.
The project
So, I created a new Windows Store C++ plus XAML plus DirectX
app. There are a couple different audio pipelines I could use. I
decided to go with XAudio2 rather than WASAPI, as XAudio2 is much
easier to get into, and has decent performance. Is XAudio2 fast
enough for real-time sound generation? It has been shown to be so
for many apps. In fact, the MorphWiz and Tachyon apps that Jordan
Rudess played before Build 2012 used XAudio2
(although the developer may have converted to WASAPI by now in
order to eek out some extra performance).
I added a single class named "Synthesizer". It's a bit much for
me to call this a "synthesizer", but hey. :)
Keep in mind that I'm not a good C++ programmer; I'm
still re-learning. The code here is shown just as a proof of
concept, not best practices or anything like that.
The Synthesizer header file
#pragma once
//#include <wrl.h>
#include <pch.h>
#include <xaudio2.h>
#include <xaudio2fx.h>
//#include <mmreg.h>
#define VOICE_BUFFER_SAMPLE_COUNT (44100 * 2)
ref class Synthesizer sealed
{
private:
int m_sampleBits;
IXAudio2MasteringVoice* m_masterVoice; // cannot use ComPtr for this
IXAudio2* m_audioEngine;
// this is temp stuff which will be rolled into another class
IXAudio2SourceVoice* m_voice;
XAUDIO2_BUFFER m_buffer;
float m_bufferData[VOICE_BUFFER_SAMPLE_COUNT];
public:
Synthesizer(uint32 sampleRate);
};
There's nothing overly exciting going on in that class right
now. The thing that took me the longest to figure out was that I
couldn't reference any of the IXAudio2* interfaces (other than the
main one) using a ComPtr. The error was suitably cryptic for a noob
like me.
The Synthesizer Class has a single XAudio2 voice (you can have
multiple voices). And no: no destructor, I don't even clean up
after myself. Remember what I said about this not showing best
practices?
Synthesizer class file
#include <pch.h>
#include <math.h>
#include <Synthesizer.h>
Synthesizer::Synthesizer(uint32 sampleRate)
{
m_sampleBits = 32; // 32 for IEEE float
// initialize XAudio 2
DX::ThrowIfFailed(XAudio2Create(&m_audioEngine, 0, XAUDIO2_DEFAULT_PROCESSOR));
DX::ThrowIfFailed(m_audioEngine->CreateMasteringVoice(&m_masterVoice, XAUDIO2_DEFAULT_CHANNELS, sampleRate));
// 2 channels because this is stereo
int channels = 2;
// set up wave format using my good friend WAVEFORMATEX
WAVEFORMATEX wfx;
wfx.wBitsPerSample = m_sampleBits;
wfx.nAvgBytesPerSec = sampleRate * channels * m_sampleBits / 8;
wfx.nChannels = channels;
wfx.nBlockAlign = channels * m_sampleBits / 8;
wfx.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; // or could use WAVE_FORMAT_PCM
wfx.nSamplesPerSec = sampleRate;
wfx.cbSize = 0; // set to zero for PCM or IEEE float
DX::ThrowIfFailed(m_audioEngine->CreateSourceVoice(&m_voice, (WAVEFORMATEX*)&wfx));
//float noteFrequency = 22.5; // A0
float noteFrequency = 55; // A1
//float noteFrequency = 110; // A2
//float noteFrequency = 220; // A3
//float noteFrequency = 440; // A4
// there's probably a #define for this somewhere
float pi = 3.14159265;
// fill the buffer
for (int i = 0; i < VOICE_BUFFER_SAMPLE_COUNT; i+=2)
{
m_bufferData[i] = sin(i * 2 * pi * noteFrequency/sampleRate);
// stereo buffer. Slight detuning of the second tone
m_bufferData[i+1] = sin(i * 2 * pi * (noteFrequency + 2)/sampleRate);
}
// the buffer will be looped infinitely
m_buffer.AudioBytes = VOICE_BUFFER_SAMPLE_COUNT * m_sampleBits / 8;
m_buffer.PlayBegin = 0;
m_buffer.PlayLength = 0; // play entire buffer
m_buffer.LoopBegin = 0;
m_buffer.LoopLength = 0; // loop entire buffer
m_buffer.LoopCount = XAUDIO2_LOOP_INFINITE;
m_buffer.pAudioData = (BYTE *)&m_bufferData;
m_buffer.pContext = NULL;
// wire up the buffer
DX::ThrowIfFailed(m_voice->SubmitSourceBuffer(&m_buffer));
// start playing sound. Yeah, this is in the constructor. Yeah, I know.
DX::ThrowIfFailed(m_voice->Start(0));
}
The code is commented in-line and should be reasonably easy to
follow. Basically all I do is initialize XAudio2, fill a buffer
with samples of a sine wave, then tell XAudio2 to play that buffer,
looping it forever.
References
Then, I added a synth instance to the header file for the main
XAML page, and then from the main XAML page's constructor, I simply
created an instance:
DirectXPage.xaml.cpp header addition
#pragma once
#include "DirectXPage.g.h"
#include "SimpleTextRenderer.h"
#include "BasicTimer.h"
#include <Synthesizer.h>
namespace PeteBrown_DroneSynthApp
{
/// <summary>
/// A DirectX page that can be used on its own. Note that it may not be used within a Frame.
/// </summary>
[Windows::Foundation::Metadata::WebHostHidden]
public ref class DirectXPage sealed
{
public:
...
private:
...
Synthesizer^ m_synth;
};
}
DirectXPage.xaml.cpp constructor addition
DirectXPage::DirectXPage() :
m_renderNeeded(true),
m_lastPointValid(false)
{
InitializeComponent();
...
m_synth = ref new Synthesizer(44100);
}
Once again, yeah, I know I'm doing all this from the
constructor. Don't shoot me :)
I then ran the app, and got the wonderful DX + XAML UI (two bits
of text) with what sounds like a hearing test playing in the
background. Clearly you can take this much further and do all sorts
of neat things with audio generation in real time. For me,
this was just a little proof-of-concept I had to work through
before investing any real time in this side project. Concept
proven, I'll move on to some other parts of the app.
For more information on XAudio2 on Windows, see MSDN.
XAudio2 is a technology shared with the Windows Store (x86, 64, and
ARM), the desktop, Xbox 360, and with Windows Phone 8, so once you
learn it, you can apply it in all three places. For more
information on XAudio2 on Windows Phone 8, see this MSDN
page.