Merge pull request #813 from jpcima/sample-quality-setting

Sample quality controls in UI
This commit is contained in:
JP Cimalando 2021-04-10 21:19:24 +02:00 committed by GitHub
commit a49cf37930
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 422 additions and 31 deletions

View file

@ -3,7 +3,7 @@ version 1.0305
header_name {.h} header_name {.h}
code_name {.cxx} code_name {.cxx}
widget_class mainView {open widget_class mainView {open
xywh {353 221 800 475} type Double xywh {439 94 800 475} type Double
class LogicalGroup visible class LogicalGroup visible
} { } {
Fl_Box imageContainer_ { Fl_Box imageContainer_ {
@ -20,7 +20,7 @@ widget_class mainView {open
class RoundedGroup class RoundedGroup
} { } {
Fl_Box {} { Fl_Box {} {
comment {tag=kTagAbout} selected comment {tag=kTagAbout}
image {../resources/logo_text_shaded.png} xywh {35 9 120 60} image {../resources/logo_text_shaded.png} xywh {35 9 120 60}
class AboutButton class AboutButton
} }
@ -330,6 +330,32 @@ widget_class mainView {open
class ValueButton 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_ { Fl_Box piano_ {
xywh {5 400 790 70} labelsize 12 xywh {5 400 790 70} labelsize 12

View file

@ -26,6 +26,10 @@ EditRange EditRange::get(EditId id)
return { 440, 300, 500 }; return { 440, 300, 500 };
case EditId::StretchTuning: case EditId::StretchTuning:
return { 0, 0, 1 }; return { 0, 0, 1 };
case EditId::SampleQuality:
return { 1, 0, 10 };
case EditId::OscillatorQuality:
return { 1, 0, 3 };
case EditId::UIActivePanel: case EditId::UIActivePanel:
return { 0, 0, 255 }; return { 0, 0, 255 };
} }

View file

@ -18,6 +18,8 @@ enum class EditId : int {
ScalaRootKey, ScalaRootKey,
TuningFrequency, TuningFrequency,
StretchTuning, StretchTuning,
SampleQuality,
OscillatorQuality,
CanEditUserFilesDir, CanEditUserFilesDir,
UserFilesDir, UserFilesDir,
FallbackFilesDir, FallbackFilesDir,

View file

@ -85,6 +85,8 @@ struct Editor::Impl : EditorController::Receiver, IControlListener {
kTagSetScalaRootKey, kTagSetScalaRootKey,
kTagSetTuningFrequency, kTagSetTuningFrequency,
kTagSetStretchedTuning, kTagSetStretchedTuning,
kTagSetSampleQuality,
kTagSetOscillatorQuality,
kTagSetCCVolume, kTagSetCCVolume,
kTagSetCCPan, kTagSetCCPan,
kTagChooseUserFilesDir, kTagChooseUserFilesDir,
@ -112,6 +114,8 @@ struct Editor::Impl : EditorController::Receiver, IControlListener {
CTextLabel* tuningFrequencyLabel_ = nullptr; CTextLabel* tuningFrequencyLabel_ = nullptr;
CControl *stretchedTuningSlider_ = nullptr; CControl *stretchedTuningSlider_ = nullptr;
CTextLabel* stretchedTuningLabel_ = nullptr; CTextLabel* stretchedTuningLabel_ = nullptr;
SValueMenu *sampleQualitySlider_ = nullptr;
SValueMenu *oscillatorQualitySlider_ = nullptr;
CTextLabel* keyswitchLabel_ = nullptr; CTextLabel* keyswitchLabel_ = nullptr;
CTextLabel* keyswitchInactiveLabel_ = nullptr; CTextLabel* keyswitchInactiveLabel_ = nullptr;
CTextLabel* keyswitchBadge_ = nullptr; CTextLabel* keyswitchBadge_ = nullptr;
@ -390,6 +394,24 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
updateStretchedTuningLabel(value); updateStretchedTuningLabel(value);
} }
break; break;
case EditId::SampleQuality:
{
const int value = static_cast<int>(v.to_float());
if (CControl* slider = sampleQualitySlider_) {
slider->setValue(float(value));
slider->invalid();
}
}
break;
case EditId::OscillatorQuality:
{
const int value = static_cast<int>(v.to_float());
if (CControl* slider = oscillatorQualitySlider_) {
slider->setValue(float(value));
slider->invalid();
}
}
break;
case EditId::CanEditUserFilesDir: case EditId::CanEditUserFilesDir:
{ {
if (STitleContainer* group = userFilesGroup_) if (STitleContainer* group = userFilesGroup_)
@ -920,6 +942,8 @@ void Editor::Impl::createFrameContents()
adjustMinMaxToEditRange(tuningFrequencySlider_, EditId::TuningFrequency); adjustMinMaxToEditRange(tuningFrequencySlider_, EditId::TuningFrequency);
tuningFrequencySlider_->setWheelInc(0.1f / EditRange::get(EditId::TuningFrequency).extent()); tuningFrequencySlider_->setWheelInc(0.1f / EditRange::get(EditId::TuningFrequency).extent());
adjustMinMaxToEditRange(stretchedTuningSlider_, EditId::StretchTuning); 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}) for (int value : {1, 2, 4, 8, 16, 32, 64, 96, 128, 160, 192, 224, 256})
numVoicesSlider_->addEntry(std::to_string(value), value); numVoicesSlider_->addEntry(std::to_string(value), value);
@ -1000,6 +1024,37 @@ void Editor::Impl::createFrameContents()
menu->addEntry("Open SFZ folder", kTagOpenSfzFolder); menu->addEntry("Open SFZ folder", kTagOpenSfzFolder);
} }
if (SValueMenu *menu = sampleQualitySlider_) {
static const std::array<const char*, 11> 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<const char*, 4> 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_) { if (SPiano* piano = piano_) {
piano->onKeyPressed = [this](unsigned key, float vel) { piano->onKeyPressed = [this](unsigned key, float vel) {
uint8_t msg[3]; uint8_t msg[3];
@ -1765,6 +1820,14 @@ void Editor::Impl::valueChanged(CControl* ctl)
updateTuningFrequencyLabel(value); updateTuningFrequencyLabel(value);
break; break;
case kTagSetSampleQuality:
ctrl.uiSendValue(EditId::SampleQuality, value);
break;
case kTagSetOscillatorQuality:
ctrl.uiSendValue(EditId::OscillatorQuality, value);
break;
case kTagSetStretchedTuning: case kTagSetStretchedTuning:
ctrl.uiSendValue(EditId::StretchTuning, value); ctrl.uiSendValue(EditId::StretchTuning, value);
updateStretchedTuningLabel(value); updateStretchedTuningLabel(value);

View file

@ -169,6 +169,18 @@ view__63->addView(view__64);
auto* const view__65 = createValueButton(CRect(20, 60, 120, 85), kTagChooseUserFilesDir, "DefaultPath", kCenterText, 12); auto* const view__65 = createValueButton(CRect(20, 60, 120, 85), kTagChooseUserFilesDir, "DefaultPath", kCenterText, 12);
userFilesDirButton_ = view__65; userFilesDirButton_ = view__65;
view__63->addView(view__65); view__63->addView(view__65);
auto* const view__66 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 12); auto* const view__66 = createTitleGroup(CRect(525, 26, 720, 126), -1, "Quality", kCenterText, 12);
piano_ = view__66; view__46->addView(view__66);
view__0->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);

View file

@ -236,6 +236,12 @@ connect_port(LV2_Handle instance,
case SFIZZ_STRETCH_TUNING: case SFIZZ_STRETCH_TUNING:
self->stretch_tuning_port = (const float *)data; self->stretch_tuning_port = (const float *)data;
break; 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: case SFIZZ_ACTIVE_VOICES:
self->active_voices_port = (float *)data; self->active_voices_port = (float *)data;
break; break;
@ -976,6 +982,8 @@ run(LV2_Handle instance, uint32_t sample_count)
sfizz_set_volume(self->synth, *(self->volume_port)); sfizz_set_volume(self->synth, *(self->volume_port));
sfizz_set_scala_root_key(self->synth, *(self->scala_root_key_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_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_stretch_tuning(self);
sfizz_lv2_check_preload_size(self); sfizz_lv2_check_preload_size(self);
sfizz_lv2_check_oversampling(self); sfizz_lv2_check_oversampling(self);

View file

@ -315,8 +315,30 @@ midnam:update a lv2:Feature .
lv2:maximum 1.0 ; lv2:maximum 1.0 ;
units:unit units:coef units:unit units:coef
] , [ ] , [
a lv2:OutputPort, lv2:ControlPort ; a lv2:InputPort, lv2:ControlPort ;
lv2:index 13 ; 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:symbol "active_voices" ;
lv2:name "Active voices", lv2:name "Active voices",
"Voix utilisées"@fr ; "Voix utilisées"@fr ;
@ -327,7 +349,7 @@ midnam:update a lv2:Feature .
lv2:maximum 256 ; lv2:maximum 256 ;
] , [ ] , [
a lv2:OutputPort, lv2:ControlPort ; a lv2:OutputPort, lv2:ControlPort ;
lv2:index 14 ; lv2:index 16 ;
lv2:symbol "num_curves" ; lv2:symbol "num_curves" ;
lv2:name "Number of curves", lv2:name "Number of curves",
"Nombre de courbes"@fr ; "Nombre de courbes"@fr ;
@ -338,7 +360,7 @@ midnam:update a lv2:Feature .
lv2:maximum 65535 ; lv2:maximum 65535 ;
] , [ ] , [
a lv2:OutputPort, lv2:ControlPort ; a lv2:OutputPort, lv2:ControlPort ;
lv2:index 15 ; lv2:index 17 ;
lv2:symbol "num_masters" ; lv2:symbol "num_masters" ;
lv2:name "Number of masters", lv2:name "Number of masters",
"Nombre de maîtres"@fr ; "Nombre de maîtres"@fr ;
@ -349,7 +371,7 @@ midnam:update a lv2:Feature .
lv2:maximum 65535 ; lv2:maximum 65535 ;
] , [ ] , [
a lv2:OutputPort, lv2:ControlPort ; a lv2:OutputPort, lv2:ControlPort ;
lv2:index 16 ; lv2:index 18 ;
lv2:symbol "num_groups" ; lv2:symbol "num_groups" ;
lv2:name "Number of groups", lv2:name "Number of groups",
"Nombre de groupes"@fr ; "Nombre de groupes"@fr ;
@ -360,7 +382,7 @@ midnam:update a lv2:Feature .
lv2:maximum 65535 ; lv2:maximum 65535 ;
] , [ ] , [
a lv2:OutputPort, lv2:ControlPort ; a lv2:OutputPort, lv2:ControlPort ;
lv2:index 17 ; lv2:index 19 ;
lv2:symbol "num_regions" ; lv2:symbol "num_regions" ;
lv2:name "Number of regions", lv2:name "Number of regions",
"Nombre de régions"@fr ; "Nombre de régions"@fr ;
@ -371,7 +393,7 @@ midnam:update a lv2:Feature .
lv2:maximum 65535 ; lv2:maximum 65535 ;
] , [ ] , [
a lv2:OutputPort, lv2:ControlPort ; a lv2:OutputPort, lv2:ControlPort ;
lv2:index 18 ; lv2:index 20 ;
lv2:symbol "num_samples" ; lv2:symbol "num_samples" ;
lv2:name "Number of samples", lv2:name "Number of samples",
"Nombre d'échantillons"@fr ; "Nombre d'échantillons"@fr ;

View file

@ -60,12 +60,14 @@ enum
SFIZZ_SCALA_ROOT_KEY = 10, SFIZZ_SCALA_ROOT_KEY = 10,
SFIZZ_TUNING_FREQUENCY = 11, SFIZZ_TUNING_FREQUENCY = 11,
SFIZZ_STRETCH_TUNING = 12, SFIZZ_STRETCH_TUNING = 12,
SFIZZ_ACTIVE_VOICES = 13, SFIZZ_SAMPLE_QUALITY = 13,
SFIZZ_NUM_CURVES = 14, SFIZZ_OSCILLATOR_QUALITY = 14,
SFIZZ_NUM_MASTERS = 15, SFIZZ_ACTIVE_VOICES = 15,
SFIZZ_NUM_GROUPS = 16, SFIZZ_NUM_CURVES = 16,
SFIZZ_NUM_REGIONS = 17, SFIZZ_NUM_MASTERS = 17,
SFIZZ_NUM_SAMPLES = 18, SFIZZ_NUM_GROUPS = 18,
SFIZZ_NUM_REGIONS = 19,
SFIZZ_NUM_SAMPLES = 20,
}; };
// For use with instance-access // For use with instance-access

View file

@ -42,6 +42,8 @@ struct sfizz_plugin_t
const float *scala_root_key_port {}; const float *scala_root_key_port {};
const float *tuning_frequency_port {}; const float *tuning_frequency_port {};
const float *stretch_tuning_port {}; const float *stretch_tuning_port {};
const float *sample_quality_port {};
const float *oscillator_quality_port {};
float *active_voices_port {}; float *active_voices_port {};
float *num_curves_port {}; float *num_curves_port {};
float *num_masters_port {}; float *num_masters_port {};

View file

@ -324,6 +324,12 @@ port_event(LV2UI_Handle ui,
case SFIZZ_STRETCH_TUNING: case SFIZZ_STRETCH_TUNING:
self->uiReceiveValue(EditId::StretchTuning, v); self->uiReceiveValue(EditId::StretchTuning, v);
break; 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: case SFIZZ_ACTIVE_VOICES:
self->uiReceiveValue(EditId::UINumActiveVoices, v); self->uiReceiveValue(EditId::UINumActiveVoices, v);
break; break;
@ -582,6 +588,12 @@ void sfizz_ui_t::uiSendValue(EditId id, const EditValue& v)
case EditId::StretchTuning: case EditId::StretchTuning:
sendFloat(SFIZZ_STRETCH_TUNING, v.to_float()); sendFloat(SFIZZ_STRETCH_TUNING, v.to_float());
break; 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: case EditId::SfzFile:
sendPath(sfizz_sfz_file_uri, v.to_string()); sendPath(sfizz_sfz_file_uri, v.to_string());
break; break;
@ -635,6 +647,12 @@ void sfizz_ui_t::uiTouch(EditId id, bool t)
case EditId::StretchTuning: case EditId::StretchTuning:
touch->touch(touch->handle, SFIZZ_STRETCH_TUNING, t); touch->touch(touch->handle, SFIZZ_STRETCH_TUNING, t);
break; 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: default:
break; break;
} }

View file

@ -60,6 +60,14 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context)
SfizzRange::getForParameter(kPidStretchedTuning).createParameter( SfizzRange::getForParameter(kPidStretchedTuning).createParameter(
Steinberg::String("Stretched tuning"), pid++, nullptr, Steinberg::String("Stretched tuning"), pid++, nullptr,
0, Vst::ParameterInfo::kCanAutomate, Vst::kRootUnitId)); 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 // MIDI special controllers
parameters.addParameter(Steinberg::String("Aftertouch"), nullptr, 0, 0.5, 0, pid++, Vst::kRootUnitId); 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); shortTitle.printf("CC%u", i);
parameters.addParameter( parameters.addParameter(
SfizzRange::getForParameter(kPidStretchedTuning).createParameter( SfizzRange::getForParameter(kPidCC0 + i).createParameter(
title, pid++, nullptr, 0, Vst::ParameterInfo::kCanAutomate, title, pid++, nullptr, 0, Vst::ParameterInfo::kCanAutomate,
Vst::kRootUnitId, shortTitle)); Vst::kRootUnitId, shortTitle));
} }
@ -174,6 +182,8 @@ tresult PLUGIN_API SfizzVstControllerNoUi::setComponentState(IBStream* stream)
setParam(kPidScalaRootKey, s.scalaRootKey); setParam(kPidScalaRootKey, s.scalaRootKey);
setParam(kPidTuningFrequency, s.tuningFrequency); setParam(kPidTuningFrequency, s.tuningFrequency);
setParam(kPidStretchedTuning, s.stretchedTuning); 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))); uint32 ccLimit = uint32(std::min(s.controllers.size(), size_t(sfz::config::numCCs)));
for (uint32 cc = 0; cc < ccLimit; ++cc) { for (uint32 cc = 0; cc < ccLimit; ++cc) {

View file

@ -279,6 +279,12 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message)
case kPidStretchedTuning: case kPidStretchedTuning:
uiReceiveValue(EditId::StretchTuning, range.denormalize(value)); uiReceiveValue(EditId::StretchTuning, range.denormalize(value));
break; break;
case kPidSampleQuality:
uiReceiveValue(EditId::SampleQuality, range.denormalize(value));
break;
case kPidOscillatorQuality:
uiReceiveValue(EditId::OscillatorQuality, range.denormalize(value));
break;
default: default:
if (id >= kPidCC0 && id <= kPidCCLast) { if (id >= kPidCC0 && id <= kPidCCLast) {
int cc = int(id - kPidCC0); int cc = int(id - kPidCC0);
@ -373,6 +379,12 @@ void SfizzVstEditor::uiSendValue(EditId id, const EditValue& v)
case EditId::StretchTuning: case EditId::StretchTuning:
normalizeAndSet(kPidStretchedTuning, v.to_float()); normalizeAndSet(kPidStretchedTuning, v.to_float());
break; break;
case EditId::SampleQuality:
normalizeAndSet(kPidSampleQuality, v.to_float());
break;
case EditId::OscillatorQuality:
normalizeAndSet(kPidOscillatorQuality, v.to_float());
break;
case EditId::UserFilesDir: case EditId::UserFilesDir:
SfizzPaths::setSfzConfigDefaultPath(fs::u8path(v.to_string())); SfizzPaths::setSfzConfigDefaultPath(fs::u8path(v.to_string()));
@ -479,6 +491,8 @@ Vst::ParamID SfizzVstEditor::parameterOfEditId(EditId id)
case EditId::ScalaRootKey: return kPidScalaRootKey; case EditId::ScalaRootKey: return kPidScalaRootKey;
case EditId::TuningFrequency: return kPidTuningFrequency; case EditId::TuningFrequency: return kPidTuningFrequency;
case EditId::StretchTuning: return kPidStretchedTuning; case EditId::StretchTuning: return kPidStretchedTuning;
case EditId::SampleQuality: return kPidSampleQuality;
case EditId::OscillatorQuality: return kPidOscillatorQuality;
default: default:
if (editIdIsCC(id)) if (editIdIsCC(id))
return kPidCC0 + ccForEditId(id); return kPidCC0 + ccForEditId(id);

View file

@ -20,6 +20,8 @@ enum {
kPidScalaRootKey, kPidScalaRootKey,
kPidTuningFrequency, kPidTuningFrequency,
kPidStretchedTuning, kPidStretchedTuning,
kPidSampleQuality,
kPidOscillatorQuality,
kPidAftertouch, kPidAftertouch,
kPidPitchBend, kPidPitchBend,
kPidCC0, kPidCC0,
@ -68,6 +70,10 @@ struct SfizzRange {
return {440.0, 300.0, 500.0}; return {440.0, 300.0, 500.0};
case kPidStretchedTuning: case kPidStretchedTuning:
return {0.0, 0.0, 1.0}; 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: case kPidAftertouch:
return {0.0, 0.0, 1.0}; return {0.0, 0.0, 1.0};
case kPidPitchBend: case kPidPitchBend:

View file

@ -287,6 +287,8 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data)
synth.loadStretchTuningByRatio(_state.stretchedTuning); synth.loadStretchTuningByRatio(_state.stretchedTuning);
_currentStretchedTuning = _state.stretchedTuning; _currentStretchedTuning = _state.stretchedTuning;
} }
synth.setSampleQuality(sfz::Sfizz::ProcessLive, _state.sampleQuality);
synth.setOscillatorQuality(sfz::Sfizz::ProcessLive, _state.oscillatorQuality);
synth.renderBlock(outputs, numFrames, numChannels); synth.renderBlock(outputs, numFrames, numChannels);
@ -380,6 +382,12 @@ void SfizzVstProcessor::playOrderedParameter(int32 sampleOffset, Vst::ParamID id
case kPidStretchedTuning: case kPidStretchedTuning:
_state.stretchedTuning = range.denormalize(value); _state.stretchedTuning = range.denormalize(value);
break; break;
case kPidSampleQuality:
_state.sampleQuality = static_cast<int32>(range.denormalize(value));
break;
case kPidOscillatorQuality:
_state.oscillatorQuality = static_cast<int32>(range.denormalize(value));
break;
case kPidAftertouch: case kPidAftertouch:
synth.aftertouch(sampleOffset, fastRound(value * 127.0)); synth.aftertouch(sampleOffset, fastRound(value * 127.0));
break; break;

View file

@ -17,6 +17,9 @@ tresult SfizzVstState::load(IBStream* state)
if (!s.readInt64u(version)) if (!s.readInt64u(version))
return kResultFalse; return kResultFalse;
if (version > currentStateVersion)
return kResultFalse;
if (const char* str = s.readStr8()) if (const char* str = s.readStr8())
sfzFile = str; sfzFile = str;
else else
@ -58,6 +61,18 @@ tresult SfizzVstState::load(IBStream* state)
stretchedTuning = defaults.stretchedTuning; 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(); controllers.clear();
if (version >= 2) { if (version >= 2) {
uint32 count; uint32 count;
@ -77,9 +92,6 @@ tresult SfizzVstState::load(IBStream* state)
controllers.shrink_to_fit(); controllers.shrink_to_fit();
} }
if (version > 2)
return kResultFalse;
return kResultTrue; return kResultTrue;
} }
@ -117,6 +129,12 @@ tresult SfizzVstState::store(IBStream* state) const
if (!s.writeFloat(stretchedTuning)) if (!s.writeFloat(stretchedTuning))
return kResultFalse; return kResultFalse;
if (!s.writeInt32(sampleQuality))
return kResultFalse;
if (!s.writeInt32(oscillatorQuality))
return kResultFalse;
{ {
uint32 ccCount = 0; uint32 ccCount = 0;
uint32 ccLimit = uint32(std::min(controllers.size(), size_t(0x10000))); uint32 ccLimit = uint32(std::min(controllers.size(), size_t(0x10000)));

View file

@ -25,9 +25,11 @@ public:
int32 scalaRootKey = 60; int32 scalaRootKey = 60;
float tuningFrequency = 440.0; float tuningFrequency = 440.0;
float stretchedTuning = 0.0; float stretchedTuning = 0.0;
int32 sampleQuality = 1;
int32 oscillatorQuality = 1;
std::vector<absl::optional<float>> controllers; std::vector<absl::optional<float>> controllers;
static constexpr uint64 currentStateVersion = 2; static constexpr uint64 currentStateVersion = 3;
tresult load(IBStream* state); tresult load(IBStream* state);
tresult store(IBStream* state) const; tresult store(IBStream* state) const;

View file

@ -648,7 +648,7 @@ SFIZZ_EXPORTED_API bool sfizz_set_oversampling_factor(sfizz_synth_t* synth, sfiz
* @param synth The synth. * @param synth The synth.
* @param[in] mode The processing mode. * @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); 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 synth The synth.
* @param[in] mode The processing mode. * @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 * @par Thread-safety constraints
* - @b RT: the function must be invoked from the Real-time thread * - @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); 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. * @brief Set the global instrument volume.
* @since 0.2.0 * @since 0.2.0

View file

@ -290,7 +290,7 @@ public:
* *
* @param[in] mode The processing mode. * @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); int getSampleQuality(ProcessMode mode);
@ -305,13 +305,46 @@ public:
* @since 0.4.0 * @since 0.4.0
* *
* @param[in] mode The processing mode. * @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 * @par Thread-safety constraints
* - @b RT: the function must be invoked from the Real-time thread * - @b RT: the function must be invoked from the Real-time thread
*/ */
void setSampleQuality(ProcessMode mode, int quality); 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. * @brief Return the current value for the volume, in dB.
* @since 0.2.0 * @since 0.2.0

View file

@ -314,7 +314,8 @@ namespace Default
// Various defaut values // Various defaut values
// e.g. "additional" or multiple defautl 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 globalVolume { -7.35f };
constexpr float defaultEQFreq [numEQs] { 50.0f, 500.0f, 5000.0f }; constexpr float defaultEQFreq [numEQs] { 50.0f, 500.0f, 5000.0f };
} // namespace Default } // namespace Default

