Skip to content

Commit

Permalink
Porta-during-release modes (Experimental/Alpha) (#207)
Browse files Browse the repository at this point in the history
This feature allows portamento to extend over
a release period. Mark it experimental since it may
still change before 1.1 and lots of folks are on the nightly
 right now

Addresses #177
  • Loading branch information
baconpaul authored Feb 2, 2025
1 parent a345434 commit c5edd65
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 1 deletion.
13 changes: 12 additions & 1 deletion src/synth/patch.h
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,15 @@ struct Patch : pats::PatchBase<Patch, Param>
.withLog2SecondsFormatting()
.withMilisecondsBelowOneSecond()
.withCustomMinDisplay("Off")),
portaContinuation(intMd()
.withID(id(43))
.withName(name() + " Porta Continuation")
.withRange(0, 2)
.withDefault(0)
.withGroupName(name())
.withUnorderedMapFormatting(
{{0, "OnVoice"}, {1, "FreeRun"}, {2, "GateRun"}})),

pianoModeActive(md_t()
.asBool()
.withName(name() + " Piano Mode Active")
Expand Down Expand Up @@ -1300,7 +1309,8 @@ struct Patch : pats::PatchBase<Patch, Param>
uint32_t id(int f) const { return idBase + f; }

Param level, velSensitivity, playMode;
Param bendUp, bendDown, polyLimit, defaultTrigger, portaTime, pianoModeActive;
Param bendUp, bendDown, polyLimit, defaultTrigger, portaTime, portaContinuation,
pianoModeActive;
Param unisonCount, unisonSpread, uniPhaseRand, unisonPan;
Param mpeActive, mpeBendRange;
Param octTranspose, fineTune, pan, lfoDepth;
Expand All @@ -1319,6 +1329,7 @@ struct Patch : pats::PatchBase<Patch, Param>
&polyLimit,
&defaultTrigger,
&portaTime,
&portaContinuation,
&pianoModeActive,
&unisonCount,
&unisonSpread,
Expand Down
28 changes: 28 additions & 0 deletions src/synth/synth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,17 @@ template <bool multiOut> void Synth::processInternal(const clap_output_events_t
loops++;
lagHandler.process();

if (portaContinuation.updateEveryBlock && portaContinuation.active)
{
portaContinuation.portaFrac += portaContinuation.dPortaFrac;
portaContinuation.sourceKey += portaContinuation.dKey;
if (portaContinuation.portaFrac >= 1.0)
{
portaContinuation.active = false;
portaContinuation.updateEveryBlock = false;
}
}

float lOutput alignas(16)[2 * (1 + (multiOut ? numOps : 0))][blockSize];
memset(lOutput, 0, sizeof(lOutput));

Expand Down Expand Up @@ -435,6 +446,23 @@ void Synth::addToVoiceList(Voice *v)

Voice *Synth::removeFromVoiceList(Voice *cvoice)
{
if (patch.output.portaContinuation.value > 0.5 && voiceCount == 1 &&
cvoice->voiceValues.portaDiff > 1e-5)
{
portaContinuation.sourceKey =
cvoice->voiceValues.key + cvoice->voiceValues.portaDiff * cvoice->voiceValues.portaSign;
portaContinuation.portaFrac = cvoice->voiceValues.portaFrac;
portaContinuation.dPortaFrac = cvoice->voiceValues.dPortaFrac;
portaContinuation.dKey = -cvoice->voiceValues.dPorta * cvoice->voiceValues.portaSign;

portaContinuation.active = true;
portaContinuation.updateEveryBlock =
(int)std::round(patch.output.portaContinuation.value) == 1;
}
else
{
portaContinuation.active = false;
}
if (cvoice->prior)
{
cvoice->prior->next = cvoice->next;
Expand Down
21 changes: 21 additions & 0 deletions src/synth/synth.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ struct Synth
void dumpVoiceList();
int voiceCount{0};

struct PortaContinuation
{
bool active{false};
bool updateEveryBlock{false};
float sourceKey{0.f};
float dKey{0.f};
float portaFrac{0.f};
float dPortaFrac{0.f};
} portaContinuation;

struct VMResponder
{
Synth &synth;
Expand Down Expand Up @@ -186,6 +196,13 @@ struct Synth
synth.voices[i].voiceValues.rephaseOnRetrigger = (!upr && prt);
synth.voices[i].voiceValues.noteExpressionTuningInSemis = 0;
synth.voices[i].voiceValues.noteExpressionPanBipolar = 0;

if (synth.portaContinuation.active)
{
synth.voices[i].restartPortaTo(synth.portaContinuation.sourceKey,
key, synth.patch.output.portaTime,
synth.portaContinuation.portaFrac);
}
synth.voices[i].attack();

synth.addToVoiceList(&synth.voices[i]);
Expand All @@ -197,6 +214,10 @@ struct Synth
}
}
}
// If there is a porta continuation we dealt with it
if (ct > 0)
synth.portaContinuation.active = false;

return made;
}
void releaseVoice(Voice *v, float rv)
Expand Down
24 changes: 24 additions & 0 deletions src/synth/voice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,4 +293,28 @@ void Voice::setupPortaTo(uint16_t newKey, float log2Time)
voiceValues.portaSign = sign;
}

