Loading and playing a wave

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.

A wave player audio node

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;
    }
}
This is the absolute minimum wave player. There's no pitch control or speed control of any kind. There's no looping or loop points. Only mono waves are supported. It's minimalism at it's best. Note that we do keep our waves as shorts in this example, so I divide by 32768 to convert it to the range we expect in our program.

A wave player instrument

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;
}
More minimalism. No envelope and only mono waves are supported. I didn't even put in an implementation for SetNote().

A wave player factory

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.

Loading the wave data from a file

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:

Debugging properties page

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

CSE 471