Skip to content

Commit

Permalink
Support Absolute UNI mode in Window and S&H (#1452)
Browse files Browse the repository at this point in the history
The Window and S&H oscillators didn't support Absolute
mode for Unison. So

1. Make a direct load of an FXP availble so we can unit test them
2. Set up unit tests to compare all the uni-featuring oscillators between
   absolute and relative
3. Modify the Window oscillator to obey Absolute, thus making the test pass

Closes #1284
  • Loading branch information
baconpaul authored Jan 5, 2020
1 parent cb4f4ea commit d24c1b7
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 12 deletions.
1 change: 1 addition & 0 deletions src/common/SurgeSynthesizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class alignas(16) SurgeSynthesizer
// unsigned int getParameterFlags (long index);
void loadRaw(const void* data, int size, bool preset = false);
void loadPatch(int id);
bool loadPatchByPath(const char* fxpPath, int categoryId, const char* name );
void incrementPatch(bool nextPrev);
void incrementCategory(bool nextPrev);

Expand Down
24 changes: 18 additions & 6 deletions src/common/SurgeSynthesizerIO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,17 +150,21 @@ void SurgeSynthesizer::loadPatch(int id)
patchid = id;

Patch e = storage.patch_list[id];
loadPatchByPath( e.path.generic_string().c_str(), e.category, e.name.c_str() );
}

FILE* f = fopen(e.path.generic_string().c_str(), "rb");
bool SurgeSynthesizer::loadPatchByPath( const char* fxpPath, int categoryId, const char* patchName )
{
FILE* f = fopen(fxpPath, "rb");
if (!f)
return;
return false;
fxChunkSetCustom fxp;
fread(&fxp, sizeof(fxChunkSetCustom), 1, f);
if ((vt_read_int32BE(fxp.chunkMagic) != 'CcnK') || (vt_read_int32BE(fxp.fxMagic) != 'FPCh') ||
(vt_read_int32BE(fxp.fxID) != 'cjs3'))
{
fclose(f);
return;
return false;
}

int cs = vt_read_int32BE(fxp.chunkSize);
Expand All @@ -174,9 +178,16 @@ void SurgeSynthesizer::loadPatch(int id)

storage.getPatch().comment = "";
storage.getPatch().author = "";
storage.getPatch().category = storage.patch_category[e.category].name;
current_category_id = e.category;
storage.getPatch().name = e.name;
if( categoryId >= 0 )
{
storage.getPatch().category = storage.patch_category[categoryId].name;
}
else
{
storage.getPatch().category = "direct-load";
}
current_category_id = categoryId;
storage.getPatch().name = patchName;

loadRaw(data, cs, true);
free(data);
Expand Down Expand Up @@ -213,6 +224,7 @@ void SurgeSynthesizer::loadPatch(int id)
** Notify the host display that the patch name has changed
*/
updateDisplay();
return true;
}

