Add loprog/hiprob in the API and engine
This commit is contained in:
parent
3d50afc794
commit
ff0257dc07
18 changed files with 203 additions and 23 deletions
17
src/sfizz.h
17
src/sfizz.h
|
|
@ -445,6 +445,23 @@ SFIZZ_EXPORTED_API void sfizz_send_cc(sfizz_synth_t* synth, int delay, int cc_nu
|
||||||
*/
|
*/
|
||||||
SFIZZ_EXPORTED_API void sfizz_send_hdcc(sfizz_synth_t* synth, int delay, int cc_number, float norm_value);
|
SFIZZ_EXPORTED_API void sfizz_send_hdcc(sfizz_synth_t* synth, int delay, int cc_number, float norm_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a program change event to the synth.
|
||||||
|
* @since 1.1.2
|
||||||
|
*
|
||||||
|
* This command should be delay-ordered with all other midi-type events
|
||||||
|
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
|
||||||
|
* synth is undefined.
|
||||||
|
*
|
||||||
|
* @param synth The synth.
|
||||||
|
* @param delay The delay of the event in the block, in samples.
|
||||||
|
* @param program The program number, in domain 0 to 127.
|
||||||
|
*
|
||||||
|
* @par Thread-safety constraints
|
||||||
|
* - @b RT: the function must be invoked from the Real-time thread
|
||||||
|
*/
|
||||||
|
SFIZZ_EXPORTED_API void sfizz_send_program_change(sfizz_synth_t* synth, int delay, int program);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Send a high precision CC automation to the synth.
|
* @brief Send a high precision CC automation to the synth.
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,23 @@ public:
|
||||||
*/
|
*/
|
||||||
void hdcc(int delay, int ccNumber, float normValue) noexcept;
|
void hdcc(int delay, int ccNumber, float normValue) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a program change event to the synth
|
||||||
|
* @since 1.1.2
|
||||||
|
*
|
||||||
|
* This command should be delay-ordered with all other midi-type events
|
||||||
|
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
|
||||||
|
* synth is undefined.
|
||||||
|
*
|
||||||
|
* @param delay the delay at which the event occurs; this should be lower
|
||||||
|
* than the size of the block in the next call to renderBlock().
|
||||||
|
* @param program the cc number, in domain 0 to 127.
|
||||||
|
*
|
||||||
|
* @par Thread-safety constraints
|
||||||
|
* - @b RT: the function must be invoked from the Real-time thread
|
||||||
|
*/
|
||||||
|
void programChange(int delay, int program) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Send a high precision CC automation to the synth
|
* @brief Send a high precision CC automation to the synth
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,12 @@ public:
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
double getBeatsPerFrame() const { return beatsPerSecond_ * samplePeriod_; }
|
double getBeatsPerFrame() const { return beatsPerSecond_ * samplePeriod_; }
|
||||||
|
/**
|
||||||
|
* @brief Get beats per second
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
double getBeatsPerSecond() const { return beatsPerSecond_; }
|
||||||
/**
|
/**
|
||||||
* @brief Create a normalized phase signal for LFO which completes a
|
* @brief Create a normalized phase signal for LFO which completes a
|
||||||
* period every N-th beat.
|
* period every N-th beat.
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,8 @@ FloatSpec loPolyAftertouch { 0, {0, 127}, kNormalizeMidi|kPermissiveBounds };
|
||||||
FloatSpec hiPolyAftertouch { 127, {0, 127}, kNormalizeMidi|kFillGap|kPermissiveBounds };
|
FloatSpec hiPolyAftertouch { 127, {0, 127}, kNormalizeMidi|kFillGap|kPermissiveBounds };
|
||||||
FloatSpec loBend { -8191, {-8192.0f, 8191.0f}, kNormalizeBend|kPermissiveBounds };
|
FloatSpec loBend { -8191, {-8192.0f, 8191.0f}, kNormalizeBend|kPermissiveBounds };
|
||||||
FloatSpec hiBend { 8191, {-8192.0f, 8191.0f}, kNormalizeBend|kFillGap|kPermissiveBounds };
|
FloatSpec hiBend { 8191, {-8192.0f, 8191.0f}, kNormalizeBend|kFillGap|kPermissiveBounds };
|
||||||
|
UInt8Spec loProgram { 0, {0, 127}, 0 };
|
||||||
|
UInt8Spec hiProgram { 127, {0, 127}, 0 };
|
||||||
FloatSpec loNormalized { 0.0f, {0.0f, 1.0f}, kPermissiveBounds };
|
FloatSpec loNormalized { 0.0f, {0.0f, 1.0f}, kPermissiveBounds };
|
||||||
FloatSpec hiNormalized { 1.0f, {0.0f, 1.0f}, kPermissiveBounds };
|
FloatSpec hiNormalized { 1.0f, {0.0f, 1.0f}, kPermissiveBounds };
|
||||||
FloatSpec loBipolar { -1.0f, {-1.0f, 1.0f}, kPermissiveBounds };
|
FloatSpec loBipolar { -1.0f, {-1.0f, 1.0f}, kPermissiveBounds };
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,8 @@ namespace Default
|
||||||
extern const OpcodeSpec<float> xfinLo;
|
extern const OpcodeSpec<float> xfinLo;
|
||||||
extern const OpcodeSpec<float> loBend;
|
extern const OpcodeSpec<float> loBend;
|
||||||
extern const OpcodeSpec<float> hiBend;
|
extern const OpcodeSpec<float> hiBend;
|
||||||
|
extern const OpcodeSpec<uint8_t> loProgram;
|
||||||
|
extern const OpcodeSpec<uint8_t> hiProgram;
|
||||||
extern const OpcodeSpec<float> loNormalized;
|
extern const OpcodeSpec<float> loNormalized;
|
||||||
extern const OpcodeSpec<float> hiNormalized;
|
extern const OpcodeSpec<float> hiNormalized;
|
||||||
extern const OpcodeSpec<float> loBipolar;
|
extern const OpcodeSpec<float> loBipolar;
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,14 @@ void Layer::initializeActivations()
|
||||||
pitchSwitched_ = true;
|
pitchSwitched_ = true;
|
||||||
bpmSwitched_ = true;
|
bpmSwitched_ = true;
|
||||||
aftertouchSwitched_ = true;
|
aftertouchSwitched_ = true;
|
||||||
|
programSwitched_ = true;
|
||||||
ccSwitched_.set();
|
ccSwitched_.set();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Layer::isSwitchedOn() const noexcept
|
bool Layer::isSwitchedOn() const noexcept
|
||||||
{
|
{
|
||||||
return keySwitched_ && previousKeySwitched_ && sequenceSwitched_ && pitchSwitched_ && bpmSwitched_ && aftertouchSwitched_ && ccSwitched_.all();
|
return keySwitched_ && previousKeySwitched_ && sequenceSwitched_ && pitchSwitched_
|
||||||
|
&& programSwitched_ && bpmSwitched_ && aftertouchSwitched_ && ccSwitched_.all();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Layer::registerNoteOn(int noteNumber, float velocity, float randValue) noexcept
|
bool Layer::registerNoteOn(int noteNumber, float velocity, float randValue) noexcept
|
||||||
|
|
@ -152,10 +154,7 @@ void Layer::updateCCState(int ccNumber, float ccValue) noexcept
|
||||||
if (!conditions)
|
if (!conditions)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (conditions->containsWithEnd(ccValue))
|
ccSwitched_.set(ccNumber, conditions->containsWithEnd(ccValue));
|
||||||
ccSwitched_.set(ccNumber, true);
|
|
||||||
else
|
|
||||||
ccSwitched_.set(ccNumber, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Layer::registerCC(int ccNumber, float ccValue, float randValue) noexcept
|
bool Layer::registerCC(int ccNumber, float ccValue, float randValue) noexcept
|
||||||
|
|
@ -189,30 +188,23 @@ bool Layer::registerCC(int ccNumber, float ccValue, float randValue) noexcept
|
||||||
|
|
||||||
void Layer::registerPitchWheel(float pitch) noexcept
|
void Layer::registerPitchWheel(float pitch) noexcept
|
||||||
{
|
{
|
||||||
const Region& region = region_;
|
pitchSwitched_ = region_.bendRange.containsWithEnd(pitch);
|
||||||
if (region.bendRange.containsWithEnd(pitch))
|
}
|
||||||
pitchSwitched_ = true;
|
|
||||||
else
|
void Layer::registerProgramChange(int program) noexcept
|
||||||
pitchSwitched_ = false;
|
{
|
||||||
|
programSwitched_ = region_.programRange.containsWithEnd(program);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Layer::registerAftertouch(float aftertouch) noexcept
|
void Layer::registerAftertouch(float aftertouch) noexcept
|
||||||
{
|
{
|
||||||
const Region& region = region_;
|
aftertouchSwitched_ = region_.aftertouchRange.containsWithEnd(aftertouch);
|
||||||
if (region.aftertouchRange.containsWithEnd(aftertouch))
|
|
||||||
aftertouchSwitched_ = true;
|
|
||||||
else
|
|
||||||
aftertouchSwitched_ = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Layer::registerTempo(float secondsPerQuarter) noexcept
|
void Layer::registerTempo(float secondsPerQuarter) noexcept
|
||||||
{
|
{
|
||||||
const Region& region = region_;
|
|
||||||
const float bpm = 60.0f / secondsPerQuarter;
|
const float bpm = 60.0f / secondsPerQuarter;
|
||||||
if (region.bpmRange.containsWithEnd(bpm))
|
bpmSwitched_ = region_.bpmRange.containsWithEnd(bpm);
|
||||||
bpmSwitched_ = true;
|
|
||||||
else
|
|
||||||
bpmSwitched_ = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Layer::delaySustainRelease(int noteNumber, float velocity) noexcept
|
void Layer::delaySustainRelease(int noteNumber, float velocity) noexcept
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,12 @@ public:
|
||||||
* @param secondsPerQuarter
|
* @param secondsPerQuarter
|
||||||
*/
|
*/
|
||||||
void registerTempo(float secondsPerQuarter) noexcept;
|
void registerTempo(float secondsPerQuarter) noexcept;
|
||||||
|
/**
|
||||||
|
* @brief Register program change
|
||||||
|
*
|
||||||
|
* @param secondsPerQuarter
|
||||||
|
*/
|
||||||
|
void registerProgramChange(int program) noexcept;
|
||||||
|
|
||||||
// Started notes
|
// Started notes
|
||||||
bool sustainPressed_ { false };
|
bool sustainPressed_ { false };
|
||||||
|
|
@ -136,6 +142,7 @@ public:
|
||||||
bool previousKeySwitched_ {};
|
bool previousKeySwitched_ {};
|
||||||
bool sequenceSwitched_ {};
|
bool sequenceSwitched_ {};
|
||||||
bool pitchSwitched_ {};
|
bool pitchSwitched_ {};
|
||||||
|
bool programSwitched_ {};
|
||||||
bool bpmSwitched_ {};
|
bool bpmSwitched_ {};
|
||||||
bool aftertouchSwitched_ {};
|
bool aftertouchSwitched_ {};
|
||||||
std::bitset<config::numCCs> ccSwitched_;
|
std::bitset<config::numCCs> ccSwitched_;
|
||||||
|
|
|
||||||
|
|
@ -292,3 +292,14 @@ const sfz::EventVector& sfz::MidiState::getPolyAftertouchEvents(int noteNumber)
|
||||||
|
|
||||||
return polyAftertouchEvents[noteNumber];
|
return polyAftertouchEvents[noteNumber];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int sfz::MidiState::getProgram() const noexcept
|
||||||
|
{
|
||||||
|
return currentProgram;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::MidiState::programChangeEvent(int delay, int program) noexcept
|
||||||
|
{
|
||||||
|
ASSERT(program >= 0 && program <= 127);
|
||||||
|
currentProgram = program;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,20 @@ public:
|
||||||
*/
|
*/
|
||||||
float getPolyAftertouch(int noteNumber) const noexcept;
|
float getPolyAftertouch(int noteNumber) const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the current midi program
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
int getProgram() const noexcept;
|
||||||
|
/**
|
||||||
|
* @brief Register a program change event
|
||||||
|
*
|
||||||
|
* @param delay
|
||||||
|
* @param program
|
||||||
|
*/
|
||||||
|
void programChangeEvent(int delay, int program) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Register a CC event
|
* @brief Register a CC event
|
||||||
*
|
*
|
||||||
|
|
@ -275,6 +289,11 @@ private:
|
||||||
*/
|
*/
|
||||||
std::array<EventVector, 128> polyAftertouchEvents;
|
std::array<EventVector, 128> polyAftertouchEvents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Current midi program
|
||||||
|
*/
|
||||||
|
int currentProgram { 0 };
|
||||||
|
|
||||||
float sampleRate { config::defaultSampleRate };
|
float sampleRate { config::defaultSampleRate };
|
||||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||||
float alternate { 0.0f };
|
float alternate { 0.0f };
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,12 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode)
|
||||||
case hash("hibend"):
|
case hash("hibend"):
|
||||||
bendRange.setEnd(opcode.read(Default::hiBend));
|
bendRange.setEnd(opcode.read(Default::hiBend));
|
||||||
break;
|
break;
|
||||||
|
case hash("loprog"):
|
||||||
|
programRange.setStart(opcode.read(Default::loProgram));
|
||||||
|
break;
|
||||||
|
case hash("hiprog"):
|
||||||
|
programRange.setEnd(opcode.read(Default::hiProgram));
|
||||||
|
break;
|
||||||
case hash("locc&"):
|
case hash("locc&"):
|
||||||
if (opcode.parameters.back() >= config::numCCs)
|
if (opcode.parameters.back() >= config::numCCs)
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -278,6 +278,7 @@ struct Region {
|
||||||
|
|
||||||
// Region logic: MIDI conditions
|
// Region logic: MIDI conditions
|
||||||
UncheckedRange<float> bendRange { Default::loBend, Default::hiBend }; // hibend and lobend
|
UncheckedRange<float> bendRange { Default::loBend, Default::hiBend }; // hibend and lobend
|
||||||
|
UncheckedRange<uint8_t> programRange { Default::loProgram, Default::hiProgram }; // loprog and hiprog
|
||||||
CCMap<UncheckedRange<float>> ccConditions {{ Default::loCC, Default::hiCC }};
|
CCMap<UncheckedRange<float>> ccConditions {{ Default::loCC, Default::hiCC }};
|
||||||
absl::optional<uint8_t> lastKeyswitch {}; // sw_last
|
absl::optional<uint8_t> lastKeyswitch {}; // sw_last
|
||||||
absl::optional<UncheckedRange<uint8_t>> lastKeyswitchRange {}; // sw_last
|
absl::optional<UncheckedRange<uint8_t>> lastKeyswitchRange {}; // sw_last
|
||||||
|
|
|
||||||
|
|
@ -871,9 +871,10 @@ void Synth::Impl::finalizeSfzLoad()
|
||||||
region.velCurve = Curve::buildFromVelcurvePoints(
|
region.velCurve = Curve::buildFromVelcurvePoints(
|
||||||
region.velocityPoints, Curve::Interpolator::Linear);
|
region.velocityPoints, Curve::Interpolator::Linear);
|
||||||
|
|
||||||
layer.registerPitchWheel(0);
|
layer.registerPitchWheel(midiState.getPitchBend());
|
||||||
layer.registerAftertouch(0);
|
layer.registerAftertouch(midiState.getChannelAftertouch());
|
||||||
layer.registerTempo(2.0f);
|
layer.registerTempo(resources_.getBeatClock().getBeatsPerSecond());
|
||||||
|
layer.registerProgramChange(midiState.getProgram());
|
||||||
maxFilters = max(maxFilters, region.filters.size());
|
maxFilters = max(maxFilters, region.filters.size());
|
||||||
maxEQs = max(maxEQs, region.equalizers.size());
|
maxEQs = max(maxEQs, region.equalizers.size());
|
||||||
maxLFOs = max(maxLFOs, region.lfos.size());
|
maxLFOs = max(maxLFOs, region.lfos.size());
|
||||||
|
|
@ -1530,6 +1531,15 @@ void Synth::hdPitchWheel(int delay, float normalizedPitch) noexcept
|
||||||
impl.performHdcc(delay, ExtendedCCs::pitchBend, normalizedPitch, false);
|
impl.performHdcc(delay, ExtendedCCs::pitchBend, normalizedPitch, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Synth::programChange(int delay, int program) noexcept
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
impl.resources_.getMidiState().programChangeEvent(delay, program);
|
||||||
|
for (const Impl::LayerPtr& layer : impl.layers_)
|
||||||
|
layer->registerProgramChange(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Synth::channelAftertouch(int delay, int aftertouch) noexcept
|
void Synth::channelAftertouch(int delay, int aftertouch) noexcept
|
||||||
{
|
{
|
||||||
const float normalizedAftertouch = normalize7Bits(aftertouch);
|
const float normalizedAftertouch = normalize7Bits(aftertouch);
|
||||||
|
|
|
||||||
|
|
@ -408,6 +408,14 @@ public:
|
||||||
* @param normValue the normalized cc value, in domain 0 to 1
|
* @param normValue the normalized cc value, in domain 0 to 1
|
||||||
*/
|
*/
|
||||||
void hdcc(int delay, int ccNumber, float normValue) noexcept;
|
void hdcc(int delay, int ccNumber, float normValue) noexcept;
|
||||||
|
/**
|
||||||
|
* @brief Send a program change event to the synth
|
||||||
|
*
|
||||||
|
* @param delay the delay at which the event occurs; this should be lower than the size of
|
||||||
|
* the block in the next call to renderBlock().
|
||||||
|
* @param ccNumber the program number
|
||||||
|
*/
|
||||||
|
void programChange(int delay, int program) noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Send a high precision CC automation to the synth
|
* @brief Send a high precision CC automation to the synth
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -435,6 +435,14 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
||||||
client.receive(delay, path, "ff", args);
|
client.receive(delay, path, "ff", args);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
MATCH("/region&/program_range", "") {
|
||||||
|
GET_REGION_OR_BREAK(indices[0])
|
||||||
|
sfizz_arg_t args[2];
|
||||||
|
args[0].i = region.programRange.getStart();
|
||||||
|
args[1].i = region.programRange.getEnd();
|
||||||
|
client.receive(delay, path, "ii", args);
|
||||||
|
} break;
|
||||||
|
|
||||||
MATCH("/region&/cc_range&", "") {
|
MATCH("/region&/cc_range&", "") {
|
||||||
GET_REGION_OR_BREAK(indices[0])
|
GET_REGION_OR_BREAK(indices[0])
|
||||||
sfizz_arg_t args[2];
|
sfizz_arg_t args[2];
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,11 @@ void sfz::Sfizz::automateHdcc(int delay, int ccNumber, float normValue) noexcept
|
||||||
synth->synth.automateHdcc(delay, ccNumber, normValue);
|
synth->synth.automateHdcc(delay, ccNumber, normValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sfz::Sfizz::programChange(int delay, int program) noexcept
|
||||||
|
{
|
||||||
|
synth->synth.programChange(delay, program);
|
||||||
|
}
|
||||||
|
|
||||||
void sfz::Sfizz::pitchWheel(int delay, int pitch) noexcept
|
void sfz::Sfizz::pitchWheel(int delay, int pitch) noexcept
|
||||||
{
|
{
|
||||||
synth->synth.pitchWheel(delay, pitch);
|
synth->synth.pitchWheel(delay, pitch);
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,10 @@ void sfizz_send_hd_pitch_wheel(sfizz_synth_t* synth, int delay, float pitch)
|
||||||
{
|
{
|
||||||
synth->synth.hdPitchWheel(delay, pitch);
|
synth->synth.hdPitchWheel(delay, pitch);
|
||||||
}
|
}
|
||||||
|
void sfizz_send_program_change(sfizz_synth_t* synth, int delay, int program)
|
||||||
|
{
|
||||||
|
synth->synth.programChange(delay, program);
|
||||||
|
}
|
||||||
void sfizz_send_aftertouch(sfizz_synth_t* synth, int delay, int aftertouch)
|
void sfizz_send_aftertouch(sfizz_synth_t* synth, int delay, int aftertouch)
|
||||||
{
|
{
|
||||||
synth->synth.channelAftertouch(delay, aftertouch);
|
synth->synth.channelAftertouch(delay, aftertouch);
|
||||||
|
|
|
||||||
|
|
@ -706,3 +706,43 @@ TEST_CASE("[Keyswitches] sw_default with octave_offset")
|
||||||
REQUIRE(messageList == expected);
|
REQUIRE(messageList == expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Region activation] Program change")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||||
|
|
||||||
|
SECTION("Default value") {
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
|
||||||
|
<region> sample=*saw
|
||||||
|
)");
|
||||||
|
REQUIRE(synth.getLayerView(0)->isSwitchedOn());
|
||||||
|
synth.noteOn(0, 51, 64);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE(numPlayingVoices(synth) == 1);
|
||||||
|
synth.programChange(0, 45);
|
||||||
|
REQUIRE(synth.getLayerView(0)->isSwitchedOn());
|
||||||
|
synth.noteOn(0, 53, 64);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE(numPlayingVoices(synth) == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Change range") {
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
|
||||||
|
<region> sample=*saw hiprog=2
|
||||||
|
<region> sample=*sine loprog=1 hiprog=126
|
||||||
|
<region> sample=*tri loprog=-1 hiprog=200
|
||||||
|
)");
|
||||||
|
synth.noteOn(0, 51, 64);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE(playingSamples(synth) == std::vector<std::string> { "*saw", "*tri" });
|
||||||
|
synth.programChange(0, 5);
|
||||||
|
synth.noteOn(0, 53, 64);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE(playingSamples(synth) == std::vector<std::string> { "*saw", "*tri", "*sine", "*tri" });
|
||||||
|
synth.programChange(0, 127);
|
||||||
|
synth.noteOn(0, 54, 64);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE(playingSamples(synth) == std::vector<std::string> { "*saw", "*tri", "*sine", "*tri", "*tri" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -706,6 +706,31 @@ TEST_CASE("[Values] Bend range")
|
||||||
REQUIRE(messageList == expected);
|
REQUIRE(messageList == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Values] Program range")
|
||||||
|
{
|
||||||
|
Synth synth;
|
||||||
|
std::vector<std::string> messageList;
|
||||||
|
Client client(&messageList);
|
||||||
|
client.setReceiveCallback(&simpleMessageReceiver);
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||||
|
<region> sample=kick.wav
|
||||||
|
<region> sample=kick.wav loprog=1 hiprog=45
|
||||||
|
<region> sample=kick.wav loprog=-1 hiprog=555
|
||||||
|
<region> sample=kick.wav hiprog=-1
|
||||||
|
)");
|
||||||
|
synth.dispatchMessage(client, 0, "/region0/program_range", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region1/program_range", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region2/program_range", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region3/program_range", "", nullptr);
|
||||||
|
std::vector<std::string> expected {
|
||||||
|
"/region0/program_range,ii : { 0, 127 }",
|
||||||
|
"/region1/program_range,ii : { 1, 45 }",
|
||||||
|
"/region2/program_range,ii : { 0, 127 }",
|
||||||
|
"/region3/program_range,ii : { 0, 127 }",
|
||||||
|
};
|
||||||
|
REQUIRE(messageList == expected);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("[Values] CC condition range")
|
TEST_CASE("[Values] CC condition range")
|
||||||
{
|
{
|
||||||
Synth synth;
|
Synth synth;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue