diff --git a/plugins/editor/layout/main.fl b/plugins/editor/layout/main.fl index 91a03cf8..7cbdde11 100644 --- a/plugins/editor/layout/main.fl +++ b/plugins/editor/layout/main.fl @@ -3,7 +3,7 @@ version 1.0305 header_name {.h} code_name {.cxx} widget_class mainView {open - xywh {353 221 800 475} type Double + xywh {439 94 800 475} type Double class LogicalGroup visible } { Fl_Box imageContainer_ { @@ -20,7 +20,7 @@ widget_class mainView {open class RoundedGroup } { Fl_Box {} { - comment {tag=kTagAbout} selected + comment {tag=kTagAbout} image {../resources/logo_text_shaded.png} xywh {35 9 120 60} class AboutButton } @@ -330,6 +330,32 @@ widget_class mainView {open class ValueButton } } + Fl_Group {} { + label Quality open + xywh {530 135 195 100} box ROUNDED_BOX labelsize 12 align 17 + class TitleGroup + } { + Fl_Spinner sampleQualitySlider_ { + comment {tag=kTagSetSampleQuality} + xywh {545 195 80 25} labelsize 12 textsize 12 + class ValueMenu + } + Fl_Box {} { + label Sample + xywh {545 155 80 25} labelsize 12 + class ValueLabel + } + Fl_Box {} { + label Oscillator + xywh {630 155 80 25} labelsize 12 + class ValueLabel + } + Fl_Spinner oscillatorQualitySlider_ { + comment {tag=kTagSetOscillatorQuality} selected + xywh {630 195 80 25} labelsize 12 textsize 12 + class ValueMenu + } + } } Fl_Box piano_ { xywh {5 400 790 70} labelsize 12 diff --git a/plugins/editor/src/editor/EditIds.cpp b/plugins/editor/src/editor/EditIds.cpp index 1d148fb0..a88feb29 100644 --- a/plugins/editor/src/editor/EditIds.cpp +++ b/plugins/editor/src/editor/EditIds.cpp @@ -26,6 +26,10 @@ EditRange EditRange::get(EditId id) return { 440, 300, 500 }; case EditId::StretchTuning: return { 0, 0, 1 }; + case EditId::SampleQuality: + return { 1, 0, 10 }; + case EditId::OscillatorQuality: + return { 1, 0, 3 }; case EditId::UIActivePanel: return { 0, 0, 255 }; } diff --git a/plugins/editor/src/editor/EditIds.h b/plugins/editor/src/editor/EditIds.h index 59369023..3f55d31b 100644 --- a/plugins/editor/src/editor/EditIds.h +++ b/plugins/editor/src/editor/EditIds.h @@ -18,6 +18,8 @@ enum class EditId : int { ScalaRootKey, TuningFrequency, StretchTuning, + SampleQuality, + OscillatorQuality, CanEditUserFilesDir, UserFilesDir, FallbackFilesDir, diff --git a/plugins/editor/src/editor/Editor.cpp b/plugins/editor/src/editor/Editor.cpp index 85a67f06..939b979e 100644 --- a/plugins/editor/src/editor/Editor.cpp +++ b/plugins/editor/src/editor/Editor.cpp @@ -85,6 +85,8 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { kTagSetScalaRootKey, kTagSetTuningFrequency, kTagSetStretchedTuning, + kTagSetSampleQuality, + kTagSetOscillatorQuality, kTagSetCCVolume, kTagSetCCPan, kTagChooseUserFilesDir, @@ -112,6 +114,8 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { CTextLabel* tuningFrequencyLabel_ = nullptr; CControl *stretchedTuningSlider_ = nullptr; CTextLabel* stretchedTuningLabel_ = nullptr; + SValueMenu *sampleQualitySlider_ = nullptr; + SValueMenu *oscillatorQualitySlider_ = nullptr; CTextLabel* keyswitchLabel_ = nullptr; CTextLabel* keyswitchInactiveLabel_ = nullptr; CTextLabel* keyswitchBadge_ = nullptr; @@ -390,6 +394,24 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v) updateStretchedTuningLabel(value); } break; + case EditId::SampleQuality: + { + const int value = static_cast(v.to_float()); + if (CControl* slider = sampleQualitySlider_) { + slider->setValue(float(value)); + slider->invalid(); + } + } + break; + case EditId::OscillatorQuality: + { + const int value = static_cast(v.to_float()); + if (CControl* slider = oscillatorQualitySlider_) { + slider->setValue(float(value)); + slider->invalid(); + } + } + break; case EditId::CanEditUserFilesDir: { if (STitleContainer* group = userFilesGroup_) @@ -920,6 +942,8 @@ void Editor::Impl::createFrameContents() adjustMinMaxToEditRange(tuningFrequencySlider_, EditId::TuningFrequency); tuningFrequencySlider_->setWheelInc(0.1f / EditRange::get(EditId::TuningFrequency).extent()); adjustMinMaxToEditRange(stretchedTuningSlider_, EditId::StretchTuning); + adjustMinMaxToEditRange(sampleQualitySlider_, EditId::SampleQuality); + adjustMinMaxToEditRange(oscillatorQualitySlider_, EditId::OscillatorQuality); for (int value : {1, 2, 4, 8, 16, 32, 64, 96, 128, 160, 192, 224, 256}) numVoicesSlider_->addEntry(std::to_string(value), value); @@ -1000,6 +1024,37 @@ void Editor::Impl::createFrameContents() menu->addEntry("Open SFZ folder", kTagOpenSfzFolder); } + if (SValueMenu *menu = sampleQualitySlider_) { + static const std::array labels {{ + "Nearest", "Linear", "Polynomial", + "Sinc 8", "Sinc 12", "Sinc 16", "Sinc 24", + "Sinc 36", "Sinc 48", "Sinc 60", "Sinc 72", + }}; + for (size_t i = 0; i < labels.size(); ++i) + menu->addEntry(labels[i], float(i)); + menu->setValueToStringFunction2([](float value, std::string& result, CParamDisplay*) -> bool { + int index = int(value); + if (index < 0 || unsigned(index) >= labels.size()) + return false; + result = labels[unsigned(index)]; + return true; + }); + } + if (SValueMenu *menu = oscillatorQualitySlider_) { + static const std::array labels {{ + "Nearest", "Linear", "High", "Dual-High", + }}; + for (size_t i = 0; i < labels.size(); ++i) + menu->addEntry(labels[i], float(i)); + menu->setValueToStringFunction2([](float value, std::string& result, CParamDisplay*) -> bool { + int index = int(value); + if (index < 0 || unsigned(index) >= labels.size()) + return false; + result = labels[unsigned(index)]; + return true; + }); + } + if (SPiano* piano = piano_) { piano->onKeyPressed = [this](unsigned key, float vel) { uint8_t msg[3]; @@ -1765,6 +1820,14 @@ void Editor::Impl::valueChanged(CControl* ctl) updateTuningFrequencyLabel(value); break; + case kTagSetSampleQuality: + ctrl.uiSendValue(EditId::SampleQuality, value); + break; + + case kTagSetOscillatorQuality: + ctrl.uiSendValue(EditId::OscillatorQuality, value); + break; + case kTagSetStretchedTuning: ctrl.uiSendValue(EditId::StretchTuning, value); updateStretchedTuningLabel(value); diff --git a/plugins/editor/src/editor/layout/main.hpp b/plugins/editor/src/editor/layout/main.hpp index 5378023a..e7234625 100644 --- a/plugins/editor/src/editor/layout/main.hpp +++ b/plugins/editor/src/editor/layout/main.hpp @@ -169,6 +169,18 @@ view__63->addView(view__64); auto* const view__65 = createValueButton(CRect(20, 60, 120, 85), kTagChooseUserFilesDir, "DefaultPath", kCenterText, 12); userFilesDirButton_ = view__65; view__63->addView(view__65); -auto* const view__66 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 12); -piano_ = view__66; -view__0->addView(view__66); +auto* const view__66 = createTitleGroup(CRect(525, 26, 720, 126), -1, "Quality", kCenterText, 12); +view__46->addView(view__66); +auto* const view__67 = createValueMenu(CRect(15, 60, 95, 85), kTagSetSampleQuality, "", kCenterText, 12); +sampleQualitySlider_ = view__67; +view__66->addView(view__67); +auto* const view__68 = createValueLabel(CRect(15, 20, 95, 45), -1, "Sample", kCenterText, 12); +view__66->addView(view__68); +auto* const view__69 = createValueLabel(CRect(100, 20, 180, 45), -1, "Oscillator", kCenterText, 12); +view__66->addView(view__69); +auto* const view__70 = createValueMenu(CRect(100, 60, 180, 85), kTagSetOscillatorQuality, "", kCenterText, 12); +oscillatorQualitySlider_ = view__70; +view__66->addView(view__70); +auto* const view__71 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 12); +piano_ = view__71; +view__0->addView(view__71); diff --git a/plugins/lv2/sfizz.cpp b/plugins/lv2/sfizz.cpp index 587b9efb..e5f35024 100644 --- a/plugins/lv2/sfizz.cpp +++ b/plugins/lv2/sfizz.cpp @@ -236,6 +236,12 @@ connect_port(LV2_Handle instance, case SFIZZ_STRETCH_TUNING: self->stretch_tuning_port = (const float *)data; break; + case SFIZZ_SAMPLE_QUALITY: + self->sample_quality_port = (const float *)data; + break; + case SFIZZ_OSCILLATOR_QUALITY: + self->oscillator_quality_port = (const float *)data; + break; case SFIZZ_ACTIVE_VOICES: self->active_voices_port = (float *)data; break; @@ -976,6 +982,8 @@ run(LV2_Handle instance, uint32_t sample_count) sfizz_set_volume(self->synth, *(self->volume_port)); sfizz_set_scala_root_key(self->synth, *(self->scala_root_key_port)); sfizz_set_tuning_frequency(self->synth, *(self->tuning_frequency_port)); + sfizz_set_sample_quality(self->synth, SFIZZ_PROCESS_LIVE, (int)(*self->sample_quality_port)); + sfizz_set_oscillator_quality(self->synth, SFIZZ_PROCESS_LIVE, (int)(*self->oscillator_quality_port)); sfizz_lv2_check_stretch_tuning(self); sfizz_lv2_check_preload_size(self); sfizz_lv2_check_oversampling(self); diff --git a/plugins/lv2/sfizz.ttl.in b/plugins/lv2/sfizz.ttl.in index bd1d6e8a..040f4fd3 100644 --- a/plugins/lv2/sfizz.ttl.in +++ b/plugins/lv2/sfizz.ttl.in @@ -315,8 +315,30 @@ midnam:update a lv2:Feature . lv2:maximum 1.0 ; units:unit units:coef ] , [ - a lv2:OutputPort, lv2:ControlPort ; + a lv2:InputPort, lv2:ControlPort ; lv2:index 13 ; + lv2:symbol "sample_quality" ; + lv2:name "Sample quality", + "Qualité des échantillons"@fr , + "Qualità del campione"@it ; + pg:group <@LV2PLUGIN_URI@#tuning> ; + lv2:default 1.0 ; + lv2:minimum 0.0 ; + lv2:maximum 10.0 + ] , [ + a lv2:InputPort, lv2:ControlPort ; + lv2:index 14 ; + lv2:symbol "oscillator_quality" ; + lv2:name "Oscillator quality", + "Qualité des oscillateurs"@fr , + "Qualità dell'oscillatore"@it ; + pg:group <@LV2PLUGIN_URI@#tuning> ; + lv2:default 1.0 ; + lv2:minimum 0.0 ; + lv2:maximum 3.0 + ] , [ + a lv2:OutputPort, lv2:ControlPort ; + lv2:index 15 ; lv2:symbol "active_voices" ; lv2:name "Active voices", "Voix utilisées"@fr ; @@ -327,7 +349,7 @@ midnam:update a lv2:Feature . lv2:maximum 256 ; ] , [ a lv2:OutputPort, lv2:ControlPort ; - lv2:index 14 ; + lv2:index 16 ; lv2:symbol "num_curves" ; lv2:name "Number of curves", "Nombre de courbes"@fr ; @@ -338,7 +360,7 @@ midnam:update a lv2:Feature . lv2:maximum 65535 ; ] , [ a lv2:OutputPort, lv2:ControlPort ; - lv2:index 15 ; + lv2:index 17 ; lv2:symbol "num_masters" ; lv2:name "Number of masters", "Nombre de maîtres"@fr ; @@ -349,7 +371,7 @@ midnam:update a lv2:Feature . lv2:maximum 65535 ; ] , [ a lv2:OutputPort, lv2:ControlPort ; - lv2:index 16 ; + lv2:index 18 ; lv2:symbol "num_groups" ; lv2:name "Number of groups", "Nombre de groupes"@fr ; @@ -360,7 +382,7 @@ midnam:update a lv2:Feature . lv2:maximum 65535 ; ] , [ a lv2:OutputPort, lv2:ControlPort ; - lv2:index 17 ; + lv2:index 19 ; lv2:symbol "num_regions" ; lv2:name "Number of regions", "Nombre de régions"@fr ; @@ -371,7 +393,7 @@ midnam:update a lv2:Feature . lv2:maximum 65535 ; ] , [ a lv2:OutputPort, lv2:ControlPort ; - lv2:index 18 ; + lv2:index 20 ; lv2:symbol "num_samples" ; lv2:name "Number of samples", "Nombre d'échantillons"@fr ; diff --git a/plugins/lv2/sfizz_lv2.h b/plugins/lv2/sfizz_lv2.h index 678f0c02..51a53896 100644 --- a/plugins/lv2/sfizz_lv2.h +++ b/plugins/lv2/sfizz_lv2.h @@ -60,12 +60,14 @@ enum SFIZZ_SCALA_ROOT_KEY = 10, SFIZZ_TUNING_FREQUENCY = 11, SFIZZ_STRETCH_TUNING = 12, - SFIZZ_ACTIVE_VOICES = 13, - SFIZZ_NUM_CURVES = 14, - SFIZZ_NUM_MASTERS = 15, - SFIZZ_NUM_GROUPS = 16, - SFIZZ_NUM_REGIONS = 17, - SFIZZ_NUM_SAMPLES = 18, + SFIZZ_SAMPLE_QUALITY = 13, + SFIZZ_OSCILLATOR_QUALITY = 14, + SFIZZ_ACTIVE_VOICES = 15, + SFIZZ_NUM_CURVES = 16, + SFIZZ_NUM_MASTERS = 17, + SFIZZ_NUM_GROUPS = 18, + SFIZZ_NUM_REGIONS = 19, + SFIZZ_NUM_SAMPLES = 20, }; // For use with instance-access diff --git a/plugins/lv2/sfizz_lv2_plugin.h b/plugins/lv2/sfizz_lv2_plugin.h index 348e417d..2eed6d52 100644 --- a/plugins/lv2/sfizz_lv2_plugin.h +++ b/plugins/lv2/sfizz_lv2_plugin.h @@ -42,6 +42,8 @@ struct sfizz_plugin_t const float *scala_root_key_port {}; const float *tuning_frequency_port {}; const float *stretch_tuning_port {}; + const float *sample_quality_port {}; + const float *oscillator_quality_port {}; float *active_voices_port {}; float *num_curves_port {}; float *num_masters_port {}; diff --git a/plugins/lv2/sfizz_ui.cpp b/plugins/lv2/sfizz_ui.cpp index 1730fdf3..93a90813 100644 --- a/plugins/lv2/sfizz_ui.cpp +++ b/plugins/lv2/sfizz_ui.cpp @@ -324,6 +324,12 @@ port_event(LV2UI_Handle ui, case SFIZZ_STRETCH_TUNING: self->uiReceiveValue(EditId::StretchTuning, v); break; + case SFIZZ_SAMPLE_QUALITY: + self->uiReceiveValue(EditId::SampleQuality, v); + break; + case SFIZZ_OSCILLATOR_QUALITY: + self->uiReceiveValue(EditId::OscillatorQuality, v); + break; case SFIZZ_ACTIVE_VOICES: self->uiReceiveValue(EditId::UINumActiveVoices, v); break; @@ -582,6 +588,12 @@ void sfizz_ui_t::uiSendValue(EditId id, const EditValue& v) case EditId::StretchTuning: sendFloat(SFIZZ_STRETCH_TUNING, v.to_float()); break; + case EditId::SampleQuality: + sendFloat(SFIZZ_SAMPLE_QUALITY, v.to_float()); + break; + case EditId::OscillatorQuality: + sendFloat(SFIZZ_OSCILLATOR_QUALITY, v.to_float()); + break; case EditId::SfzFile: sendPath(sfizz_sfz_file_uri, v.to_string()); break; @@ -635,6 +647,12 @@ void sfizz_ui_t::uiTouch(EditId id, bool t) case EditId::StretchTuning: touch->touch(touch->handle, SFIZZ_STRETCH_TUNING, t); break; + case EditId::SampleQuality: + touch->touch(touch->handle, SFIZZ_SAMPLE_QUALITY, t); + break; + case EditId::OscillatorQuality: + touch->touch(touch->handle, SFIZZ_OSCILLATOR_QUALITY, t); + break; default: break; } diff --git a/plugins/vst/SfizzVstController.cpp b/plugins/vst/SfizzVstController.cpp index 159f6a39..85456466 100644 --- a/plugins/vst/SfizzVstController.cpp +++ b/plugins/vst/SfizzVstController.cpp @@ -60,6 +60,14 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) SfizzRange::getForParameter(kPidStretchedTuning).createParameter( Steinberg::String("Stretched tuning"), pid++, nullptr, 0, Vst::ParameterInfo::kCanAutomate, Vst::kRootUnitId)); + parameters.addParameter( + SfizzRange::getForParameter(kPidSampleQuality).createParameter( + Steinberg::String("Sample quality"), pid++, nullptr, + 0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId)); + parameters.addParameter( + SfizzRange::getForParameter(kPidOscillatorQuality).createParameter( + Steinberg::String("Oscillator quality"), pid++, nullptr, + 0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId)); // MIDI special controllers parameters.addParameter(Steinberg::String("Aftertouch"), nullptr, 0, 0.5, 0, pid++, Vst::kRootUnitId); @@ -73,7 +81,7 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) shortTitle.printf("CC%u", i); parameters.addParameter( - SfizzRange::getForParameter(kPidStretchedTuning).createParameter( + SfizzRange::getForParameter(kPidCC0 + i).createParameter( title, pid++, nullptr, 0, Vst::ParameterInfo::kCanAutomate, Vst::kRootUnitId, shortTitle)); } @@ -174,6 +182,8 @@ tresult PLUGIN_API SfizzVstControllerNoUi::setComponentState(IBStream* stream) setParam(kPidScalaRootKey, s.scalaRootKey); setParam(kPidTuningFrequency, s.tuningFrequency); setParam(kPidStretchedTuning, s.stretchedTuning); + setParam(kPidSampleQuality, s.sampleQuality); + setParam(kPidOscillatorQuality, s.oscillatorQuality); uint32 ccLimit = uint32(std::min(s.controllers.size(), size_t(sfz::config::numCCs))); for (uint32 cc = 0; cc < ccLimit; ++cc) { diff --git a/plugins/vst/SfizzVstEditor.cpp b/plugins/vst/SfizzVstEditor.cpp index 2bd4680e..b7bb01e7 100644 --- a/plugins/vst/SfizzVstEditor.cpp +++ b/plugins/vst/SfizzVstEditor.cpp @@ -279,6 +279,12 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message) case kPidStretchedTuning: uiReceiveValue(EditId::StretchTuning, range.denormalize(value)); break; + case kPidSampleQuality: + uiReceiveValue(EditId::SampleQuality, range.denormalize(value)); + break; + case kPidOscillatorQuality: + uiReceiveValue(EditId::OscillatorQuality, range.denormalize(value)); + break; default: if (id >= kPidCC0 && id <= kPidCCLast) { int cc = int(id - kPidCC0); @@ -373,6 +379,12 @@ void SfizzVstEditor::uiSendValue(EditId id, const EditValue& v) case EditId::StretchTuning: normalizeAndSet(kPidStretchedTuning, v.to_float()); break; + case EditId::SampleQuality: + normalizeAndSet(kPidSampleQuality, v.to_float()); + break; + case EditId::OscillatorQuality: + normalizeAndSet(kPidOscillatorQuality, v.to_float()); + break; case EditId::UserFilesDir: SfizzPaths::setSfzConfigDefaultPath(fs::u8path(v.to_string())); @@ -479,6 +491,8 @@ Vst::ParamID SfizzVstEditor::parameterOfEditId(EditId id) case EditId::ScalaRootKey: return kPidScalaRootKey; case EditId::TuningFrequency: return kPidTuningFrequency; case EditId::StretchTuning: return kPidStretchedTuning; + case EditId::SampleQuality: return kPidSampleQuality; + case EditId::OscillatorQuality: return kPidOscillatorQuality; default: if (editIdIsCC(id)) return kPidCC0 + ccForEditId(id); diff --git a/plugins/vst/SfizzVstParameters.h b/plugins/vst/SfizzVstParameters.h index 9574d463..ee60b33b 100644 --- a/plugins/vst/SfizzVstParameters.h +++ b/plugins/vst/SfizzVstParameters.h @@ -20,6 +20,8 @@ enum { kPidScalaRootKey, kPidTuningFrequency, kPidStretchedTuning, + kPidSampleQuality, + kPidOscillatorQuality, kPidAftertouch, kPidPitchBend, kPidCC0, @@ -68,6 +70,10 @@ struct SfizzRange { return {440.0, 300.0, 500.0}; case kPidStretchedTuning: return {0.0, 0.0, 1.0}; + case kPidSampleQuality: + return {1.0, 0.0, 10.0}; + case kPidOscillatorQuality: + return {1.0, 0.0, 3.0}; case kPidAftertouch: return {0.0, 0.0, 1.0}; case kPidPitchBend: diff --git a/plugins/vst/SfizzVstProcessor.cpp b/plugins/vst/SfizzVstProcessor.cpp index 14f2f704..331c76b8 100644 --- a/plugins/vst/SfizzVstProcessor.cpp +++ b/plugins/vst/SfizzVstProcessor.cpp @@ -287,6 +287,8 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data) synth.loadStretchTuningByRatio(_state.stretchedTuning); _currentStretchedTuning = _state.stretchedTuning; } + synth.setSampleQuality(sfz::Sfizz::ProcessLive, _state.sampleQuality); + synth.setOscillatorQuality(sfz::Sfizz::ProcessLive, _state.oscillatorQuality); synth.renderBlock(outputs, numFrames, numChannels); @@ -380,6 +382,12 @@ void SfizzVstProcessor::playOrderedParameter(int32 sampleOffset, Vst::ParamID id case kPidStretchedTuning: _state.stretchedTuning = range.denormalize(value); break; + case kPidSampleQuality: + _state.sampleQuality = static_cast(range.denormalize(value)); + break; + case kPidOscillatorQuality: + _state.oscillatorQuality = static_cast(range.denormalize(value)); + break; case kPidAftertouch: synth.aftertouch(sampleOffset, fastRound(value * 127.0)); break; diff --git a/plugins/vst/SfizzVstState.cpp b/plugins/vst/SfizzVstState.cpp index a4e5ab68..1783f817 100644 --- a/plugins/vst/SfizzVstState.cpp +++ b/plugins/vst/SfizzVstState.cpp @@ -17,6 +17,9 @@ tresult SfizzVstState::load(IBStream* state) if (!s.readInt64u(version)) return kResultFalse; + if (version > currentStateVersion) + return kResultFalse; + if (const char* str = s.readStr8()) sfzFile = str; else @@ -58,6 +61,18 @@ tresult SfizzVstState::load(IBStream* state) stretchedTuning = defaults.stretchedTuning; } + if (version >= 3) { + if (!s.readInt32(sampleQuality)) + return kResultFalse; + + if (!s.readInt32(oscillatorQuality)) + return kResultFalse; + } + else { + sampleQuality = defaults.sampleQuality; + oscillatorQuality = defaults.oscillatorQuality; + } + controllers.clear(); if (version >= 2) { uint32 count; @@ -77,9 +92,6 @@ tresult SfizzVstState::load(IBStream* state) controllers.shrink_to_fit(); } - if (version > 2) - return kResultFalse; - return kResultTrue; } @@ -117,6 +129,12 @@ tresult SfizzVstState::store(IBStream* state) const if (!s.writeFloat(stretchedTuning)) return kResultFalse; + if (!s.writeInt32(sampleQuality)) + return kResultFalse; + + if (!s.writeInt32(oscillatorQuality)) + return kResultFalse; + { uint32 ccCount = 0; uint32 ccLimit = uint32(std::min(controllers.size(), size_t(0x10000))); diff --git a/plugins/vst/SfizzVstState.h b/plugins/vst/SfizzVstState.h index d1c08795..c31bd80d 100644 --- a/plugins/vst/SfizzVstState.h +++ b/plugins/vst/SfizzVstState.h @@ -25,9 +25,11 @@ public: int32 scalaRootKey = 60; float tuningFrequency = 440.0; float stretchedTuning = 0.0; + int32 sampleQuality = 1; + int32 oscillatorQuality = 1; std::vector> controllers; - static constexpr uint64 currentStateVersion = 2; + static constexpr uint64 currentStateVersion = 3; tresult load(IBStream* state); tresult store(IBStream* state) const; diff --git a/src/sfizz.h b/src/sfizz.h index b36989e6..f835bfc7 100644 --- a/src/sfizz.h +++ b/src/sfizz.h @@ -648,7 +648,7 @@ SFIZZ_EXPORTED_API bool sfizz_set_oversampling_factor(sfizz_synth_t* synth, sfiz * @param synth The synth. * @param[in] mode The processing mode. * - * @return The sample quality for the given mode, in the range 1 to 10. + * @return The sample quality for the given mode, in the range 0 to 10. */ SFIZZ_EXPORTED_API int sfizz_get_sample_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode); @@ -663,13 +663,47 @@ SFIZZ_EXPORTED_API int sfizz_get_sample_quality(sfizz_synth_t* synth, sfizz_proc * * @param synth The synth. * @param[in] mode The processing mode. - * @param[in] quality The desired sample quality, in the range 1 to 10. + * @param[in] quality The desired sample quality, in the range 0 to 10. * * @par Thread-safety constraints * - @b RT: the function must be invoked from the Real-time thread */ SFIZZ_EXPORTED_API void sfizz_set_sample_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode, int quality); +/** + * @brief Get the default oscillator quality. + * + * This is the quality setting which the engine uses when the instrument + * does not use the opcode `oscillator_quality`. The engine uses distinct + * default quality settings for live mode and freewheeling mode, + * which both can be accessed by the means of this function. + * @since 0.6.0 + * + * @param synth The synth. + * @param[in] mode The processing mode. + * + * @return The oscillator quality for the given mode, in the range 0 to 10. + */ +SFIZZ_EXPORTED_API int sfizz_get_oscillator_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode); + +/** + * @brief Set the default oscillator quality. + * + * This is the quality setting which the engine uses when the instrument + * does not use the opcode `oscillator_quality`. The engine uses distinct + * default quality settings for live mode and freewheeling mode, + * which both can be accessed by the means of this function. + * @since 0.6.0 + * + * @param synth The synth. + * @param[in] mode The processing mode. + * @param[in] quality The desired oscillator quality, in the range 0 to 10. + * + * @par Thread-safety constraints + * - @b RT: the function must be invoked from the Real-time thread + */ +SFIZZ_EXPORTED_API void sfizz_set_oscillator_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode, int quality); + /** * @brief Set the global instrument volume. * @since 0.2.0 diff --git a/src/sfizz.hpp b/src/sfizz.hpp index b9f2063f..88b1abf2 100644 --- a/src/sfizz.hpp +++ b/src/sfizz.hpp @@ -290,7 +290,7 @@ public: * * @param[in] mode The processing mode. * - * @return The sample quality for the given mode, in the range 1 to 10. + * @return The sample quality for the given mode, in the range 0 to 10. */ int getSampleQuality(ProcessMode mode); @@ -305,13 +305,46 @@ public: * @since 0.4.0 * * @param[in] mode The processing mode. - * @param[in] quality The desired sample quality, in the range 1 to 10. + * @param[in] quality The desired sample quality, in the range 0 to 10. * * @par Thread-safety constraints * - @b RT: the function must be invoked from the Real-time thread */ void setSampleQuality(ProcessMode mode, int quality); + /** + * @brief Get the default oscillator quality. + * + * This is the quality setting which the engine uses when the instrument + * does not use the opcode `oscillator_quality`. The engine uses distinct + * default quality settings for live mode and freewheeling mode, + * which both can be accessed by the means of this function. + * @since 0.6.0 + * + * @param[in] mode The processing mode. + * + * @return The oscillator quality for the given mode, in the range 0 to 3. + */ + int getOscillatorQuality(ProcessMode mode); + + /** + * @brief Set the default oscillator quality. + * + * This is the quality setting which the engine uses when the instrument + * does not use the opcode `oscillator_quality`. The engine uses distinct + * default quality settings for live mode and freewheeling mode, + * which both can be accessed by the means of this function. + * + * @since 0.6.0 + * + * @param[in] mode The processing mode. + * @param[in] quality The desired oscillator quality, in the range 0 to 3. + * + * @par Thread-safety constraints + * - @b RT: the function must be invoked from the Real-time thread + */ + void setOscillatorQuality(ProcessMode mode, int quality); + /** * @brief Return the current value for the volume, in dB. * @since 0.2.0 diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 5ce7971b..4a541bf6 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -314,7 +314,8 @@ namespace Default // Various defaut values // e.g. "additional" or multiple defautl values - constexpr int freewheelingQuality { 10 }; + constexpr int freewheelingSampleQuality { 10 }; + constexpr int freewheelingOscillatorQuality { 3 }; constexpr float globalVolume { -7.35f }; constexpr float defaultEQFreq [numEQs] { 50.0f, 500.0f, 5000.0f }; } // namespace Default diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 9901de70..ee2edf9b 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -1645,9 +1645,9 @@ int Synth::getSampleQuality(ProcessMode mode) void Synth::setSampleQuality(ProcessMode mode, int quality) { - SFIZZ_CHECK(quality >= 1 && quality <= 10); + SFIZZ_CHECK(quality >= 0 && quality <= 10); Impl& impl = *impl_; - quality = clamp(quality, 1, 10); + quality = clamp(quality, 0, 10); switch (mode) { case ProcessLive: @@ -1662,6 +1662,39 @@ void Synth::setSampleQuality(ProcessMode mode, int quality) } } +int Synth::getOscillatorQuality(ProcessMode mode) +{ + Impl& impl = *impl_; + switch (mode) { + case ProcessLive: + return impl.resources_.synthConfig.liveOscillatorQuality; + case ProcessFreewheeling: + return impl.resources_.synthConfig.freeWheelingOscillatorQuality; + default: + SFIZZ_CHECK(false); + return 0; + } +} + +void Synth::setOscillatorQuality(ProcessMode mode, int quality) +{ + SFIZZ_CHECK(quality >= 0 && quality <= 3); + Impl& impl = *impl_; + quality = clamp(quality, 0, 3); + + switch (mode) { + case ProcessLive: + impl.resources_.synthConfig.liveOscillatorQuality = quality; + break; + case ProcessFreewheeling: + impl.resources_.synthConfig.freeWheelingOscillatorQuality = quality; + break; + default: + SFIZZ_CHECK(false); + break; + } +} + float Synth::getVolume() const noexcept { Impl& impl = *impl_; diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 6b806898..463e21ab 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -315,6 +315,21 @@ public: * @param quality the quality setting */ void setSampleQuality(ProcessMode mode, int quality); + /** + * @brief Get the default oscillator quality for the given mode. + * + * @param mode the processing mode + * + * @return the quality setting + */ + int getOscillatorQuality(ProcessMode mode); + /** + * @brief Set the default oscillator quality for the given mode. + * + * @param mode the processing mode + * @param quality the quality setting + */ + void setOscillatorQuality(ProcessMode mode, int quality); /** * @brief Get the current value for the volume, in dB. * diff --git a/src/sfizz/SynthConfig.h b/src/sfizz/SynthConfig.h index 8fdbd510..dbd9414b 100644 --- a/src/sfizz/SynthConfig.h +++ b/src/sfizz/SynthConfig.h @@ -14,11 +14,19 @@ struct SynthConfig bool freeWheeling { false }; int liveSampleQuality { Default::sampleQuality }; - int freeWheelingSampleQuality { Default::freewheelingQuality }; + int freeWheelingSampleQuality { Default::freewheelingSampleQuality }; + + int liveOscillatorQuality { Default::oscillatorQuality }; + int freeWheelingOscillatorQuality { Default::freewheelingOscillatorQuality }; int currentSampleQuality() const noexcept { return freeWheeling ? freeWheelingSampleQuality : liveSampleQuality; } + + int currentOscillatorQuality() const noexcept + { + return freeWheeling ? freeWheelingOscillatorQuality : liveOscillatorQuality; + } }; } diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 568688c2..7b414a93 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -161,6 +161,12 @@ struct Voice::Impl * @return int */ int getCurrentSampleQuality() const noexcept; + /** + * @brief Get the oscillator quality determined by the active region. + * + * @return int + */ + int getCurrentOscillatorQuality() const noexcept; /** * @brief Reset the loop information * @@ -531,6 +537,18 @@ int Voice::getCurrentSampleQuality() const noexcept return impl.getCurrentSampleQuality(); } +int Voice::Impl::getCurrentOscillatorQuality() const noexcept +{ + return (region_ && region_->oscillatorQuality) ? + *region_->oscillatorQuality : resources_.synthConfig.currentOscillatorQuality(); +} + +int Voice::getCurrentOscillatorQuality() const noexcept +{ + Impl& impl = *impl_; + return impl.getCurrentOscillatorQuality(); +} + bool Voice::isFree() const noexcept { Impl& impl = *impl_; @@ -1459,6 +1477,7 @@ void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept const int oscillatorMode = region_->oscillatorMode; const int oscillatorMulti = region_->oscillatorMulti; + const int quality = getCurrentOscillatorQuality(); if (oscillatorMode <= 0 && oscillatorMulti < 2) { // single oscillator @@ -1467,6 +1486,7 @@ void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept return; WavetableOscillator& osc = waveOscillators_[0]; + osc.setQuality(quality); fill(*detuneSpan, 1.0f); osc.processModulated(frequencies->data(), detuneSpan->data(), tempSpan->data(), buffer.getNumFrames()); copy(*tempSpan, leftSpan); @@ -1483,6 +1503,7 @@ void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept const float* detuneMod = resources_.modMatrix.getModulation(oscillatorDetuneTarget_); for (unsigned u = 0, uSize = waveUnisonSize_; u < uSize; ++u) { WavetableOscillator& osc = waveOscillators_[u]; + osc.setQuality(quality); if (!detuneMod) fill(*detuneSpan, waveDetuneRatio_[u]); else { @@ -1512,6 +1533,8 @@ void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept WavetableOscillator& oscCar = waveOscillators_[0]; WavetableOscillator& oscMod = waveOscillators_[1]; + oscCar.setQuality(quality); + oscMod.setQuality(quality); // compute the modulator auto modulatorSpan = resources_.bufferPool.getBuffer(numFrames); diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 58242552..9e7f3593 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -122,6 +122,13 @@ public: */ int getCurrentSampleQuality() const noexcept; + /** + * @brief Get the oscillator quality determined by the active region. + * + * @return int + */ + int getCurrentOscillatorQuality() const noexcept; + /** * @brief Register a note-off event; this may trigger a release. * diff --git a/src/sfizz/sfizz.cpp b/src/sfizz/sfizz.cpp index 51391abb..eac5dc6f 100644 --- a/src/sfizz/sfizz.cpp +++ b/src/sfizz/sfizz.cpp @@ -140,6 +140,16 @@ void sfz::Sfizz::setSampleQuality(ProcessMode mode, int quality) synth->synth.setSampleQuality(static_cast(mode), quality); } +int sfz::Sfizz::getOscillatorQuality(ProcessMode mode) +{ + return synth->synth.getOscillatorQuality(static_cast(mode)); +} + +void sfz::Sfizz::setOscillatorQuality(ProcessMode mode, int quality) +{ + synth->synth.setOscillatorQuality(static_cast(mode), quality); +} + float sfz::Sfizz::getVolume() const noexcept { return synth->synth.getVolume(); diff --git a/src/sfizz/sfizz_wrapper.cpp b/src/sfizz/sfizz_wrapper.cpp index 73284eb2..50afa403 100644 --- a/src/sfizz/sfizz_wrapper.cpp +++ b/src/sfizz/sfizz_wrapper.cpp @@ -217,6 +217,16 @@ void sfizz_set_sample_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode, i return synth->synth.setSampleQuality(static_cast(mode), quality); } +int sfizz_get_oscillator_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode) +{ + return synth->synth.getOscillatorQuality(static_cast(mode)); +} + +void sfizz_set_oscillator_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode, int quality) +{ + return synth->synth.setOscillatorQuality(static_cast(mode), quality); +} + void sfizz_set_volume(sfizz_synth_t* synth, float volume) { synth->synth.setVolume(volume); diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index bea0fcf5..e077c173 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -584,7 +584,7 @@ TEST_CASE("[Synth] sample quality") synth.noteOn(0, 60, 100); synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); - REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == sfz::Default::freewheelingQuality); + REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == sfz::Default::freewheelingSampleQuality); synth.allSoundOff(); synth.disableFreeWheeling();