diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index c13707ef..2956dae6 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -103,10 +103,14 @@ namespace config { // Wavetable constants; amplitude values are matched to reference static constexpr unsigned tableSize = 1024; static constexpr double tableRefSampleRate = 44100.0 * 1.1; // +10% aliasing permissivity - static constexpr double amplitudeSine = 0.625; - static constexpr double amplitudeTriangle = 0.625; - static constexpr double amplitudeSaw = 0.515; - static constexpr double amplitudeSquare = 0.515; + /** + Default wave amplitudes, adjusted for consistent RMS among all waves. + (except square curiously, but it's to match ARIA) + */ + static constexpr double amplitudeSine = 1.0; + static constexpr double amplitudeTriangle = 1.0; + static constexpr double amplitudeSaw = 0.8164965809277261; // sqrt(2)/sqrt(3) + static constexpr double amplitudeSquare = 0.8164965809277261; // should have been sqrt(2)? /** Background file loading */ diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index 1cfa48d1..c0a27c09 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -176,13 +176,13 @@ inline void applyGain1(T gain, absl::Span input, absl::Span output) * @param size */ template -inline void applyGain1(float gain, float* array, unsigned size) noexcept +inline void applyGain1(T gain, T* array, unsigned size) noexcept { applyGain1(gain, array, array, size); } template -inline void applyGain1(float gain, absl::Span array) noexcept +inline void applyGain1(T gain, absl::Span array) noexcept { applyGain1(gain, array.data(), array.data(), array.size()); } diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index dcbffc6f..0ce7d527 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -143,6 +143,16 @@ void sfz::Synth::buildRegion(const std::vector& regionOpcodes) int regionNumber = static_cast(regions.size()); auto lastRegion = absl::make_unique(regionNumber, resources.midiState, defaultPath); + // Create default connections + constexpr unsigned defaultSmoothness = 10; + lastRegion->getOrCreateConnection( + ModKey::createCC(7, 4, defaultSmoothness, 100, 0), + ModKey::createNXYZ(ModId::Amplitude, lastRegion->id)); + lastRegion->getOrCreateConnection( + ModKey::createCC(10, 1, defaultSmoothness, 100, 0), + ModKey::createNXYZ(ModId::Pan, lastRegion->id)); + + // auto parseOpcodes = [&](const std::vector& opcodes) { for (auto& opcode : opcodes) { const auto unknown = absl::c_find_if(unknownOpcodes, [&](absl::string_view sv) { return sv.compare(opcode.opcode) == 0; }); @@ -215,6 +225,10 @@ void sfz::Synth::clear() polyphonyGroups.emplace_back(); polyphonyGroups.back().setPolyphonyLimit(config::maxVoices); modificationTime = fs::file_time_type::min(); + + // set default controllers + cc(0, 7, 100); // volume + hdcc(0, 10, 0.5f); // pan } void sfz::Synth::handleMasterOpcodes(const std::vector& members) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 331ae748..d7517a3d 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -491,6 +491,10 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept (*modulationSpan)[i] += normalizePercents(mod[i]); } pan(*modulationSpan, leftBuffer, rightBuffer); + + // add +3dB to compensate for the 2 pan stages (-3dB each stage) + applyGain1(1.4125375446227544f, leftBuffer); + applyGain1(1.4125375446227544f, rightBuffer); } void sfz::Voice::filterStageMono(AudioSpan buffer) noexcept diff --git a/tests/ModulationsT.cpp b/tests/ModulationsT.cpp index 709ec36d..48203a2e 100644 --- a/tests/ModulationsT.cpp +++ b/tests/ModulationsT.cpp @@ -78,6 +78,32 @@ TEST_CASE("[Modulations] Display names") }); } +static std::string createReferenceGraph(std::vector lines) +{ + const char* defaultConnections[] = { + R"("Controller 7 {curve=4, smooth=10, value=100, step=0}" -> "Amplitude")", + R"("Controller 10 {curve=1, smooth=10, value=100, step=0}" -> "Pan")" + }; + + for (const char* line : defaultConnections) + lines.push_back(line); + + std::sort(lines.begin(), lines.end()); + + std::string graph; + graph.reserve(1024); + + graph += "digraph {\n"; + for (const std::string& line : lines) { + graph.push_back('\t'); + graph += line; + graph.push_back('\n'); + } + graph += "}\n"; + + return graph; +}; + TEST_CASE("[Modulations] Connection graph from SFZ") { sfz::Synth synth; @@ -89,12 +115,12 @@ pitch_oncc42=71 pitch_smoothcc42=32 pan_oncc36=14.5 pan_stepcc36=1.5 width_oncc425=29 )"); + const std::string graph = synth.getResources().modMatrix.toDotGraph(); - REQUIRE(graph == R"(digraph { - "Controller 20 {curve=3, smooth=0, value=59, step=0}" -> "Amplitude" - "Controller 36 {curve=0, smooth=0, value=14.5, step=1.5}" -> "Pan" - "Controller 42 {curve=0, smooth=32, value=71, step=0}" -> "Pitch" - "Controller 425 {curve=0, smooth=0, value=29, step=0}" -> "Width" -} -)"); + REQUIRE(graph == createReferenceGraph({ + R"("Controller 20 {curve=3, smooth=0, value=59, step=0}" -> "Amplitude")", + R"("Controller 42 {curve=0, smooth=32, value=71, step=0}" -> "Pitch")", + R"("Controller 36 {curve=0, smooth=0, value=14.5, step=1.5}" -> "Pan")", + R"("Controller 425 {curve=0, smooth=0, value=29, step=0}" -> "Width")", + })); }