void SurgeSynthesizer::loadRaw(const void* data, int size, bool preset)
Expand Down
13 changes: 12 additions & 1 deletion src/common/dsp/WindowOscillator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,18 @@ void WindowOscillator::process_block(float pitch, float drift, bool stereo, bool
if (stereo)
memset(IOutputR, 0, BLOCK_SIZE_OS * sizeof(int));

float Detune = localcopy[oscdata->p[5].param_id_in_scene].f;
float Detune;
if( oscdata->p[5].absolute )
{
auto pitchmult_inv =
std::max(1.0, dsamplerate_os * (1 / 8.175798915) * storage->note_to_pitch_inv( std::min(148.f,pitch)));

Detune = localcopy[oscdata->p[5].param_id_in_scene].f * pitchmult_inv * 1.f / 440.f;
}
else
{
Detune = localcopy[oscdata->p[5].param_id_in_scene].f;
}
for (int l = 0; l < ActiveSubOscs; l++)
{
Sub.DriftLFO[l][0] = drift_noise(Sub.DriftLFO[l][1]);
Expand Down
79 changes: 74 additions & 5 deletions src/headless/UnitTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,24 +137,24 @@ std::shared_ptr<SurgeSynthesizer> surgeOnSine()
** frequency measure (which works great for the sine patch and poorly for others
** At one day we could do this with autocorrelation instead but no need now.
*/
double frequencyForNote( std::shared_ptr<SurgeSynthesizer> surge, int note )
double frequencyForNote( std::shared_ptr<SurgeSynthesizer> surge, int note, int seconds = 2, bool channel = 0 )
{
auto events = Surge::Headless::makeHoldNoteFor( note, 44100 * 2, 64 );
auto events = Surge::Headless::makeHoldNoteFor( note, 44100 * seconds, 64 );
float *buffer;
int nS, nC;
Surge::Headless::playAsConfigured( surge.get(), events, &buffer, &nS, &nC );

REQUIRE( nC == 2 );
REQUIRE( nS >= 44100 * 2 );
REQUIRE( nS <= 44100 * 2 + 4 * BLOCK_SIZE );
REQUIRE( nS >= 44100 * seconds );
REQUIRE( nS <= 44100 * seconds + 4 * BLOCK_SIZE );

// Trim off the leading and trailing
int nSTrim = (int)(nS / 2 * 0.8);
int start = (int)( nS / 2 * 0.05 );
float *leftTrimmed = new float[nSTrim];

for( int i=0; i<nSTrim; ++i )
leftTrimmed[i] = buffer[ (i + start) * 2 ];
leftTrimmed[i] = buffer[ (i + start) * 2 + channel ];

// OK so now look for sample times between positive/negative crosses
int v = -1;
Expand Down Expand Up @@ -295,6 +295,75 @@ TEST_CASE( "Simple Single Oscillator is Constant", "[dsp]" )

}

TEST_CASE( "Unison Absolute and Relative", "[osc]" )
{
auto surge = std::shared_ptr<SurgeSynthesizer>( Surge::Headless::createSurge(44100) );
REQUIRE( surge );

auto assertRelative = [surge](const char* pn) {
REQUIRE( surge->loadPatchByPath( pn, -1, "Test" ) );
auto f60_0 = frequencyForNote( surge, 60, 5, 0 );
auto f60_1 = frequencyForNote( surge, 60, 5, 1 );

auto f60_avg = 0.5 * ( f60_0 + f60_1 );

auto f72_0 = frequencyForNote( surge, 72, 5, 0 );
auto f72_1 = frequencyForNote( surge, 72, 5, 1 );
auto f72_avg = 0.5 * ( f72_0 + f72_1 );

// In relative mode, the average frequencies should double, as should the individual outliers
REQUIRE( f72_avg / f60_avg == Approx( 2 ).margin( 0.01 ) );
REQUIRE( f72_0 / f60_0 == Approx( 2 ).margin( 0.01 ) );
REQUIRE( f72_1 / f60_1 == Approx( 2 ).margin( 0.01 ) );
};

auto assertAbsolute = [surge](const char* pn, bool print = false) {
REQUIRE( surge->loadPatchByPath( pn, -1, "Test" ) );
auto f60_0 = frequencyForNote( surge, 60, 5, 0 );
auto f60_1 = frequencyForNote( surge, 60, 5, 1 );

auto f60_avg = 0.5 * ( f60_0 + f60_1 );

auto f72_0 = frequencyForNote( surge, 72, 5, 0 );
auto f72_1 = frequencyForNote( surge, 72, 5, 1 );
auto f72_avg = 0.5 * ( f72_0 + f72_1 );

// In absolute mode, the average frequencies should double, but the channels should have constant difference
REQUIRE( f72_avg / f60_avg == Approx( 2 ).margin( 0.01 ) );
REQUIRE( ( f72_0 - f72_1 ) / ( f60_0 - f60_1 ) == Approx( 1 ).margin( 0.01 ) );
if( print )
{
std::cout << "F60 " << f60_avg << " " << f60_0 << " " << f60_1 << " " << f60_0 - f60_1 << std::endl;
std::cout << "F72 " << f72_avg << " " << f72_0 << " " << f72_1 << " " << f60_0 - f60_1 << std::endl;
}
};

SECTION( "Wavetable Oscillator" )
{
assertRelative("test-data/patches/Wavetable-Sin-Uni2-Relative.fxp");
assertAbsolute("test-data/patches/Wavetable-Sin-Uni2-Absolute.fxp");
}

SECTION( "Window Oscillator" )
{
assertRelative("test-data/patches/Window-Sin-Uni2-Relative.fxp");
assertAbsolute("test-data/patches/Window-Sin-Uni2-Absolute.fxp");
}

SECTION( "Classic Oscillator" )
{
assertRelative("test-data/patches/Classic-Uni2-Relative.fxp");
assertAbsolute("test-data/patches/Classic-Uni2-Absolute.fxp");
}

SECTION( "SH Oscillator" )
{
assertRelative("test-data/patches/SH-Uni2-Relative.fxp");
assertAbsolute("test-data/patches/SH-Uni2-Absolute.fxp");
}

}

TEST_CASE( "All Patches have Bounded Output", "[dsp]" )
{
SurgeSynthesizer* surge = Surge::Headless::createSurge(44100);
Expand Down
Binary file added test-data/patches/Classic-Uni2-Absolute.fxp
Binary file not shown.
Binary file added test-data/patches/Classic-Uni2-Relative.fxp
Binary file not shown.
Binary file added test-data/patches/SH-Uni2-Absolute.fxp
Binary file not shown.
Binary file added test-data/patches/SH-Uni2-Relative.fxp
Binary file not shown.
Binary file added test-data/patches/Wavetable-Sin-Uni2-Absolute.fxp
Binary file not shown.
Binary file added test-data/patches/Wavetable-Sin-Uni2-Relative.fxp
Binary file not shown.
Binary file added test-data/patches/Window-Sin-Uni2-Absolute.fxp
Binary file not shown.
Binary file added test-data/patches/Window-Sin-Uni2-Relative.fxp
Binary file not shown.

0 comments on commit d24c1b7

Please sign in to comment.