diff --git a/src/common/SurgeSynthesizer.h b/src/common/SurgeSynthesizer.h index f5a91393c37..85b5be77b5d 100644 --- a/src/common/SurgeSynthesizer.h +++ b/src/common/SurgeSynthesizer.h @@ -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); diff --git a/src/common/SurgeSynthesizerIO.cpp b/src/common/SurgeSynthesizerIO.cpp index 443078925d4..5bd37c176e9 100644 --- a/src/common/SurgeSynthesizerIO.cpp +++ b/src/common/SurgeSynthesizerIO.cpp @@ -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); @@ -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); @@ -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) diff --git a/src/common/dsp/WindowOscillator.cpp b/src/common/dsp/WindowOscillator.cpp index 2f1a24c63ae..d712929496c 100644 --- a/src/common/dsp/WindowOscillator.cpp +++ b/src/common/dsp/WindowOscillator.cpp @@ -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]); diff --git a/src/headless/UnitTests.cpp b/src/headless/UnitTests.cpp index f5d0d585bd8..3be2d7e5224 100644 --- a/src/headless/UnitTests.cpp +++ b/src/headless/UnitTests.cpp @@ -137,16 +137,16 @@ std::shared_ptr 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 surge, int note ) +double frequencyForNote( std::shared_ptr 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); @@ -154,7 +154,7 @@ double frequencyForNote( std::shared_ptr surge, int note ) float *leftTrimmed = new float[nSTrim]; for( int i=0; i( 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); diff --git a/test-data/patches/Classic-Uni2-Absolute.fxp b/test-data/patches/Classic-Uni2-Absolute.fxp new file mode 100644 index 00000000000..1847fd563a2 Binary files /dev/null and b/test-data/patches/Classic-Uni2-Absolute.fxp differ diff --git a/test-data/patches/Classic-Uni2-Relative.fxp b/test-data/patches/Classic-Uni2-Relative.fxp new file mode 100644 index 00000000000..8c7a617569b Binary files /dev/null and b/test-data/patches/Classic-Uni2-Relative.fxp differ diff --git a/test-data/patches/SH-Uni2-Absolute.fxp b/test-data/patches/SH-Uni2-Absolute.fxp new file mode 100644 index 00000000000..db6fc6693a5 Binary files /dev/null and b/test-data/patches/SH-Uni2-Absolute.fxp differ diff --git a/test-data/patches/SH-Uni2-Relative.fxp b/test-data/patches/SH-Uni2-Relative.fxp new file mode 100644 index 00000000000..2eeb83b3257 Binary files /dev/null and b/test-data/patches/SH-Uni2-Relative.fxp differ diff --git a/test-data/patches/Wavetable-Sin-Uni2-Absolute.fxp b/test-data/patches/Wavetable-Sin-Uni2-Absolute.fxp new file mode 100644 index 00000000000..93b3d5dab6b Binary files /dev/null and b/test-data/patches/Wavetable-Sin-Uni2-Absolute.fxp differ diff --git a/test-data/patches/Wavetable-Sin-Uni2-Relative.fxp b/test-data/patches/Wavetable-Sin-Uni2-Relative.fxp new file mode 100644 index 00000000000..b16f0d36304 Binary files /dev/null and b/test-data/patches/Wavetable-Sin-Uni2-Relative.fxp differ diff --git a/test-data/patches/Window-Sin-Uni2-Absolute.fxp b/test-data/patches/Window-Sin-Uni2-Absolute.fxp new file mode 100644 index 00000000000..3906843ec06 Binary files /dev/null and b/test-data/patches/Window-Sin-Uni2-Absolute.fxp differ diff --git a/test-data/patches/Window-Sin-Uni2-Relative.fxp b/test-data/patches/Window-Sin-Uni2-Relative.fxp new file mode 100644 index 00000000000..a5d14f8a586 Binary files /dev/null and b/test-data/patches/Window-Sin-Uni2-Relative.fxp differ