void Voice::restartPortaTo(float sourceKey, uint16_t newKey, float log2Time, float portaFrac)
{
if (log2Time < -8 + 1e-5)
{
voiceValues.portaSign = 0;
voiceValues.portaDiff = 0;
voiceValues.dPorta = 0;
voiceValues.portaFrac = 0;
voiceValues.dPortaFrac = 0;
return;
}
auto blocks = monoValues.twoToTheX.twoToThe(log2Time) * monoValues.sr.sampleRate / blockSize;

auto sign = 0;
sign = newKey > sourceKey ? -1 : 1; // we are headed towards the source so set sign properly

voiceValues.portaDiff = std::abs(sourceKey - newKey);

voiceValues.dPorta = voiceValues.portaDiff / (blocks * (1 - portaFrac));
voiceValues.dPortaFrac = 1.0 / blocks;
voiceValues.portaFrac = portaFrac;
voiceValues.portaSign = sign;
}

} // namespace baconpaul::six_sines
1 change: 1 addition & 0 deletions src/synth/voice.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ struct Voice
void retriggerAllEnvelopesForKeyPress();
void retriggerAllEnvelopesForReGate();
void setupPortaTo(uint16_t newKey, float log2Seconds);
void restartPortaTo(float sourceKey, uint16_t newKey, float log2Seconds, float portaFrac);

std::array<MixerNode, numOps> mixerNode;
static constexpr int32_t fadeOverBlocks{32};
Expand Down
69 changes: 69 additions & 0 deletions src/ui/playmode-sub-panel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,27 @@ PlayModeSubPanel::PlayModeSubPanel(SixSinesEditor &e) : HasEditor(e)
portaTime->setEnabled(false);
portaL->setEnabled(false);

portaContinuationButton = std::make_unique<jcmp::TextPushButton>();
portaContinuationButton->setOnCallback(
[w = juce::Component::SafePointer(this)]()
{
if (w)
{
w->showPortaContinuationMenu();
}
});
setPortaContinuationLabel();

editor.componentRefreshByID[on.portaContinuation.meta.id] =
[w = juce::Component::SafePointer(this)]()
{
if (w)
{
w->setPortaContinuationLabel();
}
};
addAndMakeVisible(*portaContinuationButton);

auto op = [w = juce::Component::SafePointer(this)]()
{
if (w)
Expand Down Expand Up @@ -203,6 +224,7 @@ void PlayModeSubPanel::resized()
pml.add(jlo::Component(*pianoModeButton).withHeight(uicLabelHeight));
pml.add(jlo::Component(*portaL).withHeight(uicLabelHeight));
pml.add(jlo::Component(*portaTime).withHeight(uicLabelHeight).insetBy(0, 2));
pml.add(jlo::Component(*portaContinuationButton).withHeight(uicLabelHeight));
lo.add(pml);

// Unison Controls
Expand Down Expand Up @@ -306,12 +328,59 @@ void PlayModeSubPanel::showTriggerButtonMenu()
makeMenuAccessibleButtonCB(triggerButton.get()));
}

void PlayModeSubPanel::setPortaContinuationLabel()
{
auto v = (int)std::round(editor.patchCopy.output.portaContinuation.value);
switch (v)
{
case 0:
portaContinuationButton->setLabel("OnVoice");
break;
case 1:
portaContinuationButton->setLabel("FreeRun");
break;
case 2:
portaContinuationButton->setLabel("GateRun");
break;
}
}

void PlayModeSubPanel::showPortaContinuationMenu()
{
auto tmv = (int)std::round(editor.patchCopy.output.portaContinuation.value);

auto genSet = [w = juce::Component::SafePointer(this)](int nv)
{
auto that = w;
return [nv, that]()
{
auto pid = that->editor.patchCopy.output.portaContinuation.meta.id;
that->editor.patchCopy.paramMap.at(pid)->value = nv;
that->setPortaContinuationLabel();

that->editor.uiToAudio.push({Synth::UIToAudioMsg::Action::SET_PARAM, pid, (float)nv});
that->editor.flushOperator();
};
};
auto p = juce::PopupMenu();
p.addSectionHeader("Portamento Continuation");
p.addSectionHeader("Experimental; May change before 1.1");
p.addSeparator();
p.addItem("Restart Porta on New Voice", true, tmv == 0, genSet(0));
p.addItem("Continue Porta While Released", true, tmv == 1, genSet(1));
p.addItem("Suspend Porta While Released", true, tmv == 2, genSet(2));

p.showMenuAsync(juce::PopupMenu::Options().withParentComponent(&this->editor),
makeMenuAccessibleButtonCB(portaContinuationButton.get()));
}

void PlayModeSubPanel::setEnabledState()
{
auto vm = editor.patchCopy.output.playMode.value;
auto en = vm > 0.5;
portaL->setEnabled(en);
portaTime->setEnabled(en);
portaContinuationButton->setEnabled(en);
pianoModeButton->setEnabled(!en);

auto uc = editor.patchCopy.output.unisonCount.value;
Expand Down
4 changes: 4 additions & 0 deletions src/ui/playmode-sub-panel.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ struct PlayModeSubPanel : juce::Component, HasEditor
std::unique_ptr<jcmp::HSliderFilled> portaTime;
std::unique_ptr<PatchContinuous> portaTimeD;
std::unique_ptr<jcmp::Label> portaL;
std::unique_ptr<jcmp::TextPushButton> portaContinuationButton;
void setPortaContinuationLabel();
void showPortaContinuationMenu();

void setEnabledState();

std::unique_ptr<jcmp::JogUpDownButton> uniCt;
Expand Down

0 comments on commit c5edd65

Please sign in to comment.