Update to v081r02 release.

byuu says:

This release adds nall/dsp, which is a new framework for audio DSP
effects. It currently supports the usual fractional hermite resampling
and volume adjustments from ruby; but it also adds balance, and
arbitrary precision input and output (still limited to two channels, and
still signed audio [just subtract 1<<(bits-1) from an unsigned value.])
Internally, all samples are converted to doubles in the range of -1.0 to
+1.0, to allow for far greater precision with the hermite resampler and
volume/balance/etc adjustments.

As a result of this, all of the extra resampling/volume code from
ruby::audio has been removed. bsnes pulls in a copy of nall::dsp to
handle that stuff now.
This commit is contained in:
Tim Allen 2011-08-14 20:34:11 +10:00
parent 423d9ba00d
commit 71763f2d98
19 changed files with 439 additions and 175 deletions

6
bsnes/nall/dsp.hpp Executable file
View File

@ -0,0 +1,6 @@
#ifndef NALL_DSP_HPP
#define NALL_DSP_HPP
#include <nall/dsp/core.hpp>
#endif

226
bsnes/nall/dsp/core.hpp Executable file
View File

@ -0,0 +1,226 @@
#ifndef NALL_DSP_EFFECT_HPP
#define NALL_DSP_EFFECT_HPP
#include <nall/stdint.hpp>
namespace nall {
struct dsp {
inline void set_precision(unsigned precision);
inline void set_frequency(double frequency);
inline void set_volume(double volume);
inline void set_balance(double balance);
inline void set_echo(double echo);
inline void set_resampler_frequency(double frequency);
inline void sample(signed lchannel, signed rchannel);
inline bool pending();
inline void read(signed &lchannel, signed &rchannel);
inline signed clamp(const unsigned bits, const signed x);
inline void clear();
inline dsp();
inline ~dsp();
protected:
struct Settings {
unsigned precision;
double frequency;
double volume;
double balance;
double echo;
//internal
double intensity;
} settings;
struct Resampler {
double frequency;
//internal
double fraction;
double step;
} resampler;
inline void resampler_run();
struct Buffer {
double *sample[2];
uint16_t rdoffset;
uint16_t wroffset;
inline double& operator()(bool channel, signed offset) { return sample[channel][(uint16_t)(rdoffset + offset)]; }
inline void write(bool channel, signed offset, double data) { sample[channel][(uint16_t)(wroffset + offset)] = data; }
} buffer;
struct Output {
double *sample[2];
uint16_t rdoffset;
uint16_t wroffset;
inline double& operator()(bool channel, signed offset) { return sample[channel][(uint16_t)(rdoffset + offset)]; }
inline void write(bool channel, signed offset, double data) { sample[channel][(uint16_t)(wroffset + offset)] = data; }
} output;
inline void adjust_volume();
inline void adjust_balance();
inline void adjust_echo();
};
void dsp::set_precision(unsigned precision) {
settings.precision = precision;
settings.intensity = 1 << (settings.precision - 1);
}
void dsp::set_frequency(double frequency) {
settings.frequency = frequency;
resampler.fraction = 0;
resampler.step = settings.frequency / resampler.frequency;
}
void dsp::set_volume(double volume) {
settings.volume = volume;
}
void dsp::set_balance(double balance) {
settings.balance = balance;
}
void dsp::set_echo(double echo) {
settings.echo = echo;
}
void dsp::set_resampler_frequency(double frequency) {
resampler.frequency = frequency;
resampler.fraction = 0;
resampler.step = settings.frequency / resampler.frequency;
}
void dsp::sample(signed lchannel, signed rchannel) {
buffer.write(0, 0, (double)lchannel / settings.intensity);
buffer.write(1, 0, (double)rchannel / settings.intensity);
buffer.wroffset++;
#ifdef DSP_NO_RESAMPLER
output.write(0, 0, buffer(0, 0));
output.write(1, 0, buffer(1, 0));
output.wroffset++;
buffer.rdoffset++;
#else
resampler_run();
#endif
}
bool dsp::pending() {
return output.rdoffset != output.wroffset;
}
void dsp::read(signed &lchannel, signed &rchannel) {
adjust_volume();
adjust_balance();
adjust_echo();
lchannel = clamp(settings.precision, output(0, 0) * settings.intensity);
rchannel = clamp(settings.precision, output(1, 0) * settings.intensity);
output.rdoffset++;
}
void dsp::resampler_run() {
//4-tap hermite
while(resampler.fraction <= 1.0) {
for(unsigned n = 0; n < 2; n++) {
double a = buffer(n, -3);
double b = buffer(n, -2);
double c = buffer(n, -1);
double d = buffer(n, -0);
const double tension = 0.0; //-1 = low, 0 = normal, +1 = high
const double bias = 0.0; //-1 = left, 0 = even, +1 = right
double mu1, mu2, mu3, m0, m1, a0, a1, a2, a3;
mu1 = resampler.fraction;
mu2 = mu1 * mu1;
mu3 = mu2 * mu1;
m0 = (b - a) * (1.0 + bias) * (1.0 - tension) / 2.0;
m0 += (c - b) * (1.0 - bias) * (1.0 - tension) / 2.0;
m1 = (c - b) * (1.0 + bias) * (1.0 - tension) / 2.0;
m1 += (d - c) * (1.0 - bias) * (1.0 - tension) / 2.0;
a0 = +2 * mu3 - 3 * mu2 + 1;
a1 = mu3 - 2 * mu2 + mu1;
a2 = mu3 - mu2;
a3 = -2 * mu3 + 3 * mu2;
double result = (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c);
output.write(n, 0, result);
}
output.wroffset++;
resampler.fraction += resampler.step;
}
buffer.rdoffset++;
resampler.fraction -= 1.0;
}
void dsp::adjust_volume() {
output(0, 0) *= settings.volume;
output(1, 0) *= settings.volume;
}
void dsp::adjust_balance() {
if(settings.balance < 0.0) output(1, 0) *= 1.0 + settings.balance;
if(settings.balance > 0.0) output(0, 0) *= 1.0 - settings.balance;
}
void dsp::adjust_echo() {
//...
}
signed dsp::clamp(const unsigned bits, const signed x) {
const signed b = 1U << (bits - 1);
const signed m = (1U << (bits - 1)) - 1;
return (x > m) ? m : (x < -b) ? -b : x;
}
void dsp::clear() {
resampler.fraction = 0.0;
resampler.step = 1.0;
for(unsigned n = 0; n < 65536; n++) {
buffer.sample[0][n] = 0.0;
buffer.sample[1][n] = 0.0;
output.sample[0][n] = 0.0;
output.sample[1][n] = 0.0;
}
buffer.rdoffset = 0;
buffer.wroffset = 0;
output.rdoffset = 0;
output.wroffset = 0;
}
dsp::dsp() {
settings.precision = 16;
settings.frequency = 44100.0;
settings.volume = 1.0;
settings.balance = 0.0;
settings.echo = 0.0;
settings.intensity = 1 << (settings.precision - 1);
resampler.frequency = 44100.0;
buffer.sample[0] = new double[65536];
buffer.sample[1] = new double[65536];
output.sample[0] = new double[65536];
output.sample[1] = new double[65536];
clear();
}
dsp::~dsp() {
delete[] buffer.sample[0];
delete[] buffer.sample[1];
delete[] output.sample[0];
delete[] output.sample[1];
}
}
#endif

