Handle the different types of looping
This commit is contained in:
parent
f2a79723a7
commit
4a3c1f36e5
2 changed files with 139 additions and 88 deletions
|
|
@ -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;
|
||||||
|
|
||||||
|
|
@ -450,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 {
|
||||||
|
|
@ -884,8 +918,9 @@ 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()
|
const bool hasLoopSamples = static_cast<size_t>(loop.end) < source.getNumFrames();
|
||||||
&& (static_cast<size_t>(loop.end) < source.getNumFrames());
|
const bool loopContinuous = hasLoopSamples && (region_->loopMode == LoopMode::loop_continuous);
|
||||||
|
const bool loopSustain = hasLoopSamples && (region_->loopMode == LoopMode::loop_sustain) && !released();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
loop start loop end
|
loop start loop end
|
||||||
|
|
@ -907,13 +942,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 (loopSustain || loopContinuous) {
|
||||||
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)
|
||||||
|
|
@ -923,87 +952,104 @@ 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 {};
|
|
||||||
int blockRestarts { 0 };
|
|
||||||
for (unsigned i = 0; i < numSamples; ++i) {
|
|
||||||
int index = (*indices)[i] - loop.size * blockRestarts;
|
|
||||||
int wrappedIndex = index;
|
|
||||||
// wrap indices post loop-entry around the loop segment and increment the counter if necessary
|
|
||||||
if (index > loop.end) {
|
|
||||||
if (region_->loopCount && loop_.restarts >= *region_->loopCount) {
|
|
||||||
wrappedIndex = loop.end;
|
|
||||||
egAmplitude_.setReleaseTime(0.0f);
|
|
||||||
egAmplitude_.startRelease(i);
|
|
||||||
} else {
|
|
||||||
wrappedIndex -= loop.size;
|
|
||||||
blockRestarts += 1;
|
|
||||||
loop_.restarts += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(*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;
|
||||||
|
|
||||||
int blockRestarts { 0 };
|
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 (loopContinuous) {
|
||||||
|
|
||||||
for (unsigned i = 0; i < numSamples; ++i) {
|
for (unsigned i = 0; i < numSamples; ++i) {
|
||||||
ASSERT((*indices)[i] - sampleSize_ * blockRestarts >= 0);
|
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;
|
||||||
|
addPartitionIfNecessary(i, wrappedIndex, wrapped);
|
||||||
|
|
||||||
|
// Release if we reached the loop count
|
||||||
|
if (wrapped && region_->loopCount && loop_.restarts >= *region_->loopCount && !released()) {
|
||||||
|
release(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (loopSustain) {
|
||||||
|
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++;
|
||||||
|
|
||||||
|
// Release if we reached the loop count and break
|
||||||
|
if (wrapped && region_->loopCount && loop_.restarts >= *region_->loopCount) {
|
||||||
|
release(i - 1);
|
||||||
|
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) {
|
||||||
|
fill<int>(indices->subspan(i), sampleEnd);
|
||||||
|
fill<float>(coeffs->subspan(i), 1.0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} else { // One shots and loop_sustain that have ended
|
||||||
|
for (unsigned i = 0; i < numSamples; ++i) {
|
||||||
(*indices)[i] -= sampleSize_ * blockRestarts;
|
(*indices)[i] -= sampleSize_ * blockRestarts;
|
||||||
|
|
||||||
if ((*indices)[i] >= sampleEnd) {
|
if ((*indices)[i] >= sampleEnd) {
|
||||||
#ifndef NDEBUG
|
|
||||||
// Check for underflow
|
|
||||||
if (source.getNumFrames() - 1 < currentPromise_->information.end) {
|
|
||||||
DBG("[sfizz] Underflow: source available samples "
|
|
||||||
<< source.getNumFrames() << "/"
|
|
||||||
<< currentPromise_->information.end
|
|
||||||
<< " for sample " << *region_->sampleId);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (region_->sampleCount && count_ < *region_->sampleCount) {
|
if (region_->sampleCount && count_ < *region_->sampleCount) {
|
||||||
(*indices)[i] -= sampleSize_;
|
(*indices)[i] -= sampleSize_;
|
||||||
ASSERT((*indices)[i] >= 0);
|
|
||||||
blockRestarts += 1;
|
blockRestarts += 1;
|
||||||
count_ += 1;
|
count_ += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!region_->flexAmpEG) {
|
release(i);
|
||||||
egAmplitude_.setReleaseTime(0.0f);
|
|
||||||
egAmplitude_.startRelease(i);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// TODO(jpc): Flex AmpEG
|
|
||||||
flexEGs_[*region_->flexAmpEG]->release(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
@ -1421,6 +1467,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_;
|
||||||
|
|
@ -1528,10 +1582,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;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue