Add CC modifiers to end, loop_start, loop_end
The end cc modifiers are computed at voice start like the offset The loop cc modifiers are updated in each block
This commit is contained in:
parent
689bb99e40
commit
10c5e64ba1
11 changed files with 187 additions and 79 deletions
|
|
@ -23,10 +23,12 @@ FloatSpec delayMod { 0.0f, {0.0f, 100.0f}, kPermissiveBounds };
|
|||
Int64Spec offset { 0, {0, uint32_t_max}, kPermissiveBounds };
|
||||
Int64Spec offsetMod { 0, {0, uint32_t_max}, kPermissiveBounds };
|
||||
Int64Spec offsetRandom { 0, {0, uint32_t_max}, kPermissiveBounds };
|
||||
UInt32Spec sampleEnd { uint32_t_max, {0, uint32_t_max}, kEnforceBounds };
|
||||
Int64Spec sampleEnd { uint32_t_max, {0, uint32_t_max}, kEnforceBounds };
|
||||
Int64Spec sampleEndMod { 0, {-uint32_t_max, uint32_t_max}, kPermissiveBounds };
|
||||
UInt32Spec sampleCount { 0, {0, uint32_t_max}, kEnforceUpperBound };
|
||||
UInt32Spec loopStart { 0, {0, uint32_t_max}, kEnforceUpperBound };
|
||||
UInt32Spec loopEnd { uint32_t_max, {0, uint32_t_max}, kEnforceUpperBound };
|
||||
Int64Spec loopStart { 0, {0, uint32_t_max}, kEnforceUpperBound };
|
||||
Int64Spec loopEnd { uint32_t_max, {0, uint32_t_max}, kEnforceUpperBound };
|
||||
Int64Spec loopMod { 0, {-uint32_t_max, uint32_t_max}, kPermissiveBounds };
|
||||
UInt32Spec loopCount { 0, {0, uint32_t_max}, kEnforceUpperBound };
|
||||
FloatSpec loopCrossfade { 1e-3, {1e-3, 1.0f}, kEnforceLowerBound|kPermissiveUpperBound };
|
||||
ESpec<OscillatorEnabled> oscillator { OscillatorEnabled::Auto, {OscillatorEnabled::Auto, OscillatorEnabled::On}, 0 };
|
||||
|
|
|
|||
|
|
@ -123,10 +123,12 @@ namespace Default
|
|||
extern const OpcodeSpec<int64_t> offset;
|
||||
extern const OpcodeSpec<int64_t> offsetMod;
|
||||
extern const OpcodeSpec<int64_t> offsetRandom;
|
||||
extern const OpcodeSpec<uint32_t> sampleEnd;
|
||||
extern const OpcodeSpec<int64_t> sampleEnd;
|
||||
extern const OpcodeSpec<int64_t> sampleEndMod;
|
||||
extern const OpcodeSpec<uint32_t> sampleCount;
|
||||
extern const OpcodeSpec<uint32_t> loopStart;
|
||||
extern const OpcodeSpec<uint32_t> loopEnd;
|
||||
extern const OpcodeSpec<int64_t> loopStart;
|
||||
extern const OpcodeSpec<int64_t> loopEnd;
|
||||
extern const OpcodeSpec<int64_t> loopMod;
|
||||
extern const OpcodeSpec<uint32_t> loopCount;
|
||||
extern const OpcodeSpec<float> loopCrossfade;
|
||||
extern const OpcodeSpec<float> oscillatorPhase;
|
||||
|
|
|
|||
|
|
@ -256,7 +256,8 @@ absl::optional<sfz::FileInformation> sfz::FilePool::getFileInformation(const Fil
|
|||
if (haveInstrumentInfo && instrumentInfo.loop_count > 0) {
|
||||
returnedValue.hasLoop = true;
|
||||
returnedValue.loopStart = instrumentInfo.loops[0].start;
|
||||
returnedValue.loopEnd = min(returnedValue.end, instrumentInfo.loops[0].end - 1);
|
||||
returnedValue.loopEnd =
|
||||
min(returnedValue.end, static_cast<int64_t>(instrumentInfo.loops[0].end - 1));
|
||||
}
|
||||
} else {
|
||||
// TODO loops ignored when reversed
|
||||
|
|
|
|||
|
|
@ -51,10 +51,10 @@ using FileAudioBuffer = AudioBuffer<float, 2, config::defaultAlignment,
|
|||
using FileAudioBufferPtr = std::shared_ptr<FileAudioBuffer>;
|
||||
|
||||
struct FileInformation {
|
||||
uint32_t end { Default::sampleEnd };
|
||||
uint32_t maxOffset { 0 };
|
||||
uint32_t loopStart { Default::loopStart };
|
||||
uint32_t loopEnd { Default::loopEnd };
|
||||
int64_t end { Default::sampleEnd };
|
||||
int64_t maxOffset { 0 };
|
||||
int64_t loopStart { Default::loopStart };
|
||||
int64_t loopEnd { Default::loopEnd };
|
||||
bool hasLoop { false };
|
||||
double sampleRate { config::defaultSampleRate };
|
||||
int numChannels { 0 };
|
||||
|
|
|
|||
|
|
@ -108,6 +108,12 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
|||
case hash("end"):
|
||||
sampleEnd = opcode.read(Default::sampleEnd);
|
||||
break;
|
||||
case hash("end_oncc&"): // also end_cc&
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
|
||||
endCC[opcode.parameters.back()] = opcode.read(Default::sampleEndMod);
|
||||
break;
|
||||
case hash("count"):
|
||||
sampleCount = opcode.readOptional(Default::sampleCount);
|
||||
loopMode = LoopMode::one_shot;
|
||||
|
|
@ -124,6 +130,18 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
|||
case hash("loop_start"): // also loopstart
|
||||
loopRange.setStart(opcode.read(Default::loopStart));
|
||||
break;
|
||||
case hash("loop_start_oncc&"): // also loop_start_cc&
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
|
||||
loopStartCC[opcode.parameters.back()] = opcode.read(Default::loopMod);
|
||||
break;
|
||||
case hash("loop_end_oncc&"): // also loop_end_cc&
|
||||
if (opcode.parameters.back() > config::numCCs)
|
||||
return false;
|
||||
|
||||
loopEndCC[opcode.parameters.back()] = opcode.read(Default::loopMod);
|
||||
break;
|
||||
case hash("loop_crossfade"):
|
||||
loopCrossfade = opcode.read(Default::loopCrossfade);
|
||||
break;
|
||||
|
|
@ -1552,6 +1570,7 @@ uint64_t sfz::Region::getOffset(const MidiState& midiState, Oversampling factor)
|
|||
uint64_t finalOffset = offset + offsetDistribution(Random::randomGenerator);
|
||||
for (const auto& mod: offsetCC)
|
||||
finalOffset += static_cast<uint64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
return Default::offset.bounds.clamp(finalOffset) * static_cast<uint64_t>(factor);
|
||||
}
|
||||
|
||||
|
|
@ -1562,25 +1581,38 @@ float sfz::Region::getDelay(const MidiState& midiState) const noexcept
|
|||
finalDelay += delayDistribution(Random::randomGenerator);
|
||||
for (const auto& mod: delayCC)
|
||||
finalDelay += mod.data * midiState.getCCValue(mod.cc);
|
||||
|
||||
return Default::delay.bounds.clamp(finalDelay);
|
||||
}
|
||||
|
||||
uint32_t sfz::Region::trueSampleEnd(Oversampling factor) const noexcept
|
||||
uint32_t sfz::Region::getSampleEnd(MidiState& midiState, Oversampling factor) const noexcept
|
||||
{
|
||||
if (sampleEnd <= 0)
|
||||
return 0;
|
||||
int64_t end = sampleEnd;
|
||||
for (const auto& mod: endCC)
|
||||
end += static_cast<int64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
return static_cast<uint32_t>(sampleEnd) * static_cast<uint32_t>(factor);
|
||||
end = clamp(end, int64_t { 0 }, sampleEnd);
|
||||
return static_cast<uint32_t>(end) * static_cast<uint32_t>(factor);
|
||||
}
|
||||
|
||||
uint32_t sfz::Region::loopStart(Oversampling factor) const noexcept
|
||||
uint32_t sfz::Region::loopStart(MidiState& midiState, Oversampling factor) const noexcept
|
||||
{
|
||||
return loopRange.getStart() * static_cast<uint32_t>(factor);
|
||||
auto start = loopRange.getStart();
|
||||
for (const auto& mod: loopStartCC)
|
||||
start += static_cast<int64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
start = clamp(start, int64_t { 0 }, sampleEnd);
|
||||
return static_cast<uint32_t>(start) * static_cast<uint32_t>(factor);
|
||||
}
|
||||
|
||||
uint32_t sfz::Region::loopEnd(Oversampling factor) const noexcept
|
||||
uint32_t sfz::Region::loopEnd(MidiState& midiState, Oversampling factor) const noexcept
|
||||
{
|
||||
return loopRange.getEnd() * static_cast<uint32_t>(factor);
|
||||
auto end = loopRange.getEnd();
|
||||
for (const auto& mod: loopEndCC)
|
||||
end += static_cast<int64_t>(mod.data * midiState.getCCValue(mod.cc));
|
||||
|
||||
end = clamp(end, int64_t { 0 }, sampleEnd);
|
||||
return static_cast<uint32_t>(end) * static_cast<uint32_t>(factor);
|
||||
}
|
||||
|
||||
float sfz::Region::getNoteGain(int noteNumber, float velocity) const noexcept
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ struct Region {
|
|||
*
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint32_t trueSampleEnd(Oversampling factor = Oversampling::x1) const noexcept;
|
||||
uint32_t getSampleEnd(MidiState& midiState, Oversampling factor = Oversampling::x1) const noexcept;
|
||||
/**
|
||||
* @brief Parse a new opcode into the region to fill in the proper parameters.
|
||||
* This must be called multiple times for each opcode applying to this region.
|
||||
|
|
@ -259,8 +259,8 @@ struct Region {
|
|||
|
||||
void offsetAllKeys(int offset) noexcept;
|
||||
|
||||
uint32_t loopStart(Oversampling factor = Oversampling::x1) const noexcept;
|
||||
uint32_t loopEnd(Oversampling factor = Oversampling::x1) const noexcept;
|
||||
uint32_t loopStart(MidiState& midiState, Oversampling factor = Oversampling::x1) const noexcept;
|
||||
uint32_t loopEnd(MidiState& midiState, Oversampling factor = Oversampling::x1) const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the gain this region contributes into the input of the Nth
|
||||
|
|
@ -305,10 +305,13 @@ struct Region {
|
|||
int64_t offset { Default::offset }; // offset
|
||||
int64_t offsetRandom { Default::offsetRandom }; // offset_random
|
||||
CCMap<int64_t> offsetCC { Default::offsetMod };
|
||||
uint32_t sampleEnd { Default::sampleEnd }; // end
|
||||
int64_t sampleEnd { Default::sampleEnd }; // end
|
||||
CCMap<int64_t> endCC { Default::sampleEndMod };
|
||||
absl::optional<uint32_t> sampleCount {}; // count
|
||||
absl::optional<LoopMode> loopMode {}; // loopmode
|
||||
UncheckedRange<uint32_t> loopRange { Default::loopStart, Default::loopEnd }; //loopstart and loopend
|
||||
UncheckedRange<int64_t> loopRange { Default::loopStart, Default::loopEnd }; //loopstart and loopend
|
||||
CCMap<int64_t> loopStartCC { Default::sampleEndMod };
|
||||
CCMap<int64_t> loopEndCC { Default::sampleEndMod };
|
||||
absl::optional<uint32_t> loopCount {}; // count
|
||||
float loopCrossfade { Default::loopCrossfade }; // loop_crossfade
|
||||
|
||||
|
|
|
|||
|
|
@ -590,7 +590,7 @@ void Synth::Impl::finalizeSfzLoad()
|
|||
}
|
||||
|
||||
if (!region.isOscillator()) {
|
||||
region.sampleEnd = std::min(region.sampleEnd, fileInformation->end);
|
||||
region.sampleEnd = min(region.sampleEnd, fileInformation->end);
|
||||
|
||||
if (fileInformation->hasLoop) {
|
||||
if (region.loopRange.getStart() == Default::loopStart)
|
||||
|
|
|
|||
|
|
@ -165,6 +165,11 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
|||
client.receive<'h'>(delay, path, region.sampleEnd);
|
||||
} break;
|
||||
|
||||
MATCH("/region&/end_cc&", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
client.receive<'h'>(delay, path, region.endCC.getWithDefault(indices[1]));
|
||||
} break;
|
||||
|
||||
MATCH("/region&/enabled", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
if (region.disabled()) {
|
||||
|
|
@ -208,6 +213,16 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
|||
client.receive(delay, path, "hh", args);
|
||||
} break;
|
||||
|
||||
MATCH("/region&/loop_start_cc&", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
client.receive<'h'>(delay, path, region.loopStartCC.getWithDefault(indices[1]));
|
||||
} break;
|
||||
|
||||
MATCH("/region&/loop_end_cc&", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
client.receive<'h'>(delay, path, region.loopEndCC.getWithDefault(indices[1]));
|
||||
} break;
|
||||
|
||||
MATCH("/region&/loop_mode", "") {
|
||||
GET_REGION_OR_BREAK(indices[0])
|
||||
if (!region.loopMode) {
|
||||
|
|
|
|||
|
|
@ -459,7 +459,7 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc
|
|||
impl.triggerDelay_ = delay;
|
||||
impl.initialDelay_ = delay + static_cast<int>(region.getDelay(resources.midiState) * impl.sampleRate_);
|
||||
impl.baseFrequency_ = resources.tuning.getFrequencyOfKey(impl.triggerEvent_.number);
|
||||
impl.sampleSize_ = region.trueSampleEnd(resources.filePool.getOversamplingFactor()) - impl.sourcePosition_ - 1;
|
||||
impl.sampleSize_ = region.getSampleEnd(resources.midiState, resources.filePool.getOversamplingFactor()) - impl.sourcePosition_ - 1;
|
||||
impl.bendStepFactor_ = centsFactor(region.bendStep);
|
||||
impl.bendSmoother_.setSmoothing(region.bendSmooth, impl.sampleRate_);
|
||||
impl.bendSmoother_.reset(centsFactor(region.getBendInCents(resources.midiState.getPitchBend())));
|
||||
|
|
@ -578,7 +578,6 @@ void Voice::registerNoteOff(int delay, int noteNumber, float velocity) noexcept
|
|||
|
||||
void Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept
|
||||
{
|
||||
ASSERT(ccValue >= 0.0 && ccValue <= 1.0);
|
||||
Impl& impl = *impl_;
|
||||
if (impl.region_ == nullptr)
|
||||
return;
|
||||
|
|
@ -986,7 +985,8 @@ void Voice::Impl::fillWithData(AudioSpan<float> buffer) noexcept
|
|||
add1<int>(sourcePosition_, *indices);
|
||||
}
|
||||
|
||||
// calculate loop characteristics
|
||||
// Update loop characteristics with the current CC state
|
||||
updateLoopInformation();
|
||||
const auto loop = this->loop_;
|
||||
|
||||
// Looping logic
|
||||
|
|
@ -1035,7 +1035,7 @@ void Voice::Impl::fillWithData(AudioSpan<float> buffer) noexcept
|
|||
}
|
||||
|
||||
const auto sampleEnd = min(
|
||||
int(region_->trueSampleEnd(resources_.filePool.getOversamplingFactor())),
|
||||
int(sampleSize_),
|
||||
int(currentPromise_->information.end),
|
||||
int(source.getNumFrames()))
|
||||
- 1;
|
||||
|
|
@ -1591,12 +1591,14 @@ void Voice::Impl::updateLoopInformation() noexcept
|
|||
|
||||
if (!region_->shouldLoop())
|
||||
return;
|
||||
const auto& info = currentPromise_->information;
|
||||
const auto factor = resources_.filePool.getOversamplingFactor();
|
||||
const auto rate = info.sampleRate;
|
||||
|
||||
loop_.end = static_cast<int>(region_->loopEnd(factor));
|
||||
loop_.start = static_cast<int>(region_->loopStart(factor));
|
||||
Resources& resources = resources_;
|
||||
const FileInformation& info = currentPromise_->information;
|
||||
const Oversampling factor = resources_.filePool.getOversamplingFactor();
|
||||
const double rate = info.sampleRate;
|
||||
|
||||
loop_.start = static_cast<int>(region_->loopStart(resources.midiState, factor));
|
||||
loop_.end = max(static_cast<int>(region_->loopEnd(resources.midiState, factor)), loop_.start);
|
||||
loop_.size = loop_.end + 1 - loop_.start;
|
||||
loop_.xfSize = static_cast<int>(lroundPositive(region_->loopCrossfade * rate));
|
||||
// Clamp the crossfade to the part available before the loop starts
|
||||
|
|
|
|||
|
|
@ -535,9 +535,9 @@ TEST_CASE("[Files] Looped regions taken from files and possibly overriden")
|
|||
REQUIRE( synth.getRegionView(1)->loopMode == LoopMode::no_loop );
|
||||
REQUIRE( synth.getRegionView(2)->loopMode == LoopMode::loop_continuous );
|
||||
|
||||
REQUIRE(synth.getRegionView(0)->loopRange == Range<uint32_t> { 77554, 186581 });
|
||||
REQUIRE(synth.getRegionView(1)->loopRange == Range<uint32_t> { 77554, 186581 });
|
||||
REQUIRE(synth.getRegionView(2)->loopRange == Range<uint32_t> { 4, 124 });
|
||||
REQUIRE(synth.getRegionView(0)->loopRange == Range<int64_t> { 77554, 186581 });
|
||||
REQUIRE(synth.getRegionView(1)->loopRange == Range<int64_t> { 77554, 186581 });
|
||||
REQUIRE(synth.getRegionView(2)->loopRange == Range<int64_t> { 4, 124 });
|
||||
}
|
||||
|
||||
TEST_CASE("[Files] Looped regions can start at 0")
|
||||
|
|
@ -548,7 +548,7 @@ TEST_CASE("[Files] Looped regions can start at 0")
|
|||
)");
|
||||
REQUIRE( synth.getNumRegions() == 1 );
|
||||
REQUIRE( synth.getRegionView(0)->loopMode == LoopMode::loop_continuous );
|
||||
REQUIRE( synth.getRegionView(0)->loopRange == Range<uint32_t> { 0, synth.getRegionView(0)->sampleEnd } );
|
||||
REQUIRE( synth.getRegionView(0)->loopRange == Range<int64_t> { 0, synth.getRegionView(0)->sampleEnd } );
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Release triggers automatically sets the loop mode")
|
||||
|
|
|
|||
|
|
@ -189,30 +189,55 @@ TEST_CASE("[Values] End")
|
|||
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 end=194
|
||||
<region> sample=kick.wav end=-1
|
||||
<region> sample=kick.wav end=0
|
||||
<region> sample=kick.wav end=194 end=-1
|
||||
<region> sample=kick.wav end=0 end=194
|
||||
)");
|
||||
synth.dispatchMessage(client, 0, "/region0/end", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region0/enabled", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/enabled", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/enabled", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region3/enabled", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region4/enabled", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region4/end", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/end,h : { 194 }",
|
||||
"/region0/enabled,T : { }",
|
||||
"/region1/enabled,F : { }",
|
||||
"/region2/enabled,F : { }",
|
||||
"/region3/enabled,F : { }",
|
||||
"/region4/enabled,T : { }",
|
||||
"/region4/end,h : { 194 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
SECTION("Basic")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||
<region> sample=kick.wav end=194
|
||||
<region> sample=kick.wav end=-1
|
||||
<region> sample=kick.wav end=0
|
||||
<region> sample=kick.wav end=194 end=-1
|
||||
<region> sample=kick.wav end=0 end=194
|
||||
)");
|
||||
synth.dispatchMessage(client, 0, "/region0/end", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region0/enabled", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/enabled", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/enabled", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region3/enabled", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region4/enabled", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region4/end", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/end,h : { 194 }",
|
||||
"/region0/enabled,T : { }",
|
||||
"/region1/enabled,F : { }",
|
||||
"/region2/enabled,F : { }",
|
||||
"/region3/enabled,F : { }",
|
||||
"/region4/enabled,T : { }",
|
||||
"/region4/end,h : { 194 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
SECTION("CC")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||
<region> sample=kick.wav
|
||||
<region> sample=kick.wav end_cc12=12
|
||||
<region> sample=kick.wav end_oncc12=-12
|
||||
<region> sample=kick.wav end_cc14=14 end_cc12=12 end_oncc12=-12
|
||||
)");
|
||||
synth.dispatchMessage(client, 0, "/region0/end_cc12", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/end_cc12", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/end_cc12", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region3/end_cc14", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region3/end_cc12", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/end_cc12,h : { 0 }",
|
||||
"/region1/end_cc12,h : { 12 }",
|
||||
"/region2/end_cc12,h : { -12 }",
|
||||
"/region3/end_cc14,h : { 14 }",
|
||||
"/region3/end_cc12,h : { -12 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Values] Count")
|
||||
|
|
@ -305,23 +330,49 @@ TEST_CASE("[Values] Loop range")
|
|||
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 loop_start=10 loop_end=100
|
||||
<region> sample=kick.wav loopstart=10 loopend=100
|
||||
<region> sample=kick.wav loop_start=-1 loopend=-100
|
||||
)");
|
||||
synth.dispatchMessage(client, 0, "/region0/loop_range", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/loop_range", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/loop_range", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region3/loop_range", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/loop_range,hh : { 0, 44011 }", // Default loop points in the file
|
||||
"/region1/loop_range,hh : { 10, 100 }",
|
||||
"/region2/loop_range,hh : { 10, 100 }",
|
||||
"/region3/loop_range,hh : { 0, 44011 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
SECTION("Basic")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||
<region> sample=kick.wav
|
||||
<region> sample=kick.wav loop_start=10 loop_end=100
|
||||
<region> sample=kick.wav loopstart=10 loopend=100
|
||||
<region> sample=kick.wav loop_start=-1 loopend=-100
|
||||
)");
|
||||
synth.dispatchMessage(client, 0, "/region0/loop_range", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/loop_range", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/loop_range", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region3/loop_range", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/loop_range,hh : { 0, 44011 }", // Default loop points in the file
|
||||
"/region1/loop_range,hh : { 10, 100 }",
|
||||
"/region2/loop_range,hh : { 10, 100 }",
|
||||
"/region3/loop_range,hh : { 0, 44011 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
SECTION("CC")
|
||||
{
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||
<region> sample=kick.wav
|
||||
<region> sample=kick.wav loop_start_cc12=10 loop_end_cc14=-100
|
||||
<region> sample=kick.wav loop_start_oncc12=-10 loop_end_oncc14=100
|
||||
)");
|
||||
synth.dispatchMessage(client, 0, "/region0/loop_start_cc12", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region0/loop_end_cc14", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/loop_start_cc12", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region1/loop_end_cc14", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/loop_start_cc12", "", nullptr);
|
||||
synth.dispatchMessage(client, 0, "/region2/loop_end_cc14", "", nullptr);
|
||||
std::vector<std::string> expected {
|
||||
"/region0/loop_start_cc12,h : { 0 }",
|
||||
"/region0/loop_end_cc14,h : { 0 }",
|
||||
"/region1/loop_start_cc12,h : { 10 }",
|
||||
"/region1/loop_end_cc14,h : { -100 }",
|
||||
"/region2/loop_start_cc12,h : { -10 }",
|
||||
"/region2/loop_end_cc14,h : { 100 }",
|
||||
};
|
||||
REQUIRE(messageList == expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Values] Loop crossfade")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue