Sample quality controls in UI

This commit is contained in:
Jean Pierre Cimalando 2021-04-10 17:48:13 +02:00
parent 4481bc33b0
commit 94be9902c1
27 changed files with 422 additions and 31 deletions

View file

@ -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

View file

@ -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 };
}

View file

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

View file

@ -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<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:
{
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<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_) {
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);

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);
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);

View file

@ -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);

View file

@ -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 ;

View file

@ -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

View file

@ -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 {};

View file

@ -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;
}

View file

@ -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) {

View file

@ -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);

View file

@ -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:

View file

@ -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<int32>(range.denormalize(value));
break;
case kPidOscillatorQuality:
_state.oscillatorQuality = static_cast<int32>(range.denormalize(value));
break;
case kPidAftertouch:
synth.aftertouch(sampleOffset, fastRound(value * 127.0));
break;

View file

@ -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)));

View file

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

View file

@ -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

View file

@ -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

View file

@ -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_;

View file

@ -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.
*

View file

@ -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;
}
};
}

View file

@ -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<float> 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<float> 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<float>(*tempSpan, leftSpan);
@ -1483,6 +1503,7 @@ void Voice::Impl::fillWithGenerator(AudioSpan<float> 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<float> 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);

View file

@ -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.
*

View file

@ -140,6 +140,16 @@ void sfz::Sfizz::setSampleQuality(ProcessMode mode, int 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
{
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);
}
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)
{
synth->synth.setVolume(volume);

View file

@ -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();