Skip to content

Commit

Permalink
Octave Shifts with Tunings (surge-synthesizer#1474)
Browse files Browse the repository at this point in the history
Should an octave be 12 notes? Or should an octave be the size
of your current scale? Surge had hardcoded 12 notes in a few
places because that was the size of the scale, but that's
counterintuitive with larger or smaller tunings (and also makes
layered sounds break). So go from 12 -> count. Add unit tests also.

Addresses surge-synthesizer#1473
  • Loading branch information
baconpaul authored Jan 13, 2020
1 parent ec69797 commit ae9fcf9
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 8 deletions.
20 changes: 12 additions & 8 deletions src/common/dsp/SurgeVoice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -431,11 +431,15 @@ template <bool first> void SurgeVoice::calc_ctrldata(QuadFilterChainState* Q, in
else
pb *= (float)scene->pbrange_dn.val.i;

octaveSize = 12.0f;
if( ! storage->isStandardTuning )
octaveSize = storage->currentScale.count;

state.pitch = state.pkey + pb +
localcopy[pitch_id].f * (scene->pitch.extend_range ? 12.f : 1.f) +
(12.0f * localcopy[octave_id].i);
(octaveSize * localcopy[octave_id].i);
modsources[ms_keytrack]->output =
(state.pitch - (float)scene->keytrack_root.val.i) * (1.0f / 12.0f);
(state.pitch - (float)scene->keytrack_root.val.i) * (1.0f / 12.0f); // I didn't change this for octaveSize, I think rightly

if (scene->modsource_doprocess[ms_polyaftertouch])
{
Expand Down Expand Up @@ -533,7 +537,7 @@ bool SurgeVoice::process_block(QuadFilterChainState& Q, int Qe)
(osc1 && (FMmode == fm_2and3to1)))
{
osc[2]->process_block(noteShiftFromPitchParam( (scene->osc[2].keytrack.val.b ? state.pitch : ktrkroot) +
12 * scene->osc[2].octave.val.i,
octaveSize * scene->osc[2].octave.val.i,
2 ),
drift, is_wide);

Expand All @@ -560,7 +564,7 @@ bool SurgeVoice::process_block(QuadFilterChainState& Q, int Qe)
if (FMmode == fm_3to2to1)
{
osc[1]->process_block(noteShiftFromPitchParam((scene->osc[1].keytrack.val.b ? state.pitch : ktrkroot) +
12 * scene->osc[1].octave.val.i,
octaveSize * scene->osc[1].octave.val.i,
1 ),

drift, is_wide, true,
Expand All @@ -569,7 +573,7 @@ bool SurgeVoice::process_block(QuadFilterChainState& Q, int Qe)
else
{
osc[1]->process_block(noteShiftFromPitchParam((scene->osc[1].keytrack.val.b ? state.pitch : ktrkroot) +
12 * scene->osc[1].octave.val.i,
octaveSize * scene->osc[1].octave.val.i,
1),
drift, is_wide);
}
Expand Down Expand Up @@ -598,14 +602,14 @@ bool SurgeVoice::process_block(QuadFilterChainState& Q, int Qe)
{
add_block(osc[1]->output, osc[2]->output, fmbuffer, BLOCK_SIZE_OS_QUAD);
osc[0]->process_block(noteShiftFromPitchParam((scene->osc[0].keytrack.val.b ? state.pitch : ktrkroot) +
12 * scene->osc[0].octave.val.i, 0 ),
octaveSize * scene->osc[0].octave.val.i, 0 ),
drift, is_wide, true,
db_to_linear(localcopy[scene->fm_depth.param_id_in_scene].f));
}
else if (FMmode)
{
osc[0]->process_block(noteShiftFromPitchParam((scene->osc[0].keytrack.val.b ? state.pitch : 60) +
12 * scene->osc[0].octave.val.i,
octaveSize * scene->osc[0].octave.val.i,
0),

drift, is_wide, true,
Expand All @@ -614,7 +618,7 @@ bool SurgeVoice::process_block(QuadFilterChainState& Q, int Qe)
else
{
osc[0]->process_block(noteShiftFromPitchParam((scene->osc[0].keytrack.val.b ? state.pitch : ktrkroot) +
12 * scene->osc[0].octave.val.i,
octaveSize * scene->osc[0].octave.val.i,
0),
drift, is_wide);
}
Expand Down
2 changes: 2 additions & 0 deletions src/common/dsp/SurgeVoice.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ class alignas(16) SurgeVoice
pdata* paramptr;
int route[6];

float octaveSize = 12.0f;

bool osc1, osc2, osc3, ring12, ring23, noise;
int FMmode;
float noisegenL[2], noisegenR[2];
Expand Down
141 changes: 141 additions & 0 deletions src/headless/UnitTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,147 @@ TEST_CASE( "Zero Size Maps", "[tun]" )

}

TEST_CASE( "An Octave is an Octave", "[tun]" )
{
auto surge = surgeOnSine();
REQUIRE( surge.get() );

SECTION( "Untuned OSC Octave" )
{
auto f60 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].osc[0].octave.val.i = -1;
auto f60m1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].osc[0].octave.val.i = 1;
auto f60p1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].osc[0].octave.val.i = 0;
auto f60z = frequencyForNote(surge, 60);
REQUIRE( f60 == Approx( f60z ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60m1 * 2 ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60p1 / 2 ).margin( 0.1 ) );
}

SECTION( "Untuned Scene Octave" )
{
auto f60 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].octave.val.i = -1;
auto f60m1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].octave.val.i = 1;
auto f60p1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].octave.val.i = 0;
auto f60z = frequencyForNote(surge, 60);
REQUIRE( f60 == Approx( f60z ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60m1 * 2 ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60p1 / 2 ).margin( 0.1 ) );
}

