Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Do Not Merge] Feature/anti alias mode #47

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 61 additions & 8 deletions src/dsp/Oscillator.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ class Oscillator

// Return oscillator voltage value.
// Angle is in radian.
flnum oscillatorVal (flnum angleRad, flnum shapeModulationAmount)
flnum oscillatorVal (flnum angleRad, flnum shapeModulationAmount, flnum angleRadInc)
{
const flnum firstAngleRad = angleRad;
const flnum firstAngleRadInc = angleRadInc;
const flnum secondAngleRad = shapePhase (angleRad * 2, shapeModulationAmount);
const flnum secondAngleRadInc = angleRadInc * 2.0;

flnum currentSample = 0.0;
const auto sinGain = p->getSinGain();
Expand All @@ -46,11 +48,11 @@ class Oscillator
if (sinGain > 0.0)
currentSample += sinWave (secondAngleRad) * sinGain;
if (squareGain > 0.0)
currentSample += squareWave (secondAngleRad) * squareGain;
currentSample += squareWave (secondAngleRad, secondAngleRadInc) * squareGain;
if (sawGain > 0.0)
currentSample += sawWave (secondAngleRad) * sawGain;
currentSample += sawWave (secondAngleRad, secondAngleRadInc) * sawGain;
if (subSquareGain > 0.0)
currentSample += squareWave (firstAngleRad) * subSquareGain;
currentSample += squareWave (firstAngleRad, firstAngleRadInc) * subSquareGain;
if (noiseGain > 0.0)
currentSample += noiseWave() * noiseGain;

Expand All @@ -68,6 +70,7 @@ class Oscillator
}

private:
static constexpr bool ANTI_ALIAS = false;
IOscillatorParams* const p;
std::random_device seedGen;
std::default_random_engine randEngine;
Expand All @@ -83,19 +86,68 @@ class Oscillator
return angle;
}

flnum polyBlep (flnum angle, flnum angleInc)
{
flnum dt = angleInc / (2.0 * pi);
flnum t = angle / (2.0 * pi);
if (t < dt)
{
t /= dt;
return -t * t + 2.0 * t - 1.0;
}
else if (t > 1.0 - dt)
{
t = (t - 1.0) / dt;
return t * t + 2.0 * t + 1.0;
}
else
{
return 0.0;
}
}

// TODO: extract waveforms as function

static flnum sinWave (flnum angle)
{
return std::sin (angle);
}

static flnum squareWave (flnum angle)
static flnum squareWaveNaive (flnum angle)
{
return angle < pi ? 1.0 : -1.0;
}

static flnum sawWave (flnum angle)
flnum squareWave (flnum angle, flnum angleInc)
{
if (ANTI_ALIAS)
{
flnum val = squareWaveNaive (angle);
val += polyBlep (angle, angleInc);
val -= polyBlep (fmod (angle + pi, 2.0 * pi), angleInc);
return val;
}
else
{
return squareWaveNaive (angle);
}
}

flnum sawWave (flnum angle, flnum angleInc)
{
if (ANTI_ALIAS)
{
flnum val = sawWaveNaive (angle);
val -= polyBlep (angle, angleInc);
return val;
}
else
{
return sawWaveNaive (angle);
}
}

static flnum sawWaveNaive (flnum angle)
{
return std::min (2.0 * angle / (2.0 * pi), 2.0) - 1.0;
}
Expand All @@ -112,11 +164,12 @@ class Oscillator
smoothedShape.update();
flnum shape = std::clamp<flnum> (smoothedShape.get(), 0.0, 1.0);
flnum normalizedAngle = std::clamp (angle / (2.0 * pi), 0.0, 1.0);
flnum shaped = 2.0 * pi * (shape * map (normalizedAngle) + (1.0 - shape) * normalizedAngle);
flnum shaped = 2.0 * pi * (shape * curve (normalizedAngle) + (1.0 - shape) * normalizedAngle);
return shaped;
}

flnum map (flnum in0to1)
// Taeks [0,1] and returns [0,1]
flnum curve (flnum in0to1)
{
flnum out0to1 = in0to1 * in0to1 * in0to1 * in0to1 * in0to1 * in0to1 * in0to1 * in0to1;
return out0to1;
Expand Down
2 changes: 1 addition & 1 deletion src/synth/SynthVoice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ void FancySynthVoice::renderNextBlock (IAudioBuffer* outputBuffer, int startSamp
{
envManager.switchTarget (p->getEnvForAmpOn());
flnum currentSample = osc.oscillatorVal (
currentAngle, lfo->getLevel (idx) * lfo->getShapeAmount());
currentAngle, lfo->getLevel (idx) * lfo->getShapeAmount(), smoothedAngleDelta.get());
flnum rawAmp = level * envManager.getLevel();
smoothedAmp.set (rawAmp);
smoothedAmp.update();
Expand Down
59 changes: 30 additions & 29 deletions tests/dsp/OscillatorTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@ namespace onsen
{
//==============================================================================
// Oscillator
// TODO: Add tests for anti-aliasing

TEST (OscillatorTest, Sin)
{
// Only sin oscillator is used
OscillatorParamsMock params { 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
Oscillator osc (&params);
// Note that sin's algle is twice angleRad parameter of ocillatorVal()
EXPECT_NEAR (osc.oscillatorVal (0.0, 0.0), 0.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 2.0) / 2.0, 0.0), 1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi) / 2.0, 0.0), 0.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi * 3.0 / 2.0) / 2.0, 0.0), -1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0) / 2.0, 0.0), 0.5, EPSILON);
EXPECT_NEAR (osc.oscillatorVal (0.0, 0.0, 0.5 * pi), 0.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 2.0) / 2.0, 0.0, 0.5 * pi), 1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi) / 2.0, 0.0, 0.5 * pi), 0.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi * 3.0 / 2.0) / 2.0, 0.0, 0.5 * pi), -1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0) / 2.0, 0.0, 0.5 * pi), 0.5, EPSILON);
}

