Source

gb_emulator / gb_emulator / src / gb_sound_wasapi.cpp

/*  Copyright Š 2011 Chris Spencer <spencercw@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include <gb_emulator/gb_sound_wasapi.h>

#include <stdexcept>
#include <string>

#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>

#include <atlbase.h>
#include <atlcom.h>

#include <gb_emulator/constants.h>
#include <gb_emulator/gb.h>
#include <gb_emulator/gb_sound_wasapi_renderer.h>

using boost::lexical_cast;
using std::runtime_error;
using std::string;

GbSoundWasapi::GbSoundWasapi(Gb &gb):
GbSound(gb)
{
	// Initialise COM
	HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	if (FAILED(hr))
	{
		throw runtime_error("failed to initialise COM: " + lexical_cast<string>(hr));
	}
}

GbSoundWasapi::~GbSoundWasapi()
{
	// Uninitialise COM
	CoUninitialize();
}

bool GbSoundWasapi::isAvailable()
{
	// Attempt to get an instance of MMDeviceEnumerator; if it fails then WASAPI is not available
	CComPtr<IMMDeviceEnumerator> devices;
	HRESULT hr = devices.CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER);
	return SUCCEEDED(hr);
}

void GbSoundWasapi::initialise()
{
	// Get the audio endpoint
	CComPtr<IMMDeviceEnumerator> devices;
	HRESULT hr = devices.CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER);
	if (FAILED(hr))
	{
		throw runtime_error("failed to get audio device enumerator: " + lexical_cast<string>(hr));
	}

	CComPtr<IMMDevice> device;
	hr = devices->GetDefaultAudioEndpoint(eRender, eConsole, &device);
	if (FAILED(hr))
	{
		throw runtime_error("failed to get audio device: " + lexical_cast<string>(hr));
	}

	// Create the audio renderer
	{
		GbSoundWasapiRenderer *tmp = new GbSoundWasapiRenderer();
		renderer_.Attach(tmp);
	}
	renderer_->initialise(device, boost::bind(&GbSoundWasapi::saveSampleRate, this, _1),
		boost::bind(&GbSoundWasapi::extrapolate, this, _1));
}

double GbSoundWasapi::poll()
{
	// Generate the sample
	double left, right;
	generateSample(left, right);
	renderer_->pushSample(left, right);

	// Check for any audio events
	renderer_->pollEvents();

	// Return the number of cycles to execute before being called again
	return GbSound::poll();
}

void GbSoundWasapi::pollEvents()
{
	renderer_->pollEvents();
}

void GbSoundWasapi::extrapolate(unsigned samples)
{
	// Generate the requested number of samples
	for (unsigned i = 0; i != samples; ++i)
	{
		double left, right;
		generateSample(left, right);
		renderer_->pushSample(left, right);
	}
}

void GbSoundWasapi::setTimerCallback(double frequency, boost::function<void ()> callback)
{
	renderer_->setTimerCallback(frequency, callback);
}

void GbSoundWasapi::saveSampleRate(unsigned sampleRate)
{
	sampleRate_ = sampleRate;
	sampleCycles_ = static_cast<double>(CPU_CLOCK) / sampleRate;
}