View File

@ -32,6 +32,10 @@ namespace nall {
return true;
}
static bool read(const string &filename, const uint8_t *&data, unsigned &size) {
return file::read(filename, (uint8_t*&)data, size);
}
static bool write(const string &filename, const uint8_t *data, unsigned size) {
file fp;
if(fp.open(filename, mode::write) == false) return false;

110
bsnes/nall/ips.hpp Executable file
View File

@ -0,0 +1,110 @@
#ifndef NALL_IPS_HPP
#define NALL_IPS_HPP
#include <nall/file.hpp>
#include <nall/stdint.hpp>
#include <nall/string.hpp>
namespace nall {
struct ips {
inline bool apply();
inline void source(const uint8_t *data, unsigned size);
inline void modify(const uint8_t *data, unsigned size);
inline bool source(const string &filename);
inline bool modify(const string &filename);
inline ips();
inline ~ips();
uint8_t *data;
unsigned size;
const uint8_t *sourceData;
unsigned sourceSize;
const uint8_t *modifyData;
unsigned modifySize;
};
bool ips::apply() {
if(modifySize < 8) return false;
if(modifyData[0] != 'P') return false;
if(modifyData[1] != 'A') return false;
if(modifyData[2] != 'T') return false;
if(modifyData[3] != 'C') return false;
if(modifyData[4] != 'H') return false;
if(data) delete[] data;
data = new uint8_t[16 * 1024 * 1024 + 65536](); //maximum size of IPS patch + single-tag padding
size = sourceSize;
memcpy(data, sourceData, sourceSize);
unsigned offset = 5;
while(true) {
unsigned address, length;
if(offset > modifySize - 3) break;
address = modifyData[offset++] << 16;
address |= modifyData[offset++] << 8;
address |= modifyData[offset++] << 0;
if(address == 0x454f46) { //EOF
if(offset == modifySize) return true;
if(offset == modifySize - 3) {
size = modifyData[offset++] << 16;
size |= modifyData[offset++] << 8;
size |= modifyData[offset++] << 0;
return true;
}
}
if(offset > modifySize - 2) break;
length = modifyData[offset++] << 8;
length |= modifyData[offset++] << 0;
if(length) { //Copy
if(offset > modifySize - length) break;
while(length--) data[address++] = modifyData[offset++];
} else { //RLE
if(offset > modifySize - 3) break;
length = modifyData[offset++] << 8;
length |= modifyData[offset++] << 0;
if(length == 0) break; //illegal
while(length--) data[address++] = modifyData[offset];
offset++;
}
size = max(size, address);
}
delete[] data;
data = 0;
return false;
}
void ips::source(const uint8_t *data, unsigned size) {
sourceData = data, sourceSize = size;
}
void ips::modify(const uint8_t *data, unsigned size) {
modifyData = data, modifySize = size;
}
bool ips::source(const string &filename) {
return file::read(filename, sourceData, sourceSize);
}
bool ips::modify(const string &filename) {
return file::read(filename, modifyData, modifySize);
}
ips::ips() : data(0), sourceData(0), modifyData(0) {
}
ips::~ips() {
if(data) delete[] data;
if(sourceData) delete[] sourceData;
if(modifyData) delete[] modifyData;
}
}
#endif

View File

@ -1,9 +1,5 @@
class Audio {
public:
static const char *Volume;
static const char *Resample;
static const char *ResampleRatio;
static const char *Handle;
static const char *Synchronize;
static const char *Frequency;

View File

@ -161,6 +161,11 @@ VideoInterface::~VideoInterface() { term(); }
/* AudioInterface */
const char *Audio::Handle = "Handle";
const char *Audio::Synchronize = "Synchronize";
const char *Audio::Frequency = "Frequency";
const char *Audio::Latency = "Latency";
void AudioInterface::driver(const char *driver) {
if(p) term();
@ -269,7 +274,25 @@ const char* AudioInterface::driver_list() {
"None";
}
#include "ruby_audio.cpp"
bool AudioInterface::init() {
if(!p) driver();
return p->init();
}
void AudioInterface::term() {
if(p) {
delete p;
p = 0;
}
}
bool AudioInterface::cap(const string& name) { return p ? p->cap(name) : false; }
any AudioInterface::get(const string& name) { return p ? p->get(name) : false; }
bool AudioInterface::set(const string& name, const any& value) { return p ? p->set(name, value) : false; }
void AudioInterface::sample(uint16_t left, uint16_t right) { if(p) p->sample(left, right); }
void AudioInterface::clear() { if(p) p->clear(); }
AudioInterface::AudioInterface() : p(0) {}
AudioInterface::~AudioInterface() { term(); }
/* InputInterface */

View File

@ -1,6 +1,6 @@
/*
ruby
version: 0.06a (2011-02-27)
version: 0.07 (2011-08-14)
license: public domain
*/
@ -66,14 +66,6 @@ public:
private:
Audio *p;
unsigned volume;
//resample unit
double hermite(double mu, double a, double b, double c, double d);
bool resample_enabled;
double r_step, r_frac;
int r_left[4], r_right[4];
};
class InputInterface {

View File

@ -1,133 +0,0 @@
const char *Audio::Volume = "Volume";
const char *Audio::Resample = "Resample";
const char *Audio::ResampleRatio = "ResampleRatio";
const char *Audio::Handle = "Handle";
const char *Audio::Synchronize = "Synchronize";
const char *Audio::Frequency = "Frequency";
const char *Audio::Latency = "Latency";
bool AudioInterface::init() {
if(!p) driver();
return p->init();
}
void AudioInterface::term() {
if(p) {
delete p;
p = 0;
}
}
bool AudioInterface::cap(const string& name) {
if(name == Audio::Volume) return true;
if(name == Audio::Resample) return true;
if(name == Audio::ResampleRatio) return true;
return p ? p->cap(name) : false;
}
any AudioInterface::get(const string& name) {
if(name == Audio::Volume) return volume;
if(name == Audio::Resample) return resample_enabled;
if(name == Audio::ResampleRatio);
return p ? p->get(name) : false;
}
bool AudioInterface::set(const string& name, const any& value) {
if(name == Audio::Volume) {
volume = any_cast<unsigned>(value);
return true;
}
if(name == Audio::Resample) {
resample_enabled = any_cast<bool>(value);
return true;
}
if(name == Audio::ResampleRatio) {
r_step = any_cast<double>(value);
r_frac = 0;
return true;
}
return p ? p->set(name, value) : false;
}
//4-tap hermite interpolation
double AudioInterface::hermite(double mu1, double a, double b, double c, double d) {
const double tension = 0.0; //-1 = low, 0 = normal, 1 = high
const double bias = 0.0; //-1 = left, 0 = even, 1 = right
double mu2, mu3, m0, m1, a0, a1, a2, a3;
mu2 = mu1 * mu1;
mu3 = mu2 * mu1;
m0 = (b - a) * (1 + bias) * (1 - tension) / 2;
m0 += (c - b) * (1 - bias) * (1 - tension) / 2;
m1 = (c - b) * (1 + bias) * (1 - tension) / 2;
m1 += (d - c) * (1 - bias) * (1 - tension) / 2;
a0 = +2 * mu3 - 3 * mu2 + 1;
a1 = mu3 - 2 * mu2 + mu1;
a2 = mu3 - mu2;
a3 = -2 * mu3 + 3 * mu2;
return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c);
}
void AudioInterface::sample(uint16_t left, uint16_t right) {
int s_left = (int16_t)left;
int s_right = (int16_t)right;
if(volume != 100) {
s_left = sclamp<16>((double)s_left * (double)volume / 100.0);
s_right = sclamp<16>((double)s_right * (double)volume / 100.0);
}
r_left [0] = r_left [1];
r_left [1] = r_left [2];
r_left [2] = r_left [3];
r_left [3] = s_left;
r_right[0] = r_right[1];
r_right[1] = r_right[2];
r_right[2] = r_right[3];
r_right[3] = s_right;
if(resample_enabled == false) {
if(p) p->sample(left, right);
return;
}
while(r_frac <= 1.0) {
int output_left = sclamp<16>(hermite(r_frac, r_left [0], r_left [1], r_left [2], r_left [3]));
int output_right = sclamp<16>(hermite(r_frac, r_right[0], r_right[1], r_right[2], r_right[3]));
r_frac += r_step;
if(p) p->sample(output_left, output_right);
}
r_frac -= 1.0;
}
void AudioInterface::clear() {
r_frac = 0;
r_left [0] = r_left [1] = r_left [2] = r_left [3] = 0;
r_right[0] = r_right[1] = r_right[2] = r_right[3] = 0;
if(p) p->clear();
}
AudioInterface::AudioInterface() {
p = 0;
volume = 100;
resample_enabled = false;
r_step = r_frac = 0;
r_left [0] = r_left [1] = r_left [2] = r_left [3] = 0;
r_right[0] = r_right[1] = r_right[2] = r_right[3] = 0;
}
AudioInterface::~AudioInterface() {
term();
}

View File

@ -1,7 +1,7 @@
class Interface {
public:
virtual void video_refresh(const uint16_t *data, bool hires, bool interlace, bool overscan) {}
virtual void audio_sample(uint16_t l_sample, uint16_t r_sample) {}
virtual void audio_sample(int16_t l_sample, int16_t r_sample) {}
virtual int16_t input_poll(bool port, Input::Device device, unsigned index, unsigned id) { return 0; }
virtual void message(const string &text) { print(text, "\n"); }

View File

@ -1,11 +1,17 @@
namespace SNES {
namespace Info {
static const char Name[] = "bsnes";
static const char Version[] = "081.01";
static const char Version[] = "081.02";
static const unsigned SerializerVersion = 21;
}
}
/*
bsnes - SNES emulator
author: byuu
license: GPLv2
*/
#include <libco/libco.h>
#include <nall/algorithm.hpp>

View File

@ -21,7 +21,7 @@ struct Interface : public SNES::Interface {
if(pinput_poll) pinput_poll();
}
void audio_sample(uint16_t left, uint16_t right) {
void audio_sample(int16_t left, int16_t right) {
if(paudio_sample) return paudio_sample(left, right);
}

View File

@ -5,6 +5,7 @@
#include <nall/compositor.hpp>
#include <nall/config.hpp>
#include <nall/directory.hpp>
#include <nall/dsp.hpp>
#include <nall/filemap.hpp>
#include <nall/input.hpp>
#include <nall/resource.hpp>
@ -59,4 +60,5 @@ private:
void saveGeometry();
};
extern nall::dsp dspaudio;
extern Application application;

View File

@ -37,6 +37,7 @@ void Configuration::create() {
attach(audio.synchronize = true, "audio.synchronize");
attach(audio.mute = false, "audio.mute");
attach(audio.volume = 100, "audio.volume");
attach(audio.balance = 0, "audio.balance");
attach(audio.latency = 60, "audio.latency");
attach(audio.inputFrequency = 32000, "audio.inputFrequency");
attach(audio.outputFrequency = 44100, "audio.outputFrequency");

View File

@ -20,6 +20,7 @@ struct Configuration : public configuration {
bool synchronize;
bool mute;
unsigned volume;
unsigned balance;
unsigned latency;
unsigned inputFrequency;
unsigned outputFrequency;

View File

@ -137,9 +137,14 @@ void Interface::video_refresh(const uint16_t *data, bool hires, bool interlace,
}
}
void Interface::audio_sample(uint16_t left, uint16_t right) {
if(config.audio.mute) left = right = 0;
audio.sample(left, right);
void Interface::audio_sample(int16_t lchannel, int16_t rchannel) {
if(config.audio.mute) lchannel = 0, rchannel = 0;
dspaudio.sample(lchannel, rchannel);
while(dspaudio.pending()) {
signed lsample, rsample;
dspaudio.read(lsample, rsample);
audio.sample(lsample, rsample);
}
}
int16_t Interface::input_poll(bool port, SNES::Input::Device device, unsigned index, unsigned id) {

View File

@ -16,7 +16,7 @@ struct Filter : public library {
struct Interface : public SNES::Interface {
void video_refresh(const uint16_t *data, bool hires, bool interlace, bool overscan);
void audio_sample(uint16_t left, uint16_t right);
void audio_sample(int16_t left, int16_t right);
int16_t input_poll(bool port, SNES::Input::Device device, unsigned index, unsigned id);
void message(const string &text);

View File

@ -1,6 +1,7 @@
#include "base.hpp"
#include "interface.cpp"
#include "config.cpp"
nall::dsp dspaudio;
Application application;
void Application::main(int argc, char **argv) {
@ -96,17 +97,20 @@ void Application::main(int argc, char **argv) {
audio.driver(config.audio.driver);
audio.set(Audio::Handle, mainWindow.viewport.handle());
audio.set(Audio::Synchronize, config.audio.synchronize);
audio.set(Audio::Volume, config.audio.volume);
audio.set(Audio::Latency, config.audio.latency);
audio.set(Audio::Frequency, config.audio.outputFrequency);
audio.set(Audio::Resample, true);
audio.set(Audio::ResampleRatio, (double)config.audio.inputFrequency / (double)config.audio.outputFrequency);
if(audio.init() == false) {
MessageWindow::critical(mainWindow, "Failed to initialize audio.");
audio.driver("None");
audio.init();
}
dspaudio.set_precision(16); //16-bit signed audio
dspaudio.set_volume((double)config.audio.volume / 100.0);
dspaudio.set_balance((double)((signed)config.audio.balance - 100) / 100.0);
dspaudio.set_frequency(config.audio.inputFrequency);
dspaudio.set_resampler_frequency(config.audio.outputFrequency);
input.driver(config.input.driver);
input.set(Input::Handle, mainWindow.viewport.handle());
if(input.init() == false) {

View File

@ -4,10 +4,12 @@ void AudioSettings::create() {
title.setText("Audio Settings");
title.setFont(application.titleFont);
volumeLabel.setText("Volume:");
volumeSlider.setLength(201);
frequencyLabel.setText("Frequency:");
frequencySlider.setLength(2001);
volumeLabel.setText("Volume:");
volumeSlider.setLength(201);
balanceLabel.setText("Balance:");
balanceSlider.setLength(201);
panelLayout.setMargin(5);
panelLayout.append(panel, SettingsWindow::PanelWidth, ~0, 5);
@ -15,34 +17,48 @@ void AudioSettings::create() {
layout.append(title, ~0, 0, 5);
volumeLayout.append(volumeLabel, 70, 0);
volumeLayout.append(volumeValue, 60, 0);
volumeLayout.append(volumeSlider, ~0, 0);
layout.append(volumeLayout);
frequencyLayout.append(frequencyLabel, 70, 0);
frequencyLayout.append(frequencyValue, 60, 0);
frequencyLayout.append(frequencySlider, ~0, 0);
layout.append(frequencyLayout);
volumeLayout.append(volumeLabel, 70, 0);
volumeLayout.append(volumeValue, 60, 0);
volumeLayout.append(volumeSlider, ~0, 0);
layout.append(volumeLayout);
balanceLayout.append(balanceLabel, 70, 0);
balanceLayout.append(balanceValue, 60, 0);
balanceLayout.append(balanceSlider, ~0, 0);
layout.append(balanceLayout);
layout.append(spacer, ~0, ~0);
settingsWindow.append(panelLayout);
frequencySlider.onChange = [this] {
config.audio.inputFrequency = frequencySlider.position() + 31000;
dspaudio.set_frequency(config.audio.inputFrequency);
frequencyValue.setText({ config.audio.inputFrequency, "hz" });
};
volumeSlider.onChange = [this] {
config.audio.volume = volumeSlider.position();
audio.set(Audio::Volume, config.audio.volume);
dspaudio.set_volume((double)config.audio.volume / 100.0);
volumeValue.setText({ config.audio.volume, "%" });
};
frequencySlider.onChange = [this] {
config.audio.inputFrequency = frequencySlider.position() + 31000;
audio.set(Audio::ResampleRatio, (double)config.audio.inputFrequency / (double)config.audio.outputFrequency);
frequencyValue.setText({ config.audio.inputFrequency, "hz" });
balanceSlider.onChange = [this] {
config.audio.balance = balanceSlider.position();
dspaudio.set_balance((double)((signed)config.audio.balance - 100) / 100.0);
balanceValue.setText({ (signed)config.audio.balance - 100 });
};
frequencySlider.setPosition(config.audio.inputFrequency - 31000);
frequencyValue.setText({ config.audio.inputFrequency, "hz" });
volumeSlider.setPosition(config.audio.volume);
volumeValue.setText({ config.audio.volume, "%" });
frequencySlider.setPosition(config.audio.inputFrequency - 31000);
frequencyValue.setText({ config.audio.inputFrequency, "hz" });
balanceSlider.setPosition(config.audio.balance);
balanceValue.setText({ (signed)config.audio.balance - 100 });
}

View File

@ -4,15 +4,20 @@ struct AudioSettings {
VerticalLayout layout;
Label title;
HorizontalLayout frequencyLayout;
Label frequencyLabel;
Label frequencyValue;
HorizontalSlider frequencySlider;
HorizontalLayout volumeLayout;
Label volumeLabel;
Label volumeValue;
HorizontalSlider volumeSlider;
HorizontalLayout frequencyLayout;
Label frequencyLabel;
Label frequencyValue;
HorizontalSlider frequencySlider;
HorizontalLayout balanceLayout;
Label balanceLabel;
Label balanceValue;
HorizontalSlider balanceSlider;
Widget spacer;