Merge pull request #654 from paulfd/loop-count
Add support for count and loop_count
This commit is contained in:
commit
8b16f82fda
8 changed files with 189 additions and 81 deletions
|
|
@ -12,9 +12,10 @@ extern const OpcodeSpec<int64_t> offset { 0, Range<int64_t>(0, uint32_t_max), 0
|
||||||
extern const OpcodeSpec<int64_t> offsetMod { 0, Range<int64_t>(0, uint32_t_max), 0 };
|
extern const OpcodeSpec<int64_t> offsetMod { 0, Range<int64_t>(0, uint32_t_max), 0 };
|
||||||
extern const OpcodeSpec<int64_t> offsetRandom { 0, Range<int64_t>(0, uint32_t_max), 0 };
|
extern const OpcodeSpec<int64_t> offsetRandom { 0, Range<int64_t>(0, uint32_t_max), 0 };
|
||||||
extern const OpcodeSpec<uint32_t> sampleEnd { uint32_t_max, Range<uint32_t>(0, uint32_t_max), kEnforceLowerBound };
|
extern const OpcodeSpec<uint32_t> sampleEnd { uint32_t_max, Range<uint32_t>(0, uint32_t_max), kEnforceLowerBound };
|
||||||
extern const OpcodeSpec<uint32_t> sampleCount { 1, Range<uint32_t>(1, uint32_t_max), 0 };
|
extern const OpcodeSpec<uint32_t> sampleCount { 0, Range<uint32_t>(0, uint32_t_max), 0 };
|
||||||
extern const OpcodeSpec<uint32_t> loopStart { 0, Range<uint32_t>(0, uint32_t_max), 0 };
|
extern const OpcodeSpec<uint32_t> loopStart { 0, Range<uint32_t>(0, uint32_t_max), 0 };
|
||||||
extern const OpcodeSpec<uint32_t> loopEnd { uint32_t_max, Range<uint32_t>(0, uint32_t_max), 0 };
|
extern const OpcodeSpec<uint32_t> loopEnd { uint32_t_max, Range<uint32_t>(0, uint32_t_max), 0 };
|
||||||
|
extern const OpcodeSpec<uint32_t> loopCount { 0, Range<uint32_t>(0, uint32_t_max), 0 };
|
||||||
extern const OpcodeSpec<float> loopCrossfade { 1e-3, Range<float>(1e-3, 1.0f), 0 };
|
extern const OpcodeSpec<float> loopCrossfade { 1e-3, Range<float>(1e-3, 1.0f), 0 };
|
||||||
extern const OpcodeSpec<OscillatorEnabled> oscillator { OscillatorEnabled::Auto, Range<OscillatorEnabled>(OscillatorEnabled::Auto, OscillatorEnabled::On), 0 };
|
extern const OpcodeSpec<OscillatorEnabled> oscillator { OscillatorEnabled::Auto, Range<OscillatorEnabled>(OscillatorEnabled::Auto, OscillatorEnabled::On), 0 };
|
||||||
extern const OpcodeSpec<float> oscillatorPhase { 0.0f, Range<float>(-1000.0f, 1000.0f), 0 };
|
extern const OpcodeSpec<float> oscillatorPhase { 0.0f, Range<float>(-1000.0f, 1000.0f), 0 };
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ namespace Default
|
||||||
extern const OpcodeSpec<uint32_t> sampleCount;
|
extern const OpcodeSpec<uint32_t> sampleCount;
|
||||||
extern const OpcodeSpec<uint32_t> loopStart;
|
extern const OpcodeSpec<uint32_t> loopStart;
|
||||||
extern const OpcodeSpec<uint32_t> loopEnd;
|
extern const OpcodeSpec<uint32_t> loopEnd;
|
||||||
|
extern const OpcodeSpec<uint32_t> loopCount;
|
||||||
extern const OpcodeSpec<float> loopCrossfade;
|
extern const OpcodeSpec<float> loopCrossfade;
|
||||||
extern const OpcodeSpec<float> oscillatorPhase;
|
extern const OpcodeSpec<float> oscillatorPhase;
|
||||||
extern const OpcodeSpec<OscillatorEnabled> oscillator;
|
extern const OpcodeSpec<OscillatorEnabled> oscillator;
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,8 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
||||||
sampleEnd = opcode.read(Default::sampleEnd);
|
sampleEnd = opcode.read(Default::sampleEnd);
|
||||||
break;
|
break;
|
||||||
case hash("count"):
|
case hash("count"):
|
||||||
sampleCount = opcode.read(Default::sampleCount);
|
sampleCount = opcode.readOptional(Default::sampleCount);
|
||||||
|
loopMode = LoopMode::one_shot;
|
||||||
break;
|
break;
|
||||||
case hash("loop_mode"): // also loopmode
|
case hash("loop_mode"): // also loopmode
|
||||||
loopMode = opcode.readOptional(Default::loopMode);
|
loopMode = opcode.readOptional(Default::loopMode);
|
||||||
|
|
@ -130,6 +131,9 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
||||||
case hash("loop_end"): // also loopend
|
case hash("loop_end"): // also loopend
|
||||||
loopRange.setEnd(opcode.read(Default::loopEnd));
|
loopRange.setEnd(opcode.read(Default::loopEnd));
|
||||||
break;
|
break;
|
||||||
|
case hash("loop_count"):
|
||||||
|
loopCount = opcode.readOptional(Default::loopCount);
|
||||||
|
break;
|
||||||
case hash("loop_start"): // also loopstart
|
case hash("loop_start"): // also loopstart
|
||||||
loopRange.setStart(opcode.read(Default::loopStart));
|
loopRange.setStart(opcode.read(Default::loopStart));
|
||||||
break;
|
break;
|
||||||
|
|
@ -1657,7 +1661,7 @@ uint32_t sfz::Region::trueSampleEnd(Oversampling factor) const noexcept
|
||||||
if (sampleEnd <= 0)
|
if (sampleEnd <= 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return min(static_cast<uint32_t>(sampleEnd), loopRange.getEnd()) * static_cast<uint32_t>(factor);
|
return static_cast<uint32_t>(sampleEnd) * static_cast<uint32_t>(factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t sfz::Region::loopStart(Oversampling factor) const noexcept
|
uint32_t sfz::Region::loopStart(Oversampling factor) const noexcept
|
||||||
|
|
|
||||||
|
|
@ -326,9 +326,10 @@ struct Region {
|
||||||
int64_t offsetRandom { Default::offsetRandom }; // offset_random
|
int64_t offsetRandom { Default::offsetRandom }; // offset_random
|
||||||
CCMap<int64_t> offsetCC { Default::offsetMod };
|
CCMap<int64_t> offsetCC { Default::offsetMod };
|
||||||
uint32_t sampleEnd { Default::sampleEnd }; // end
|
uint32_t sampleEnd { Default::sampleEnd }; // end
|
||||||
uint32_t sampleCount { Default::sampleCount }; // count
|
absl::optional<uint32_t> sampleCount {}; // count
|
||||||
absl::optional<LoopMode> loopMode {}; // loopmode
|
absl::optional<LoopMode> loopMode {}; // loopmode
|
||||||
Range<uint32_t> loopRange { Default::loopStart, Default::loopEnd }; //loopstart and loopend
|
Range<uint32_t> loopRange { Default::loopStart, Default::loopEnd }; //loopstart and loopend
|
||||||
|
absl::optional<uint32_t> loopCount {}; // count
|
||||||
float loopCrossfade { Default::loopCrossfade }; // loop_crossfade
|
float loopCrossfade { Default::loopCrossfade }; // loop_crossfade
|
||||||
|
|
||||||
// Wavetable oscillator
|
// Wavetable oscillator
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,10 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
||||||
|
|
||||||
MATCH("/region&/count", "") {
|
MATCH("/region&/count", "") {
|
||||||
GET_REGION_OR_BREAK(indices[0])
|
GET_REGION_OR_BREAK(indices[0])
|
||||||
client.receive<'h'>(delay, path, region.sampleCount);
|
if (region.sampleCount)
|
||||||
|
client.receive<'h'>(delay, path, *region.sampleCount);
|
||||||
|
else
|
||||||
|
client.receive<'N'>(delay, path, {});
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
MATCH("/region&/loop_range", "") {
|
MATCH("/region&/loop_range", "") {
|
||||||
|
|
@ -225,6 +228,14 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
||||||
client.receive<'f'>(delay, path, region.loopCrossfade);
|
client.receive<'f'>(delay, path, region.loopCrossfade);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
MATCH("/region&/loop_count", "") {
|
||||||
|
GET_REGION_OR_BREAK(indices[0])
|
||||||
|
if (region.loopCount)
|
||||||
|
client.receive<'h'>(delay, path, *region.loopCount);
|
||||||
|
else
|
||||||
|
client.receive<'N'>(delay, path, {});
|
||||||
|
} break;
|
||||||
|
|
||||||
MATCH("/region&/group", "") {
|
MATCH("/region&/group", "") {
|
||||||
GET_REGION_OR_BREAK(indices[0])
|
GET_REGION_OR_BREAK(indices[0])
|
||||||
client.receive<'h'>(delay, path, region.group);
|
client.receive<'h'>(delay, path, region.group);
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,30 @@ struct Voice::Impl
|
||||||
*/
|
*/
|
||||||
void updateLoopInformation() noexcept;
|
void updateLoopInformation() noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check whether the voice is released
|
||||||
|
*
|
||||||
|
* @return true
|
||||||
|
* @return false
|
||||||
|
*/
|
||||||
|
bool released() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Release the voice after a given delay
|
||||||
|
*
|
||||||
|
* @param delay
|
||||||
|
*/
|
||||||
|
void release(int delay) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Off the voice (steal). This will respect the off mode of the region
|
||||||
|
* and set the envelopes if necessary.
|
||||||
|
*
|
||||||
|
* @param delay
|
||||||
|
* @param fast whether to apply a fast release regardless of the off mode
|
||||||
|
*/
|
||||||
|
void off(int delay, bool fast = false) noexcept;
|
||||||
|
|
||||||
const NumericId<Voice> id_;
|
const NumericId<Voice> id_;
|
||||||
StateListener* stateListener_ = nullptr;
|
StateListener* stateListener_ = nullptr;
|
||||||
|
|
||||||
|
|
@ -192,6 +216,9 @@ struct Voice::Impl
|
||||||
int sourcePosition_ { 0 };
|
int sourcePosition_ { 0 };
|
||||||
int initialDelay_ { 0 };
|
int initialDelay_ { 0 };
|
||||||
int age_ { 0 };
|
int age_ { 0 };
|
||||||
|
uint32_t count_ { 1 };
|
||||||
|
int sampleSize_ { 0 };
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
int start { 0 };
|
int start { 0 };
|
||||||
int end { 0 };
|
int end { 0 };
|
||||||
|
|
@ -199,6 +226,7 @@ struct Voice::Impl
|
||||||
int xfSize { 0 };
|
int xfSize { 0 };
|
||||||
int xfOutStart { 0 };
|
int xfOutStart { 0 };
|
||||||
int xfInStart { 0 };
|
int xfInStart { 0 };
|
||||||
|
uint32_t restarts { 0 };
|
||||||
} loop_;
|
} loop_;
|
||||||
|
|
||||||
FileDataHolder currentPromise_;
|
FileDataHolder currentPromise_;
|
||||||
|
|
@ -416,6 +444,7 @@ void Voice::startVoice(Region* region, int delay, const TriggerEvent& event) noe
|
||||||
impl.triggerDelay_ = delay;
|
impl.triggerDelay_ = delay;
|
||||||
impl.initialDelay_ = delay + static_cast<int>(region->getDelay() * impl.sampleRate_);
|
impl.initialDelay_ = delay + static_cast<int>(region->getDelay() * impl.sampleRate_);
|
||||||
impl.baseFrequency_ = impl.resources_.tuning.getFrequencyOfKey(impl.triggerEvent_.number);
|
impl.baseFrequency_ = impl.resources_.tuning.getFrequencyOfKey(impl.triggerEvent_.number);
|
||||||
|
impl.sampleSize_ = region->trueSampleEnd(impl.resources_.filePool.getOversamplingFactor()) - impl.sourcePosition_ - 1;
|
||||||
impl.bendStepFactor_ = centsFactor(region->bendStep);
|
impl.bendStepFactor_ = centsFactor(region->bendStep);
|
||||||
impl.bendSmoother_.setSmoothing(region->bendSmooth, impl.sampleRate_);
|
impl.bendSmoother_.setSmoothing(region->bendSmooth, impl.sampleRate_);
|
||||||
impl.bendSmoother_.reset(centsFactor(region->getBendInCents(impl.resources_.midiState.getPitchBend())));
|
impl.bendSmoother_.reset(centsFactor(region->getBendInCents(impl.resources_.midiState.getPitchBend())));
|
||||||
|
|
@ -445,29 +474,39 @@ bool Voice::isFree() const noexcept
|
||||||
void Voice::release(int delay) noexcept
|
void Voice::release(int delay) noexcept
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
if (impl.state_ != State::playing)
|
impl.release(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Voice::Impl::release(int delay) noexcept
|
||||||
|
{
|
||||||
|
if (state_ != State::playing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!impl.region_->flexAmpEG) {
|
if (!region_->flexAmpEG) {
|
||||||
if (impl.egAmplitude_.getRemainingDelay() > delay)
|
if (egAmplitude_.getRemainingDelay() > delay)
|
||||||
impl.switchState(State::cleanMeUp);
|
switchState(State::cleanMeUp);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (impl.flexEGs_[*impl.region_->flexAmpEG]->getRemainingDelay() > static_cast<unsigned>(delay))
|
if (flexEGs_[*region_->flexAmpEG]->getRemainingDelay() > static_cast<unsigned>(delay))
|
||||||
impl.switchState(State::cleanMeUp);
|
switchState(State::cleanMeUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl.resources_.modMatrix.releaseVoice(impl.id_, impl.region_->getId(), delay);
|
resources_.modMatrix.releaseVoice(id_, region_->getId(), delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Voice::off(int delay, bool fast) noexcept
|
void Voice::off(int delay, bool fast) noexcept
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
if (!impl.region_->flexAmpEG) {
|
impl.off(delay, fast);
|
||||||
if (impl.region_->offMode == OffMode::fast || fast) {
|
}
|
||||||
impl.egAmplitude_.setReleaseTime(Default::offTime);
|
|
||||||
} else if (impl.region_->offMode == OffMode::time) {
|
void Voice::Impl::off(int delay, bool fast) noexcept
|
||||||
impl.egAmplitude_.setReleaseTime(impl.region_->offTime);
|
{
|
||||||
|
if (!region_->flexAmpEG) {
|
||||||
|
if (region_->offMode == OffMode::fast || fast) {
|
||||||
|
egAmplitude_.setReleaseTime(Default::offTime);
|
||||||
|
} else if (region_->offMode == OffMode::time) {
|
||||||
|
egAmplitude_.setReleaseTime(region_->offTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -882,8 +921,13 @@ void Voice::Impl::fillWithData(AudioSpan<float> buffer) noexcept
|
||||||
|
|
||||||
// calculate loop characteristics
|
// calculate loop characteristics
|
||||||
const auto loop = this->loop_;
|
const auto loop = this->loop_;
|
||||||
const bool isLooping = region_->shouldLoop()
|
|
||||||
&& (static_cast<size_t>(loop.end) < source.getNumFrames());
|
// Looping logic
|
||||||
|
const bool hasLoopSamples = static_cast<size_t>(loop.end) < source.getNumFrames();
|
||||||
|
const bool loopCountReached = region_->loopCount && loop_.restarts >= *region_->loopCount;
|
||||||
|
const bool loopContinuous = (region_->loopMode == LoopMode::loop_continuous);
|
||||||
|
const bool loopSustain = (region_->loopMode == LoopMode::loop_sustain) && !released();
|
||||||
|
const bool shouldLoop = hasLoopSamples && (loopSustain || loopContinuous) && !loopCountReached;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
loop start loop end
|
loop start loop end
|
||||||
|
|
@ -905,13 +949,7 @@ void Voice::Impl::fillWithData(AudioSpan<float> buffer) noexcept
|
||||||
enum PartitionType { kPartitionNormal, kPartitionLoopXfade };
|
enum PartitionType { kPartitionNormal, kPartitionLoopXfade };
|
||||||
|
|
||||||
SpanHolder<absl::Span<int>> partitionBuffers[2];
|
SpanHolder<absl::Span<int>> partitionBuffers[2];
|
||||||
if (!isLooping) {
|
if (shouldLoop) {
|
||||||
static const int starts[1] = { 0 };
|
|
||||||
static const int types[1] = { kPartitionNormal };
|
|
||||||
partitionStarts = absl::MakeSpan(const_cast<int*>(starts), 1);
|
|
||||||
partitionTypes = absl::MakeSpan(const_cast<int*>(types), 1);
|
|
||||||
numPartitions = 1;
|
|
||||||
} else {
|
|
||||||
for (auto& buf : partitionBuffers) {
|
for (auto& buf : partitionBuffers) {
|
||||||
buf = resources_.bufferPool.getIndexBuffer(numSamples);
|
buf = resources_.bufferPool.getIndexBuffer(numSamples);
|
||||||
if (!buf)
|
if (!buf)
|
||||||
|
|
@ -921,63 +959,85 @@ void Voice::Impl::fillWithData(AudioSpan<float> buffer) noexcept
|
||||||
partitionTypes = *partitionBuffers[1];
|
partitionTypes = *partitionBuffers[1];
|
||||||
// Note: partitions will be alternance of Normal/Xfade
|
// Note: partitions will be alternance of Normal/Xfade
|
||||||
// computed along with index processing below
|
// computed along with index processing below
|
||||||
|
} else {
|
||||||
|
static const int starts[1] = { 0 };
|
||||||
|
static const int types[1] = { kPartitionNormal };
|
||||||
|
partitionStarts = absl::MakeSpan(const_cast<int*>(starts), 1);
|
||||||
|
partitionTypes = absl::MakeSpan(const_cast<int*>(types), 1);
|
||||||
|
numPartitions = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// index preprocessing for loops
|
const auto sampleEnd = min(
|
||||||
if (isLooping) {
|
|
||||||
int oldIndex {};
|
|
||||||
int oldPartitionType {};
|
|
||||||
for (unsigned i = 0; i < numSamples; ++i) {
|
|
||||||
int index = (*indices)[i];
|
|
||||||
|
|
||||||
// wrap indices post loop-entry around the loop segment
|
|
||||||
int wrappedIndex = (index <= loop.end) ? index :
|
|
||||||
(loop.start + (index - loop.start) % loop.size);
|
|
||||||
(*indices)[i] = wrappedIndex;
|
|
||||||
|
|
||||||
// identify the partition this index is in
|
|
||||||
bool xfading = wrappedIndex >= loop.start && wrappedIndex >= loop.xfOutStart;
|
|
||||||
int partitionType = xfading ? kPartitionLoopXfade : kPartitionNormal;
|
|
||||||
// if looping or entering a different type, start a new partition
|
|
||||||
bool start = i == 0 || wrappedIndex < oldIndex || partitionType != oldPartitionType;
|
|
||||||
if (start) {
|
|
||||||
partitionStarts[numPartitions] = i;
|
|
||||||
partitionTypes[numPartitions] = partitionType;
|
|
||||||
++numPartitions;
|
|
||||||
}
|
|
||||||
|
|
||||||
oldIndex = wrappedIndex;
|
|
||||||
oldPartitionType = partitionType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// index preprocessing for one-shots
|
|
||||||
else {
|
|
||||||
// cut short the voice at the instant of reaching end of sample
|
|
||||||
const auto sampleEnd = min(
|
|
||||||
int(region_->trueSampleEnd(resources_.filePool.getOversamplingFactor())),
|
int(region_->trueSampleEnd(resources_.filePool.getOversamplingFactor())),
|
||||||
int(currentPromise_->information.end),
|
int(currentPromise_->information.end),
|
||||||
int(source.getNumFrames()))
|
int(source.getNumFrames()))
|
||||||
- 1;
|
- 1;
|
||||||
|
|
||||||
for (unsigned i = 0; i < numSamples; ++i) {
|
int blockRestarts { 0 };
|
||||||
|
int oldIndex {};
|
||||||
|
int oldPartitionType {};
|
||||||
|
|
||||||
|
const auto addPartitionIfNecessary = [&] (unsigned blockIndex, int wrappedIndex, bool wrapped) {
|
||||||
|
const bool xfading = wrappedIndex >= loop.start && wrappedIndex >= loop.xfOutStart;
|
||||||
|
const int partitionType = xfading ? kPartitionLoopXfade : kPartitionNormal;
|
||||||
|
|
||||||
|
// if looping or entering a different type, start a new partition
|
||||||
|
bool start = blockIndex == 0 || wrapped || partitionType != oldPartitionType;
|
||||||
|
if (start) {
|
||||||
|
partitionStarts[numPartitions] = blockIndex;
|
||||||
|
partitionTypes[numPartitions] = partitionType;
|
||||||
|
++numPartitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
oldIndex = wrappedIndex;
|
||||||
|
oldPartitionType = partitionType;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (shouldLoop) {
|
||||||
|
unsigned i = 0;
|
||||||
|
while (i < numSamples) {
|
||||||
|
int wrappedIndex = (*indices)[i] - loop.size * blockRestarts;
|
||||||
|
if (wrappedIndex > loop.end) {
|
||||||
|
wrappedIndex -= loop.size;
|
||||||
|
blockRestarts += 1;
|
||||||
|
loop_.restarts += 1;
|
||||||
|
}
|
||||||
|
(*indices)[i] = wrappedIndex;
|
||||||
|
const bool wrapped = wrappedIndex < oldIndex;
|
||||||
|
|
||||||
|
// identify the partition this index is in
|
||||||
|
addPartitionIfNecessary(i, wrappedIndex, wrapped);
|
||||||
|
i++;
|
||||||
|
|
||||||
|
// Break if we reached the loop count
|
||||||
|
if (wrapped && region_->loopCount && loop_.restarts >= *region_->loopCount)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < numSamples) { // In case we released within the block, continue as if it were a one-shot
|
||||||
|
(*indices)[i] -= loop.size * blockRestarts;
|
||||||
if ((*indices)[i] >= sampleEnd) {
|
if ((*indices)[i] >= sampleEnd) {
|
||||||
#ifndef NDEBUG
|
fill<int>(indices->subspan(i), sampleEnd);
|
||||||
// Check for underflow
|
fill<float>(coeffs->subspan(i), 0x1.fffffep-1);
|
||||||
if (source.getNumFrames() - 1 < currentPromise_->information.end) {
|
break;
|
||||||
DBG("[sfizz] Underflow: source available samples "
|
}
|
||||||
<< source.getNumFrames() << "/"
|
i++;
|
||||||
<< currentPromise_->information.end
|
}
|
||||||
<< " for sample " << *region_->sampleId);
|
} else { // One shots and loops that have released (for loop sustain) or ended (with loop counts)
|
||||||
}
|
for (unsigned i = 0; i < numSamples; ++i) {
|
||||||
#endif
|
(*indices)[i] -= sampleSize_ * blockRestarts;
|
||||||
if (!region_->flexAmpEG) {
|
|
||||||
egAmplitude_.setReleaseTime(0.0f);
|
if ((*indices)[i] >= sampleEnd) {
|
||||||
egAmplitude_.startRelease(i);
|
if (region_->sampleCount && count_ < *region_->sampleCount && !region_->shouldLoop()) {
|
||||||
}
|
(*indices)[i] -= sampleSize_;
|
||||||
else {
|
blockRestarts += 1;
|
||||||
// TODO(jpc): Flex AmpEG
|
count_ += 1;
|
||||||
flexEGs_[*region_->flexAmpEG]->release(i);
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!released())
|
||||||
|
off(i, true);
|
||||||
|
|
||||||
fill<int>(indices->subspan(i), sampleEnd);
|
fill<int>(indices->subspan(i), sampleEnd);
|
||||||
fill<float>(coeffs->subspan(i), 1.0f);
|
fill<float>(coeffs->subspan(i), 1.0f);
|
||||||
break;
|
break;
|
||||||
|
|
@ -1395,6 +1455,14 @@ void Voice::Impl::fillWithGenerator(AudioSpan<float> buffer) noexcept
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Voice::Impl::released() const noexcept
|
||||||
|
{
|
||||||
|
if (!region_->flexAmpEG)
|
||||||
|
return egAmplitude_.isReleased();
|
||||||
|
else
|
||||||
|
return flexEGs_[*region_->flexAmpEG]->isReleased();
|
||||||
|
}
|
||||||
|
|
||||||
bool Voice::checkOffGroup(const Region* other, int delay, int noteNumber) noexcept
|
bool Voice::checkOffGroup(const Region* other, int delay, int noteNumber) noexcept
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
|
|
@ -1419,6 +1487,7 @@ void Voice::reset() noexcept
|
||||||
impl.currentPromise_.reset();
|
impl.currentPromise_.reset();
|
||||||
impl.sourcePosition_ = 0;
|
impl.sourcePosition_ = 0;
|
||||||
impl.age_ = 0;
|
impl.age_ = 0;
|
||||||
|
impl.count_ = 1;
|
||||||
impl.floatPositionOffset_ = 0.0f;
|
impl.floatPositionOffset_ = 0.0f;
|
||||||
impl.noteIsOff_ = false;
|
impl.noteIsOff_ = false;
|
||||||
|
|
||||||
|
|
@ -1443,6 +1512,7 @@ void Voice::Impl::resetLoopInformation() noexcept
|
||||||
loop_.xfSize = 0;
|
loop_.xfSize = 0;
|
||||||
loop_.xfOutStart = 0;
|
loop_.xfOutStart = 0;
|
||||||
loop_.xfInStart = 0;
|
loop_.xfInStart = 0;
|
||||||
|
loop_.restarts = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Voice::Impl::updateLoopInformation() noexcept
|
void Voice::Impl::updateLoopInformation() noexcept
|
||||||
|
|
@ -1500,10 +1570,8 @@ bool Voice::releasedOrFree() const noexcept
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
if (impl.state_ != State::playing)
|
if (impl.state_ != State::playing)
|
||||||
return true;
|
return true;
|
||||||
if (!impl.region_->flexAmpEG)
|
|
||||||
return impl.egAmplitude_.isReleased();
|
return impl.released();
|
||||||
else
|
|
||||||
return impl.flexEGs_[*impl.region_->flexAmpEG]->isReleased();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Voice::setMaxFiltersPerVoice(size_t numFilters)
|
void Voice::setMaxFiltersPerVoice(size_t numFilters)
|
||||||
|
|
|
||||||
|
|
@ -309,7 +309,6 @@ public:
|
||||||
* @brief Release the voice after a given delay
|
* @brief Release the voice after a given delay
|
||||||
*
|
*
|
||||||
* @param delay
|
* @param delay
|
||||||
* @param fastRelease whether to do a normal release or cut the voice abruptly
|
|
||||||
*/
|
*/
|
||||||
void release(int delay) noexcept;
|
void release(int delay) noexcept;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -237,9 +237,9 @@ TEST_CASE("[Values] Count")
|
||||||
synth.dispatchMessage(client, 0, "/region1/count", "", nullptr);
|
synth.dispatchMessage(client, 0, "/region1/count", "", nullptr);
|
||||||
synth.dispatchMessage(client, 0, "/region2/count", "", nullptr);
|
synth.dispatchMessage(client, 0, "/region2/count", "", nullptr);
|
||||||
std::vector<std::string> expected {
|
std::vector<std::string> expected {
|
||||||
"/region0/count,h : { 1 }",
|
"/region0/count,N : { }",
|
||||||
"/region1/count,h : { 2 }",
|
"/region1/count,h : { 2 }",
|
||||||
"/region2/count,h : { 1 }",
|
"/region2/count,N : { }",
|
||||||
};
|
};
|
||||||
REQUIRE(messageList == expected);
|
REQUIRE(messageList == expected);
|
||||||
}
|
}
|
||||||
|
|
@ -350,6 +350,29 @@ TEST_CASE("[Values] Loop crossfade")
|
||||||
REQUIRE(messageList == expected);
|
REQUIRE(messageList == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Values] Loop count")
|
||||||
|
{
|
||||||
|
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 loop_count=2
|
||||||
|
<region> sample=kick.wav loop_count=-1
|
||||||
|
)");
|
||||||
|
synth.dispatchMessage(client, 0, "/region0/loop_count", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region1/loop_count", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region2/loop_count", "", nullptr);
|
||||||
|
std::vector<std::string> expected {
|
||||||
|
"/region0/loop_count,N : { }",
|
||||||
|
"/region1/loop_count,h : { 2 }",
|
||||||
|
"/region2/loop_count,N : { }",
|
||||||
|
};
|
||||||
|
REQUIRE(messageList == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("[Values] Group")
|
TEST_CASE("[Values] Group")
|
||||||
{
|
{
|
||||||
Synth synth;
|
Synth synth;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue