This tutorial describes how to load and play a wave in Synthie. It is assumed that you have completed the tutorial on making an instrument persistent prior to trying this tutorial.
We're going to create the components to play a wave file. First first thing we need is an audio node that can play waves files. Add a new class called CWavePlayer that is derived from CAudioNode. Here is the header for CWavePlayer:
#pragma once
#include "audionode.h"
class CWavePlayer : public CAudioNode
{
public:
CWavePlayer(void);
~CWavePlayer(void);
public:
virtual void Start();
virtual bool Generate();
void SetSamples(short *s, int n) {m_samples = s; m_numsamples = n;}
private:
short *m_samples;
int m_numsamples;
int m_position;
};
And here is the implementation:
#include "StdAfx.h"
#include "WavePlayer.h"
CWavePlayer::CWavePlayer(void)
{
m_samples = NULL;
m_numsamples = 0;
}
CWavePlayer::~CWavePlayer(void)
{
}
void CWavePlayer::Start()
{
m_position = 0;
}
bool CWavePlayer::Generate()
{
if(m_position < m_numsamples)
{
m_frame[0] = m_samples[m_position++] / 32768.0;
m_frame[1] = m_frame[0];
return true;
}
else
{
m_frame[1] = m_frame[0] = 0;
return false;
}
}
Now we need an instrument. Create a new class called CWaveInstrument that is derived from CInstrument. Here is the implementation:
#pragma once
#include "instrument.h"
#include "WavePlayer.h"
class CWaveInstrument :
public CInstrument
{
public:
CWaveInstrument(void);
~CWaveInstrument(void);
virtual void Start();
virtual bool Generate();
virtual void SetNote(CNote *note);
CWavePlayer *GetPlayer() {return &m_wavePlayer;}
private:
CWavePlayer m_wavePlayer;
};
And the implementation:
#include "StdAfx.h"
#include "WaveInstrument.h"
CWaveInstrument::CWaveInstrument(void)
{
}
CWaveInstrument::~CWaveInstrument(void)
{
}
void CWaveInstrument::Start()
{
m_wavePlayer.SetSampleRate(SampleRate());
m_wavePlayer.Start();
}
void CWaveInstrument::SetNote(CNote *note)
{
}
bool CWaveInstrument::Generate()
{
bool valid = m_wavePlayer.Generate();
m_frame[0] = m_wavePlayer.Frame(0);
m_frame[1] = m_frame[0];
return valid;
}
Here's where it gets interesting. Create a new class called CWaveInstrumentFactory. Here's the initial implementation:
#pragma once
#include "WaveInstrument.h"
#include <vector>
class CWaveInstrumentFactory
{
public:
CWaveInstrumentFactory(void);
~CWaveInstrumentFactory(void);
void SetNote(CNote *note);
CWaveInstrument *CreateInstrument();
private:
std::vector<short> m_wave;
};
and the implementation:
#include "StdAfx.h"
#include "WaveInstrumentFactory.h"
#include <cmath>
CWaveInstrumentFactory::CWaveInstrumentFactory(void)
{
for(double time=0; time<2; time += 1. / 44100.)
{
m_wave.push_back(short(3267 * sin(2 * 3.1415 * 1000 * time)));
}
}
CWaveInstrumentFactory::~CWaveInstrumentFactory(void)
{
}
CWaveInstrument *CWaveInstrumentFactory::CreateInstrument()
{
CWaveInstrument *instrument = new CWaveInstrument();
instrument->GetPlayer()->SetSamples(&m_wave[0], (int)m_wave.size());
return instrument;
}
void CWaveInstrumentFactory::SetNote(CNote *note)
{
}
I'm preloading our wave table with a 1000Hz sine wave for two seconds. Since this is just test code, I hard coded in the sample rate.
Finally, we add this to our synthesizer. Add this private member variable to CSynthesizer:
CWaveInstrumentFactory m_waveinstfactory;
Add add this code to CSynthesizer::Generate() (green already exists):
CInstrument *instrument = NULL; if(note->Instrument() == L"ToneInstrument") { instrument = new CToneInstrument(); } else if(note->Instrument() == L"OddSines") { m_oddsinesfactory.SetNote(note); instrument = m_oddsinesfactory.CreateInstrument(); } else if(note->Instrument() == L"Wave") { m_waveinstfactory.SetNote(note); instrument = m_waveinstfactory.CreateInstrument(); }
You'll need a test file. Here's what I used:
<?xml version="1.0" encoding="utf-8"?> <score bpm="60" beatspermeasure="4"> <instrument instrument="Wave"> <note measure="1" beat="1" duration="8" note="C4"/> </instrument> </score>
This should work and play the 2 second tone I preloaded into the wave table.
You'll need a wave file to play. It should be 44100 samples per second, mono or stereo, and .WAV format. The code in Sythie used to read any format, but the DirectShow components in the latest DirectX are broken, so it does not read other formats such as MP3 correctly. Use a program such as GoldWave to convert the sample rate. I'm using drumriff.wav in this example.
Add this function to CWaveInstrumentFactory:
bool CWaveInstrumentFactory::LoadFile(const char *filename)
{
m_wave.clear();
CDirSoundSource m_file;
if(!m_file.Open(filename))
{
CString msg = L"Unable to open audio file: ";
msg += filename;
AfxMessageBox(msg);
return false;
}
for(int i=0; i<m_file.NumSampleFrames(); i++)
{
short frame[2];
m_file.ReadFrame(frame);
m_wave.push_back(frame[0]);
}
m_file.Close();
return true;
}
You'll need to #include "audio/DirSoundSource.h" A close examination of CAudioProcessDoc should have indicated how this class is used to read audio files.
Now add this line to the CSynthesizer constructor:
m_waveinstfactory.LoadFile("drumriff.wav");
Note that there is no leading "L", this is a standard ascii 8 bit string for the filename.
A bit of a problem when you are loading files is that working directory is not set by default. We'll set it to be the solution directory and that's where we will put the drumriff.wav file. Right click on the Synthi project and select properties. Set the configuration to "All Configurations". Expand "Configuration Properties" on the left if necessary and click on "Debugging". Set the Working Directory to $(SolutionDir) This is how it should look:

This should play the audio file now. Adding features to this should be pretty easy.