View file

@ -1645,9 +1645,9 @@ int Synth::getSampleQuality(ProcessMode mode)
void Synth::setSampleQuality(ProcessMode mode, int quality) void Synth::setSampleQuality(ProcessMode mode, int quality)
{ {
SFIZZ_CHECK(quality >= 1 && quality <= 10); SFIZZ_CHECK(quality >= 0 && quality <= 10);
Impl& impl = *impl_; Impl& impl = *impl_;
quality = clamp(quality, 1, 10); quality = clamp(quality, 0, 10);
switch (mode) { switch (mode) {
case ProcessLive: 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 float Synth::getVolume() const noexcept
{ {
Impl& impl = *impl_; Impl& impl = *impl_;

View file

@ -315,6 +315,21 @@ public:
* @param quality the quality setting * @param quality the quality setting
*/ */
void setSampleQuality(ProcessMode mode, int quality); 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. * @brief Get the current value for the volume, in dB.
* *

View file

@ -14,11 +14,19 @@ struct SynthConfig
bool freeWheeling { false }; bool freeWheeling { false };
int liveSampleQuality { Default::sampleQuality }; int liveSampleQuality { Default::sampleQuality };
int freeWheelingSampleQuality { Default::freewheelingQuality }; int freeWheelingSampleQuality { Default::freewheelingSampleQuality };
int liveOscillatorQuality { Default::oscillatorQuality };
int freeWheelingOscillatorQuality { Default::freewheelingOscillatorQuality };
int currentSampleQuality() const noexcept int currentSampleQuality() const noexcept
{ {
return freeWheeling ? freeWheelingSampleQuality : liveSampleQuality; return freeWheeling ? freeWheelingSampleQuality : liveSampleQuality;
} }
int currentOscillatorQuality() const noexcept
{
return freeWheeling ? freeWheelingOscillatorQuality : liveOscillatorQuality;
}
}; };
} }

View file

@ -161,6 +161,12 @@ struct Voice::Impl
* @return int * @return int
*/ */
int getCurrentSampleQuality() const noexcept; 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 * @brief Reset the loop information
* *
@ -531,6 +537,18 @@ int Voice::getCurrentSampleQuality() const noexcept
return impl.getCurrentSampleQuality(); 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 bool Voice::isFree() const noexcept
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
@ -1459,6 +1477,7 @@ void Voice::Impl::fillWithGenerator(AudioSpan<float> buffer) noexcept
const int oscillatorMode = region_->oscillatorMode; const int oscillatorMode = region_->oscillatorMode;
const int oscillatorMulti = region_->oscillatorMulti; const int oscillatorMulti = region_->oscillatorMulti;
const int quality = getCurrentOscillatorQuality();
if (oscillatorMode <= 0 && oscillatorMulti < 2) { if (oscillatorMode <= 0 && oscillatorMulti < 2) {
// single oscillator // single oscillator
@ -1467,6 +1486,7 @@ void Voice::Impl::fillWithGenerator(AudioSpan<float> buffer) noexcept
return; return;
WavetableOscillator& osc = waveOscillators_[0]; WavetableOscillator& osc = waveOscillators_[0];
osc.setQuality(quality);
fill(*detuneSpan, 1.0f); fill(*detuneSpan, 1.0f);
osc.processModulated(frequencies->data(), detuneSpan->data(), tempSpan->data(), buffer.getNumFrames()); osc.processModulated(frequencies->data(), detuneSpan->data(), tempSpan->data(), buffer.getNumFrames());
copy<float>(*tempSpan, leftSpan); copy<float>(*tempSpan, leftSpan);
@ -1483,6 +1503,7 @@ void Voice::Impl::fillWithGenerator(AudioSpan<float> buffer) noexcept
const float* detuneMod = resources_.modMatrix.getModulation(oscillatorDetuneTarget_); const float* detuneMod = resources_.modMatrix.getModulation(oscillatorDetuneTarget_);
for (unsigned u = 0, uSize = waveUnisonSize_; u < uSize; ++u) { for (unsigned u = 0, uSize = waveUnisonSize_; u < uSize; ++u) {
WavetableOscillator& osc = waveOscillators_[u]; WavetableOscillator& osc = waveOscillators_[u];
osc.setQuality(quality);
if (!detuneMod) if (!detuneMod)
fill(*detuneSpan, waveDetuneRatio_[u]); fill(*detuneSpan, waveDetuneRatio_[u]);
else { else {
@ -1512,6 +1533,8 @@ void Voice::Impl::fillWithGenerator(AudioSpan<float> buffer) noexcept
WavetableOscillator& oscCar = waveOscillators_[0]; WavetableOscillator& oscCar = waveOscillators_[0];
WavetableOscillator& oscMod = waveOscillators_[1]; WavetableOscillator& oscMod = waveOscillators_[1];
oscCar.setQuality(quality);
oscMod.setQuality(quality);
// compute the modulator // compute the modulator
auto modulatorSpan = resources_.bufferPool.getBuffer(numFrames); auto modulatorSpan = resources_.bufferPool.getBuffer(numFrames);

View file

@ -122,6 +122,13 @@ public:
*/ */
int getCurrentSampleQuality() const noexcept; 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. * @brief Register a note-off event; this may trigger a release.
* *

View file

@ -140,6 +140,16 @@ void sfz::Sfizz::setSampleQuality(ProcessMode mode, int quality)
synth->synth.setSampleQuality(static_cast<sfz::Synth::ProcessMode>(mode), quality); synth->synth.setSampleQuality(static_cast<sfz::Synth::ProcessMode>(mode), quality);
} }
int sfz::Sfizz::getOscillatorQuality(ProcessMode mode)
{
return synth->synth.getOscillatorQuality(static_cast<sfz::Synth::ProcessMode>(mode));
}
void sfz::Sfizz::setOscillatorQuality(ProcessMode mode, int quality)
{
synth->synth.setOscillatorQuality(static_cast<sfz::Synth::ProcessMode>(mode), quality);
}
float sfz::Sfizz::getVolume() const noexcept float sfz::Sfizz::getVolume() const noexcept
{ {
return synth->synth.getVolume(); return synth->synth.getVolume();

View file

@ -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<sfz::Synth::ProcessMode>(mode), quality); return synth->synth.setSampleQuality(static_cast<sfz::Synth::ProcessMode>(mode), quality);
} }
int sfizz_get_oscillator_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode)
{
return synth->synth.getOscillatorQuality(static_cast<sfz::Synth::ProcessMode>(mode));
}
void sfizz_set_oscillator_quality(sfizz_synth_t* synth, sfizz_process_mode_t mode, int quality)
{
return synth->synth.setOscillatorQuality(static_cast<sfz::Synth::ProcessMode>(mode), quality);
}
void sfizz_set_volume(sfizz_synth_t* synth, float volume) void sfizz_set_volume(sfizz_synth_t* synth, float volume)
{ {
synth->synth.setVolume(volume); synth->synth.setVolume(volume);

View file

@ -584,7 +584,7 @@ TEST_CASE("[Synth] sample quality")
synth.noteOn(0, 60, 100); synth.noteOn(0, 60, 100);
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == sfz::Default::freewheelingQuality); REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == sfz::Default::freewheelingSampleQuality);
synth.allSoundOff(); synth.allSoundOff();
synth.disableFreeWheeling(); synth.disableFreeWheeling();