TEST (OscillatorTest, Square)
Expand All @@ -35,11 +36,11 @@ TEST (OscillatorTest, Square)
OscillatorParamsMock params { 0.0, 1.0, 0.0, 0.0, 0.0, 0.0 };
Oscillator osc (&params);
// Note that square's algle is twice angleRad parameter of ocillatorVal()
EXPECT_NEAR (osc.oscillatorVal (0.0, 0.0), 1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 2.0) / 2.0, 0.0), 1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi) / 2.0, 0.0), -1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi * 3.0 / 2.0) / 2.0, 0.0), -1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0) / 2.0, 0.0), 1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal (0.0, 0.0, 0.5 * pi), 1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 2.0) / 2.0, 0.0, 0.5 * pi), 1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi) / 2.0, 0.0, 0.5 * pi), -1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi * 3.0 / 2.0) / 2.0, 0.0, 0.5 * pi), -1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0) / 2.0, 0.0, 0.5 * pi), 1.0, EPSILON);
}

TEST (OscillatorTest, Saw)
Expand All @@ -48,11 +49,11 @@ TEST (OscillatorTest, Saw)
OscillatorParamsMock params { 0.0, 0.0, 1.0, 0.0, 0.0, 0.0 };
Oscillator osc (&params);
// Note that saw's algle is twice angleRad parameter of ocillatorVal()
EXPECT_NEAR (osc.oscillatorVal (0.0, 0.0), -1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 2.0) / 2.0, 0.0), -0.5, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi) / 2.0, 0.0), 0.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi * 3.0 / 2.0) / 2.0, 0.0), 0.5, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0) / 2.0, 0.0), -0.83333331346511841, EPSILON);
EXPECT_NEAR (osc.oscillatorVal (0.0, 0.0, 0.5 * pi), -1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 2.0) / 2.0, 0.0, 0.5 * pi), -0.5, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi) / 2.0, 0.0, 0.5 * pi), 0.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi * 3.0 / 2.0) / 2.0, 0.0, 0.5 * pi), 0.5, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0) / 2.0, 0.0, 0.5 * pi), -0.83333331346511841, EPSILON);
}

TEST (OscillatorTest, SubSquare)
Expand All @@ -61,11 +62,11 @@ TEST (OscillatorTest, SubSquare)
OscillatorParamsMock params { 0.0, 0.0, 0.0, 1.0, 0.0, 0.0 };
Oscillator osc (&params);

EXPECT_NEAR (osc.oscillatorVal (0.0, 0.0), 1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 2.0), 0.0), 1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi), 0.0), -1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi * 3.0 / 2.0), 0.0), -1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0), 0.0), 1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal (0.0, 0.0, 0.5 * pi), 1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 2.0), 0.0, 0.5 * pi), 1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi), 0.0, 0.5 * pi), -1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi * 3.0 / 2.0), 0.0, 0.5 * pi), -1.0, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0), 0.0, 0.5 * pi), 1.0, EPSILON);
}

TEST (OscillatorTest, Noise)
Expand All @@ -80,7 +81,7 @@ TEST (OscillatorTest, Noise)
// Generate value and calculate mean
for (int i = 0; i < n; ++i)
{
flnum val = osc.oscillatorVal (0.0, 0.0);
flnum val = osc.oscillatorVal (0.0, 0.0, 0.5 * pi);
EXPECT_LE (val, 1.0);
EXPECT_GE (val, 0.0);
vals.push_back (val);
Expand Down Expand Up @@ -114,23 +115,23 @@ TEST (OscillatorTest, ChangeShapeAndMixWaves)
// Use lax epsilon because shape value is smoothed
constexpr flnum LAX_EPSILON = 0.001;

EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0), 0.0), 2.1993587017059326, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 3.0), 0.0), 2.5326919555664062, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0), 0.0, 0.5 * pi), 2.1993587017059326, EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 3.0), 0.0, 0.5 * pi), 2.5326919555664062, EPSILON);
params.shape = 0.5;
// Wait for oscillatorVal becames stable
for (int i = 0; i < NUM_UPDATE; i++)
{
osc.oscillatorVal (0, 0.0);
osc.oscillatorVal (0, 0.0, 0.5 * pi);
}
EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0), 0.0), 1.6666688919067383, LAX_EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 3.0), 0.0), 2.1997506618499756, LAX_EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0), 0.0, 0.5 * pi), 1.6666688919067383, LAX_EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 3.0), 0.0, 0.5 * pi), 2.1997506618499756, LAX_EPSILON);
params.shape = 1.0;
// Wait for oscillatorVal becames stable
for (int i = 0; i < NUM_UPDATE; i++)
{
osc.oscillatorVal (0, 0.0);
osc.oscillatorVal (0, 0.0, 0.5 * pi);
}
EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0), 0.0), 1.0000048875808716, LAX_EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 3.0), 0.0), 1.0012624263763428, LAX_EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 6.0), 0.0, 0.5 * pi), 1.0000048875808716, LAX_EPSILON);
EXPECT_NEAR (osc.oscillatorVal ((pi / 3.0), 0.0, 0.5 * pi), 1.0012624263763428, LAX_EPSILON);
}
} // namespace onsen