Merge pull request #898 from paulfd/release-sustain
Add the ability to cancel the release on the basic ADSR Envelope
This commit is contained in:
commit
1a8185aa27
21 changed files with 270 additions and 81 deletions
|
|
@ -122,7 +122,8 @@ void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
while (count < size) {
|
while (count < size) {
|
||||||
currentValue = std::max(sustain, currentValue + transitionDelta);
|
if (currentValue > sustain)
|
||||||
|
currentValue += transitionDelta;
|
||||||
output[count++] = currentValue;
|
output[count++] = currentValue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -173,6 +174,14 @@ void ADSREnvelope::startRelease(int releaseDelay) noexcept
|
||||||
this->releaseDelay = releaseDelay;
|
this->releaseDelay = releaseDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ADSREnvelope::cancelRelease(int delay) noexcept
|
||||||
|
{
|
||||||
|
(void)delay;
|
||||||
|
currentState = State::Sustain;
|
||||||
|
shouldRelease = false;
|
||||||
|
this->releaseDelay = -1;
|
||||||
|
}
|
||||||
|
|
||||||
void ADSREnvelope::setReleaseTime(Float timeInSeconds) noexcept
|
void ADSREnvelope::setReleaseTime(Float timeInSeconds) noexcept
|
||||||
{
|
{
|
||||||
releaseRate = secondsToExpRate(timeInSeconds);
|
releaseRate = secondsToExpRate(timeInSeconds);
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,12 @@ public:
|
||||||
* @param releaseDelay the delay before releasing in samples
|
* @param releaseDelay the delay before releasing in samples
|
||||||
*/
|
*/
|
||||||
void startRelease(int releaseDelay) noexcept;
|
void startRelease(int releaseDelay) noexcept;
|
||||||
|
/**
|
||||||
|
* @brief Cancel a release and get back into sustain.
|
||||||
|
*
|
||||||
|
* @param delay
|
||||||
|
*/
|
||||||
|
void cancelRelease(int delay) noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Is the envelope smoothing?
|
* @brief Is the envelope smoothing?
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,7 @@ FloatSpec lofiBitred { 0.0f, {0.0f, 100.0f}, 0 };
|
||||||
FloatSpec lofiDecim { 0.0f, {0.0f, 100.0f}, 0 };
|
FloatSpec lofiDecim { 0.0f, {0.0f, 100.0f}, 0 };
|
||||||
FloatSpec rectify { 0.0f, {0.0f, 100.0f}, 0 };
|
FloatSpec rectify { 0.0f, {0.0f, 100.0f}, 0 };
|
||||||
UInt32Spec stringsNumber { maxStrings, {0, maxStrings}, 0 };
|
UInt32Spec stringsNumber { maxStrings, {0, maxStrings}, 0 };
|
||||||
|
BoolSpec sustainCancelsRelease { false, {0, 1}, kEnforceBounds };
|
||||||
|
|
||||||
ESpec<Trigger> trigger { Trigger::attack, {Trigger::attack, Trigger::release_key}, 0};
|
ESpec<Trigger> trigger { Trigger::attack, {Trigger::attack, Trigger::release_key}, 0};
|
||||||
ESpec<CrossfadeCurve> crossfadeCurve { CrossfadeCurve::power, {CrossfadeCurve::gain, CrossfadeCurve::power}, 0};
|
ESpec<CrossfadeCurve> crossfadeCurve { CrossfadeCurve::power, {CrossfadeCurve::gain, CrossfadeCurve::power}, 0};
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,7 @@ namespace Default
|
||||||
extern const OpcodeSpec<SelfMask> selfMask;
|
extern const OpcodeSpec<SelfMask> selfMask;
|
||||||
extern const OpcodeSpec<FilterType> filter;
|
extern const OpcodeSpec<FilterType> filter;
|
||||||
extern const OpcodeSpec<EqType> eq;
|
extern const OpcodeSpec<EqType> eq;
|
||||||
|
extern const OpcodeSpec<bool> sustainCancelsRelease;
|
||||||
|
|
||||||
// Default/max count for objects
|
// Default/max count for objects
|
||||||
constexpr int numEQs { 3 };
|
constexpr int numEQs { 3 };
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ struct FlexEnvelope::Impl {
|
||||||
|
|
||||||
//
|
//
|
||||||
void process(absl::Span<float> out);
|
void process(absl::Span<float> out);
|
||||||
|
bool advanceToStage(unsigned stageNumber);
|
||||||
bool advanceToNextStage();
|
bool advanceToNextStage();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -114,6 +115,25 @@ void FlexEnvelope::release(unsigned releaseDelay)
|
||||||
impl.currentFramesUntilRelease_ = releaseDelay;
|
impl.currentFramesUntilRelease_ = releaseDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FlexEnvelope::cancelRelease(unsigned delay)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
const FlexEGDescription& desc = *impl.desc_;
|
||||||
|
|
||||||
|
if (impl.currentFramesUntilRelease_) {
|
||||||
|
// Cancel the future release
|
||||||
|
impl.currentFramesUntilRelease_ = absl::nullopt;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!impl.isReleased_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
impl.isReleased_ = false;
|
||||||
|
impl.advanceToStage(desc.sustain);
|
||||||
|
impl.stageTargetLevel_ = impl.currentLevel_;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned FlexEnvelope::getRemainingDelay() const noexcept
|
unsigned FlexEnvelope::getRemainingDelay() const noexcept
|
||||||
{
|
{
|
||||||
const Impl& impl = *impl_;
|
const Impl& impl = *impl_;
|
||||||
|
|
@ -226,25 +246,29 @@ void FlexEnvelope::Impl::process(absl::Span<float> out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FlexEnvelope::Impl::advanceToNextStage()
|
bool FlexEnvelope::Impl::advanceToStage(unsigned stageNumber)
|
||||||
{
|
{
|
||||||
const FlexEGDescription& desc = *desc_;
|
const FlexEGDescription& desc = *desc_;
|
||||||
|
|
||||||
unsigned nextStageNo = currentStageNumber_ + 1;
|
currentStageNumber_ = stageNumber;
|
||||||
currentStageNumber_ = nextStageNo;
|
|
||||||
|
|
||||||
if (nextStageNo >= desc.points.size())
|
if (stageNumber >= desc.points.size())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const FlexEGPoint& point = desc.points[nextStageNo];
|
const FlexEGPoint& point = desc.points[stageNumber];
|
||||||
stageSourceLevel_ = currentLevel_;
|
stageSourceLevel_ = currentLevel_;
|
||||||
stageTargetLevel_ = point.level;
|
stageTargetLevel_ = point.level;
|
||||||
stageTime_ = point.time;
|
stageTime_ = point.time;
|
||||||
stageSustained_ = int(nextStageNo) == desc.sustain;
|
stageSustained_ = int(stageNumber) == desc.sustain;
|
||||||
stageCurve_ = &point.curve();
|
stageCurve_ = &point.curve();
|
||||||
|
|
||||||
currentTime_ = 0;
|
currentTime_ = 0;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool FlexEnvelope::Impl::advanceToNextStage()
|
||||||
|
{
|
||||||
|
return advanceToStage(currentStageNumber_ + 1);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,11 @@ public:
|
||||||
*/
|
*/
|
||||||
void release(unsigned releaseDelay);
|
void release(unsigned releaseDelay);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Cancel the release
|
||||||
|
*/
|
||||||
|
void cancelRelease(unsigned delay);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Get the remaining delay samples
|
Get the remaining delay samples
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -437,6 +437,12 @@ void Synth::Impl::handleControlOpcodes(const std::vector<Opcode>& members)
|
||||||
DBG("Unsupported value for hint_stealing: " << member.value);
|
DBG("Unsupported value for hint_stealing: " << member.value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case hash("hint_sustain_cancels_release"):
|
||||||
|
{
|
||||||
|
SynthConfig& config = resources_.getSynthConfig();
|
||||||
|
config.sustainCancelsRelease = member.read(Default::sustainCancelsRelease);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// Unsupported control opcode
|
// Unsupported control opcode
|
||||||
DBG("Unsupported control opcode: " << member.name);
|
DBG("Unsupported control opcode: " << member.name);
|
||||||
|
|
|
||||||
|
|
@ -28,5 +28,7 @@ struct SynthConfig
|
||||||
{
|
{
|
||||||
return freeWheeling ? freeWheelingOscillatorQuality : liveOscillatorQuality;
|
return freeWheeling ? freeWheelingOscillatorQuality : liveOscillatorQuality;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool sustainCancelsRelease { Default::sustainCancelsRelease };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -676,6 +676,13 @@ void Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept
|
||||||
if (impl.noteIsOff_ && region.loopMode != LoopMode::one_shot
|
if (impl.noteIsOff_ && region.loopMode != LoopMode::one_shot
|
||||||
&& sostenutoPedalReleaseCondition && sustainPedalReleaseCondition)
|
&& sostenutoPedalReleaseCondition && sustainPedalReleaseCondition)
|
||||||
release(delay);
|
release(delay);
|
||||||
|
|
||||||
|
if (region.checkSustain && (impl.sustainState_ == Impl::SustainState::Sustaining)
|
||||||
|
&& impl.resources_.getSynthConfig().sustainCancelsRelease
|
||||||
|
&& impl.released() && (region.trigger != Trigger::release && region.trigger != Trigger::release_key) ) {
|
||||||
|
ModMatrix& modMatrix = impl.resources_.getModMatrix();
|
||||||
|
modMatrix.cancelRelease(impl.id_, impl.region_->getId(), delay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Voice::registerPitchWheel(int delay, float pitch) noexcept
|
void Voice::registerPitchWheel(int delay, float pitch) noexcept
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,15 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) { (void)sourceKey; (void)voiceId; (void)delay; }
|
virtual void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) { (void)sourceKey; (void)voiceId; (void)delay; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cancel the release and get back into sustain
|
||||||
|
*
|
||||||
|
* @param sourceKey identifier of the source to release
|
||||||
|
* @param voiceId the particular voice to initialize, if per-voice
|
||||||
|
* @param delay the frame time when it happens
|
||||||
|
*/
|
||||||
|
virtual void cancelRelease(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) { (void)sourceKey; (void)voiceId; (void)delay; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Generate a cycle of the modulator
|
* @brief Generate a cycle of the modulator
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -281,6 +281,19 @@ void ModMatrix::releaseVoice(NumericId<Voice> voiceId, NumericId<Region> regionI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModMatrix::cancelRelease(NumericId<Voice> voiceId, NumericId<Region> regionId, unsigned delay)
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
|
||||||
|
ASSERT(regionId);
|
||||||
|
|
||||||
|
const auto idNumber = static_cast<size_t>(regionId.number());
|
||||||
|
for (auto idx: impl.sourceIndicesForRegion_[idNumber]) {
|
||||||
|
const Impl::Source& source = impl.sources_[idx];
|
||||||
|
source.gen->cancelRelease(source.key, voiceId, delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ModMatrix::beginCycle(unsigned numFrames)
|
void ModMatrix::beginCycle(unsigned numFrames)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,11 @@ public:
|
||||||
*/
|
*/
|
||||||
void releaseVoice(NumericId<Voice> voiceId, NumericId<Region> regionId, unsigned delay);
|
void releaseVoice(NumericId<Voice> voiceId, NumericId<Region> regionId, unsigned delay);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cancel release for a given voice.
|
||||||
|
*/
|
||||||
|
void cancelRelease(NumericId<Voice> voiceId, NumericId<Region> regionId, unsigned delay);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Start modulation processing for the entire cycle.
|
* @brief Start modulation processing for the entire cycle.
|
||||||
* This clears all the buffers.
|
* This clears all the buffers.
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,52 @@ ADSREnvelopeSource::ADSREnvelopeSource(VoiceManager& manager, MidiState& state)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ADSREnvelope* getEG(Voice* voice, const ModKey& key)
|
||||||
|
{
|
||||||
|
ADSREnvelope* eg = nullptr;
|
||||||
|
if (!voice)
|
||||||
|
return eg;
|
||||||
|
|
||||||
|
switch (key.id()) {
|
||||||
|
case ModId::AmpEG:
|
||||||
|
eg = voice->getAmplitudeEG();
|
||||||
|
break;
|
||||||
|
case ModId::PitchEG:
|
||||||
|
eg = voice->getPitchEG();
|
||||||
|
break;
|
||||||
|
case ModId::FilEG:
|
||||||
|
eg = voice->getFilterEG();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return eg;
|
||||||
|
}
|
||||||
|
|
||||||
|
return eg;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EGDescription* getEGDescription(const Region* region, const ModKey& key)
|
||||||
|
{
|
||||||
|
const EGDescription* desc = nullptr;
|
||||||
|
if (!region)
|
||||||
|
return desc;
|
||||||
|
|
||||||
|
switch (key.id()) {
|
||||||
|
case ModId::AmpEG:
|
||||||
|
desc = ®ion->amplitudeEG;
|
||||||
|
break;
|
||||||
|
case ModId::PitchEG:
|
||||||
|
desc = &*region->pitchEG;
|
||||||
|
break;
|
||||||
|
case ModId::FilEG:
|
||||||
|
desc = &*region->filterEG;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||||
{
|
{
|
||||||
Voice* voice = voiceManager_.getVoiceById(voiceId);
|
Voice* voice = voiceManager_.getVoiceById(voiceId);
|
||||||
|
|
@ -27,29 +73,9 @@ void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId,
|
||||||
}
|
}
|
||||||
|
|
||||||
const Region* region = voice->getRegion();
|
const Region* region = voice->getRegion();
|
||||||
ADSREnvelope* eg = nullptr;
|
ADSREnvelope* eg = getEG(voice, sourceKey);
|
||||||
const EGDescription* desc = nullptr;
|
const EGDescription* desc = getEGDescription(region, sourceKey);
|
||||||
|
ASSERT(eg);
|
||||||
switch (sourceKey.id()) {
|
|
||||||
case ModId::AmpEG:
|
|
||||||
eg = voice->getAmplitudeEG();
|
|
||||||
ASSERT(eg);
|
|
||||||
desc = ®ion->amplitudeEG;
|
|
||||||
break;
|
|
||||||
case ModId::PitchEG:
|
|
||||||
eg = voice->getPitchEG();
|
|
||||||
ASSERT(eg);
|
|
||||||
desc = &*region->pitchEG;
|
|
||||||
break;
|
|
||||||
case ModId::FilEG:
|
|
||||||
eg = voice->getFilterEG();
|
|
||||||
ASSERT(eg);
|
|
||||||
desc = &*region->filterEG;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ASSERTFALSE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TriggerEvent& triggerEvent = voice->getTriggerEvent();
|
const TriggerEvent& triggerEvent = voice->getTriggerEvent();
|
||||||
const float sampleRate = voice->getSampleRate();
|
const float sampleRate = voice->getSampleRate();
|
||||||
|
|
@ -64,27 +90,24 @@ void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voice
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ADSREnvelope* eg = nullptr;
|
ADSREnvelope* eg = getEG(voice, sourceKey);
|
||||||
|
ASSERT(eg);
|
||||||
|
|
||||||
switch (sourceKey.id()) {
|
eg->startRelease(delay);
|
||||||
case ModId::AmpEG:
|
}
|
||||||
eg = voice->getAmplitudeEG();
|
|
||||||
ASSERT(eg);
|
void ADSREnvelopeSource::cancelRelease(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||||
break;
|
{
|
||||||
case ModId::PitchEG:
|
Voice* voice = voiceManager_.getVoiceById(voiceId);
|
||||||
eg = voice->getPitchEG();
|
if (!voice) {
|
||||||
ASSERT(eg);
|
|
||||||
break;
|
|
||||||
case ModId::FilEG:
|
|
||||||
eg = voice->getFilterEG();
|
|
||||||
ASSERT(eg);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
eg->startRelease(delay);
|
ADSREnvelope* eg = getEG(voice, sourceKey);
|
||||||
|
ASSERT(eg);
|
||||||
|
|
||||||
|
eg->cancelRelease(delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
||||||
|
|
@ -95,25 +118,8 @@ void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId<Voice> voic
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ADSREnvelope* eg = nullptr;
|
ADSREnvelope* eg = getEG(voice, sourceKey);
|
||||||
|
ASSERT(eg);
|
||||||
switch (sourceKey.id()) {
|
|
||||||
case ModId::AmpEG:
|
|
||||||
eg = voice->getAmplitudeEG();
|
|
||||||
ASSERT(eg);
|
|
||||||
break;
|
|
||||||
case ModId::PitchEG:
|
|
||||||
eg = voice->getPitchEG();
|
|
||||||
ASSERT(eg);
|
|
||||||
break;
|
|
||||||
case ModId::FilEG:
|
|
||||||
eg = voice->getFilterEG();
|
|
||||||
ASSERT(eg);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ASSERTFALSE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
eg->getBlock(buffer);
|
eg->getBlock(buffer);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ public:
|
||||||
explicit ADSREnvelopeSource(VoiceManager &manager, MidiState& state);
|
explicit ADSREnvelopeSource(VoiceManager &manager, MidiState& state);
|
||||||
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
|
void cancelRelease(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,6 @@ void ChannelAftertouchSource::init(const ModKey& sourceKey, NumericId<Voice> voi
|
||||||
UNUSED(delay);
|
UNUSED(delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelAftertouchSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
|
||||||
{
|
|
||||||
UNUSED(sourceKey);
|
|
||||||
UNUSED(voiceId);
|
|
||||||
UNUSED(delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChannelAftertouchSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
void ChannelAftertouchSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
||||||
{
|
{
|
||||||
UNUSED(sourceKey);
|
UNUSED(sourceKey);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ class ChannelAftertouchSource : public ModGenerator {
|
||||||
public:
|
public:
|
||||||
explicit ChannelAftertouchSource(VoiceManager &manager, MidiState& state);
|
explicit ChannelAftertouchSource(VoiceManager &manager, MidiState& state);
|
||||||
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
|
||||||
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,26 @@ void FlexEnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voice
|
||||||
eg->release(delay);
|
eg->release(delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FlexEnvelopeSource::cancelRelease(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||||
|
{
|
||||||
|
unsigned egIndex = sourceKey.parameters().N;
|
||||||
|
|
||||||
|
Voice* voice = voiceManager_.getVoiceById(voiceId);
|
||||||
|
if (!voice) {
|
||||||
|
ASSERTFALSE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Region* region = voice->getRegion();
|
||||||
|
if (egIndex >= region->flexEGs.size()) {
|
||||||
|
ASSERTFALSE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FlexEnvelope* eg = voice->getFlexEG(egIndex);
|
||||||
|
eg->cancelRelease(delay);
|
||||||
|
}
|
||||||
|
|
||||||
void FlexEnvelopeSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
void FlexEnvelopeSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
||||||
{
|
{
|
||||||
unsigned egIndex = sourceKey.parameters().N;
|
unsigned egIndex = sourceKey.parameters().N;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ public:
|
||||||
explicit FlexEnvelopeSource(VoiceManager& manager);
|
explicit FlexEnvelopeSource(VoiceManager& manager);
|
||||||
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
|
void cancelRelease(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,6 @@ void PolyAftertouchSource::init(const ModKey& sourceKey, NumericId<Voice> voiceI
|
||||||
UNUSED(delay);
|
UNUSED(delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PolyAftertouchSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
|
||||||
{
|
|
||||||
UNUSED(sourceKey);
|
|
||||||
UNUSED(voiceId);
|
|
||||||
UNUSED(delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PolyAftertouchSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
void PolyAftertouchSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
||||||
{
|
{
|
||||||
UNUSED(sourceKey);
|
UNUSED(sourceKey);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ class PolyAftertouchSource : public ModGenerator {
|
||||||
public:
|
public:
|
||||||
explicit PolyAftertouchSource(VoiceManager &manager, MidiState& state);
|
explicit PolyAftertouchSource(VoiceManager &manager, MidiState& state);
|
||||||
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
|
||||||
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -1829,6 +1829,95 @@ TEST_CASE("[Synth] Short empty files are turned into *silence")
|
||||||
REQUIRE(messageList == expected);
|
REQUIRE(messageList == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Sustain cancels release (Flex EG)")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||||
|
<control> hint_sustain_cancels_release=1
|
||||||
|
<region> sample=*sine
|
||||||
|
eg01_ampeg=1 eg01_sustain=2
|
||||||
|
eg01_time1=0 eg01_level1=0.00
|
||||||
|
eg01_time2=0.06 eg01_level2=0.92
|
||||||
|
eg01_time3=1.00 eg01_level3=0.00 eg01_shape3=-3
|
||||||
|
)");
|
||||||
|
synth.noteOn(0, 60, 63 );
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( playingSamples(synth) == std::vector<std::string> { "*sine" } );
|
||||||
|
synth.noteOff(0, 60, 0 );
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( playingSamples(synth) == std::vector<std::string> { } );
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.cc(0, 64, 127);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( playingSamples(synth) == std::vector<std::string> { "*sine" } );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Sustain cancels release (Flex EG) is off by default")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||||
|
<region> sample=*sine
|
||||||
|
eg01_ampeg=1 eg01_sustain=2
|
||||||
|
eg01_time1=0 eg01_level1=0.00
|
||||||
|
eg01_time2=0.06 eg01_level2=0.92
|
||||||
|
eg01_time3=1.00 eg01_level3=0.00 eg01_shape3=-3
|
||||||
|
)");
|
||||||
|
synth.noteOn(0, 60, 63 );
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( playingSamples(synth) == std::vector<std::string> { "*sine" } );
|
||||||
|
synth.noteOff(0, 60, 0 );
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( playingSamples(synth) == std::vector<std::string> { } );
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.cc(0, 64, 127);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( playingSamples(synth) == std::vector<std::string> { } );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Sustain cancels release")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||||
|
<control> hint_sustain_cancels_release=1
|
||||||
|
<region> sample=*sine ampeg_release=10
|
||||||
|
)");
|
||||||
|
synth.noteOn(0, 60, 63 );
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( playingSamples(synth) == std::vector<std::string> { "*sine" } );
|
||||||
|
synth.noteOff(0, 60, 0 );
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( playingSamples(synth) == std::vector<std::string> { } );
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.cc(0, 64, 127);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( playingSamples(synth) == std::vector<std::string> { "*sine" } );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Sustain cancels release is off by default")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||||
|
<region> sample=*sine ampeg_release=10
|
||||||
|
)");
|
||||||
|
synth.noteOn(0, 60, 63 );
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( playingSamples(synth) == std::vector<std::string> { "*sine" } );
|
||||||
|
synth.noteOff(0, 60, 0 );
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( playingSamples(synth) == std::vector<std::string> { } );
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.cc(0, 64, 127);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( playingSamples(synth) == std::vector<std::string> { } );
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("[Synth] Resets all controllers to default values")
|
TEST_CASE("[Synth] Resets all controllers to default values")
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue