Skip to content

Commit

Permalink
Add hard-coded PolyBLEP
Browse files Browse the repository at this point in the history
  • Loading branch information
utokusa committed Jul 28, 2022
1 parent 2e11212 commit 46c3130
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 38 deletions.
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

0 comments on commit 46c3130

Please sign in to comment.