SECTION( "Tuned to 12 OSC octave" )
{
Surge::Storage::Scale s = Surge::Storage::readSCLFile("test-data/scl/12-intune.scl" );
surge->storage.retuneToScale(s);

auto f60 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].osc[0].octave.val.i = -1;
auto f60m1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].osc[0].octave.val.i = 1;
auto f60p1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].osc[0].octave.val.i = 0;
auto f60z = frequencyForNote(surge, 60);
REQUIRE( f60 == Approx( f60z ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60m1 * 2 ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60p1 / 2 ).margin( 0.1 ) );
}


SECTION( "Tuned to 12 Scene Octave" )
{
Surge::Storage::Scale s = Surge::Storage::readSCLFile("test-data/scl/12-intune.scl" );
surge->storage.retuneToScale(s);

auto f60 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].octave.val.i = -1;
auto f60m1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].octave.val.i = 1;
auto f60p1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].octave.val.i = 0;
auto f60z = frequencyForNote(surge, 60);
REQUIRE( f60 == Approx( f60z ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60m1 * 2 ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60p1 / 2 ).margin( 0.1 ) );
}

SECTION( "22 note scale OSC Octave" )
{
Surge::Storage::Scale s = Surge::Storage::readSCLFile("test-data/scl/zeus22.scl" );
surge->storage.retuneToScale(s);
REQUIRE( s.count == 22 );

auto f60 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].osc[0].octave.val.i = -1;
auto f60m1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].osc[0].octave.val.i = 1;
auto f60p1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].osc[0].octave.val.i = 0;
auto f60z = frequencyForNote(surge, 60);
REQUIRE( f60 == Approx( f60z ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60m1 * 2 ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60p1 / 2 ).margin( 0.1 ) );
}

SECTION( "22 note scale Scene Octave" )
{
Surge::Storage::Scale s = Surge::Storage::readSCLFile("test-data/scl/zeus22.scl" );
surge->storage.retuneToScale(s);
REQUIRE( s.count == 22 );

auto f60 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].octave.val.i = -1;
auto f60m1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].octave.val.i = 1;
auto f60p1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].octave.val.i = 0;
auto f60z = frequencyForNote(surge, 60);
REQUIRE( f60 == Approx( f60z ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60m1 * 2 ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60p1 / 2 ).margin( 0.1 ) );
}

SECTION( "6 note scale OSC Octave" )
{
Surge::Storage::Scale s = Surge::Storage::readSCLFile("test-data/scl/6-exact.scl" );
surge->storage.retuneToScale(s);
REQUIRE( s.count == 6 );

auto f60 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].osc[0].octave.val.i = -1;
auto f60m1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].osc[0].octave.val.i = 1;
auto f60p1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].osc[0].octave.val.i = 0;
auto f60z = frequencyForNote(surge, 60);
REQUIRE( f60 == Approx( f60z ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60m1 * 2 ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60p1 / 2 ).margin( 0.1 ) );
}

SECTION( "6 note scale Scene Octave" )
{
Surge::Storage::Scale s = Surge::Storage::readSCLFile("test-data/scl/6-exact.scl" );
surge->storage.retuneToScale(s);
REQUIRE( s.count == 6 );

auto f60 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].octave.val.i = -1;
auto f60m1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].octave.val.i = 1;
auto f60p1 = frequencyForNote(surge, 60);
surge->storage.getPatch().scene[0].octave.val.i = 0;
auto f60z = frequencyForNote(surge, 60);
REQUIRE( f60 == Approx( f60z ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60m1 * 2 ).margin( 0.1 ) );
REQUIRE( f60 == Approx( f60p1 / 2 ).margin( 0.1 ) );
}
}

TEST_CASE( "Simple Single Oscillator is Constant", "[dsp]" )
{
SurgeSynthesizer* surge = Surge::Headless::createSurge(44100);
Expand Down

0 comments on commit ae9fcf9

Please sign in to comment.