Skip to content

Commit

Permalink
Mono-Legato Envelope mode
Browse files Browse the repository at this point in the history
Add a new mode for the AEG and FEG where in mono modes you
can retrigger the envelope start not from 0 / 0 phase / 0 charge
but from a phase / charge which matches the envelope levels of
the previously playing and stolen voice, if one is present (and
start from 0 otherwise).

Closes surge-synthesizer#824
  • Loading branch information
baconpaul committed Jul 12, 2022
1 parent 7242d94 commit a9c5252
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 41 deletions.
53 changes: 43 additions & 10 deletions src/common/SurgePatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1596,17 +1596,34 @@ void SurgePatch::load_xml(const void *data, int datasize, bool is_preset)
{
for (int sc = 0; sc < n_scenes; ++sc)
{
std::string mvname = "monoVoicePrority_" + std::to_string(sc);
auto *mv1 = TINYXML_SAFE_TO_ELEMENT(nonparamconfig->FirstChild(mvname.c_str()));
storage->getPatch().scene[sc].monoVoicePriorityMode = ALWAYS_LATEST;
if (mv1)
{
// Get value
int mvv;
if (mv1->QueryIntAttribute("v", &mvv) == TIXML_SUCCESS)
{
std::string mvname = "monoVoicePrority_" + std::to_string(sc);
auto *mv1 = TINYXML_SAFE_TO_ELEMENT(nonparamconfig->FirstChild(mvname.c_str()));
storage->getPatch().scene[sc].monoVoicePriorityMode = ALWAYS_LATEST;
if (mv1)
{
storage->getPatch().scene[sc].monoVoicePriorityMode =
(MonoVoicePriorityMode)mvv;
// Get value
int mvv;
if (mv1->QueryIntAttribute("v", &mvv) == TIXML_SUCCESS)
{
storage->getPatch().scene[sc].monoVoicePriorityMode =
(MonoVoicePriorityMode)mvv;
}
}
}
{
std::string mvname = "monoVoiceEnvelope_" + std::to_string(sc);
auto *mv1 = TINYXML_SAFE_TO_ELEMENT(nonparamconfig->FirstChild(mvname.c_str()));
storage->getPatch().scene[sc].monoVoiceEnvelopeMode = RESTART_FROM_ZERO;
if (mv1)
{
// Get value
int mvv;
if (mv1->QueryIntAttribute("v", &mvv) == TIXML_SUCCESS)
{
storage->getPatch().scene[sc].monoVoiceEnvelopeMode =
(MonoVoiceEnvelopeMode)mvv;
}
}
}
}
Expand Down Expand Up @@ -1883,6 +1900,14 @@ void SurgePatch::load_xml(const void *data, int datasize, bool is_preset)
polylimit.val.i = DEFAULT_POLYLIMIT;
}

if (revision < 20)
{
for (auto &sc : scene)
{
sc.monoVoiceEnvelopeMode = RESTART_FROM_ZERO;
}
}

// ensure that filtersubtype is a valid value
for (auto &sc : scene)
{
Expand Down Expand Up @@ -2621,6 +2646,14 @@ unsigned int SurgePatch::save_xml(void **data) // allocates mem, must be freed b
nonparamconfig.InsertEndChild(mvv);
}

for (int sc = 0; sc < n_scenes; ++sc)
{
std::string mvname = "monoVoiceEnvelope_" + std::to_string(sc);
TiXmlElement mvv(mvname.c_str());
mvv.SetAttribute("v", storage->getPatch().scene[sc].monoVoiceEnvelopeMode);
nonparamconfig.InsertEndChild(mvv);
}

TiXmlElement hcs("hardclipmodes");
hcs.SetAttribute("global", (int)(storage->hardclipMode));
for (int sc = 0; sc < n_scenes; ++sc)
Expand Down
12 changes: 10 additions & 2 deletions src/common/SurgeStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,12 @@ const int FIRoffsetI16 = FIRipolI16_N >> 1;
// added new Conditioner parameter (Side Low Cut)
// 17 -> 18 (XT 1.1 nightlies) added clipping options to Delay Feedback parameter (via deform)
// added Tone parameter to Phaser effect
// 18 -> 19 (XT 1.1 release) added String deform options (interpolation, bipolar Decay params, Stiffness options)
// 18 -> 19 (XT 1.1 nightlies) added String deform options (interpolation, bipolar Decay params, Stiffness options)
// added Extend to Delay Feedback parameter (allows negative delay)
// 19 -> 20 (XT 1.1 release) added voice envelope mode, but super late so don't break 19
// clang-format on

const int ff_revision = 19;
const int ff_revision = 20;

const int n_scene_params = 273;
const int n_global_params = 11 + n_fx_slots * (n_fx_params + 1); // each param plus a type
Expand Down Expand Up @@ -487,6 +488,12 @@ enum MonoVoicePriorityMode
ALWAYS_LOWEST,
};

enum MonoVoiceEnvelopeMode
{
RESTART_FROM_ZERO,
RESTART_FROM_LATEST
};

struct MidiKeyState
{
int keystate;
Expand Down Expand Up @@ -611,6 +618,7 @@ struct SurgeSceneStorage
bool modsource_doprocess[n_modsources];

MonoVoicePriorityMode monoVoicePriorityMode = ALWAYS_LATEST;
MonoVoiceEnvelopeMode monoVoiceEnvelopeMode = RESTART_FROM_ZERO;
};

const int n_stepseqsteps = 16;
Expand Down
30 changes: 27 additions & 3 deletions src/common/SurgeSynthesizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ void SurgeSynthesizer::playVoice(int scene, char channel, char key, char velocit
&storage, &storage.getPatch().scene[scene], storage.getPatch().scenedata[scene],
key, velocity, channel, scene, detune, &channelState[channel].keyState[key],
&channelState[mpeMainChannel], &channelState[channel], mpeEnabled, voiceCounter++,
host_noteid, host_originating_key, host_originating_channel);
host_noteid, host_originating_key, host_originating_channel, 0.f, 0.f);
}
break;
}
Expand Down Expand Up @@ -781,6 +781,7 @@ void SurgeSynthesizer::playVoice(int scene, char channel, char key, char velocit
{
int32_t noteIdToReuse = -1;
int16_t channelToReuse, keyToReuse;
SurgeVoice *stealEnvelopesFrom{nullptr};
for (iter = voices[scene].begin(); iter != voices[scene].end(); iter++)
{
SurgeVoice *v = *iter;
Expand All @@ -792,6 +793,13 @@ void SurgeSynthesizer::playVoice(int scene, char channel, char key, char velocit
noteIdToReuse = v->host_note_id;
channelToReuse = v->originating_host_channel;
keyToReuse = v->originating_host_key;
stealEnvelopesFrom = v;
}
else
{
// Non-gated voices only win if there's no gated voice
if (!stealEnvelopesFrom)
stealEnvelopesFrom = v;
}
v->uber_release();
}
Expand All @@ -806,6 +814,12 @@ void SurgeSynthesizer::playVoice(int scene, char channel, char key, char velocit
}

SurgeVoice *nvoice = getUnusedVoice(scene);
float aegReuse{0.f}, fegReuse{0.f};
if (stealEnvelopesFrom &&
storage.getPatch().scene[scene].monoVoiceEnvelopeMode != RESTART_FROM_ZERO)
{
stealEnvelopesFrom->getAEGFEGLevel(aegReuse, fegReuse);
}
if (nvoice)
{
int mpeMainChannel = getMpeMainChannel(channel, key);
Expand All @@ -817,7 +831,8 @@ void SurgeSynthesizer::playVoice(int scene, char channel, char key, char velocit
&storage, &storage.getPatch().scene[scene], storage.getPatch().scenedata[scene],
key, velocity, channel, scene, detune, &channelState[channel].keyState[key],
&channelState[mpeMainChannel], &channelState[channel], mpeEnabled,
voiceCounter++, host_noteid, host_originating_key, host_originating_channel);
voiceCounter++, host_noteid, host_originating_key, host_originating_channel,
aegReuse, fegReuse);
}
}
else
Expand All @@ -833,6 +848,7 @@ void SurgeSynthesizer::playVoice(int scene, char channel, char key, char velocit
case pm_mono_st:
case pm_mono_st_fp:
{
std::cout << "PM MONO ST" << std::endl;
bool found_one = false;
int primode = storage.getPatch().scene[scene].monoVoicePriorityMode;
bool createVoice = true;
Expand Down Expand Up @@ -896,6 +912,8 @@ void SurgeSynthesizer::playVoice(int scene, char channel, char key, char velocit
if (createVoice)
{
list<SurgeVoice *>::const_iterator iter;

float aegStart{0.}, fegStart{0.};
for (iter = voices[scene].begin(); iter != voices[scene].end(); iter++)
{
SurgeVoice *v = *iter;
Expand All @@ -920,7 +938,12 @@ void SurgeSynthesizer::playVoice(int scene, char channel, char key, char velocit
else
{
if (v->state.scene_id == scene)
{
if (storage.getPatch().scene[scene].monoVoiceEnvelopeMode !=
RESTART_FROM_ZERO)
v->getAEGFEGLevel(aegStart, fegStart);
v->uber_release(); // make this optional for poly legato
}
}
}
if (!found_one)
Expand All @@ -930,13 +953,14 @@ void SurgeSynthesizer::playVoice(int scene, char channel, char key, char velocit
SurgeVoice *nvoice = getUnusedVoice(scene);
if (nvoice)
{
std::cout << "SET UP FEG AEG" << std::endl;
voices[scene].push_back(nvoice);
new (nvoice) SurgeVoice(
&storage, &storage.getPatch().scene[scene],
storage.getPatch().scenedata[scene], key, velocity, channel, scene, detune,
&channelState[channel].keyState[key], &channelState[mpeMainChannel],
&channelState[channel], mpeEnabled, voiceCounter++, host_noteid,
host_originating_key, host_originating_channel);
host_originating_key, host_originating_channel, aegStart, fegStart);
}
}
else
Expand Down
7 changes: 4 additions & 3 deletions src/common/dsp/SurgeVoice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ SurgeVoice::SurgeVoice(SurgeStorage *storage, SurgeSceneStorage *oscene, pdata *
int velocity, int channel, int scene_id, float detune,
MidiKeyState *keyState, MidiChannelState *mainChannelState,
MidiChannelState *voiceChannelState, bool mpeEnabled, int64_t voiceOrder,
int32_t host_nid, int16_t host_key, int16_t host_chan)
int32_t host_nid, int16_t host_key, int16_t host_chan, float aegStart,
float fegStart)
//: fb(storage,oscene)
{
// assign pointers
Expand Down Expand Up @@ -327,8 +328,8 @@ SurgeVoice::SurgeVoice(SurgeStorage *storage, SurgeSceneStorage *oscene, pdata *

applyModulationToLocalcopy<true>();

ampEGSource.attack();
filterEGSource.attack();
ampEGSource.attackFrom(aegStart);
filterEGSource.attackFrom(fegStart);

for (int i = 0; i < n_lfos_voice; i++)
{
Expand Down
8 changes: 7 additions & 1 deletion src/common/dsp/SurgeVoice.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ class alignas(16) SurgeVoice
int velocity, int channel, int scene_id, float detune, MidiKeyState *keyState,
MidiChannelState *mainChannelState, MidiChannelState *voiceChannelState,
bool mpeEnabled, int64_t voiceOrder, int32_t host_note_id,
int16_t originating_host_key, int16_t originating_host_channel);
int16_t originating_host_key, int16_t originating_host_channel, float aegStart,
float fegStart);
~SurgeVoice();

void release();
Expand Down Expand Up @@ -169,6 +170,11 @@ class alignas(16) SurgeVoice
}
}

void getAEGFEGLevel(float &aeg, float &feg)
{
aeg = ampEGSource.get_output(0);
feg = filterEGSource.get_output(0);
}
static float channelKeyEquvialent(float key, int channel, bool isMpeEnabled,
SurgeStorage *storage, bool remapKeyForTuning = true);

Expand Down
26 changes: 23 additions & 3 deletions src/common/dsp/modulators/ADSRModulationSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,36 @@ class ADSRModulationSource : public ModulationSource
attack();
}

virtual void attack() override
virtual void attack() override { attackFrom(0.f); }

virtual void attackFrom(float start)
{
phase = 0;
output = 0;
idlecount = 0;
scalestage = 1.f;

if (start > 0)
{
output = start;
switch (lc[a_s].i)
{
case 0:
// output = sqrt(phase);
phase = output * output;
break;
case 1:
phase = output;
break;
case 2:
// output = phase * phase;
phase = sqrt(output);
break;
};
}
// Reset the analog state machine too
_v_c1 = 0.f;
_v_c1_delayed = 0.f;
_v_c1 = start;
_v_c1_delayed = start;
_discharge = 0.f;

envstate = s_attack;
Expand Down
63 changes: 44 additions & 19 deletions src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1673,29 +1673,54 @@ int32_t SurgeGUIEditor::controlModifierClicked(Surge::GUI::IComponentTagValue *c
(p->val.i == pm_mono || p->val.i == pm_mono_st ||
p->val.i == pm_mono_fp || p->val.i == pm_mono_st_fp))
{
std::vector<std::string> labels = {"Last", "High", "Low", "Legacy"};
std::vector<MonoVoicePriorityMode> vals = {
ALWAYS_LATEST, ALWAYS_HIGHEST, ALWAYS_LOWEST,
NOTE_ON_LATEST_RETRIGGER_HIGHEST};
{
std::vector<std::string> labels = {"Last", "High", "Low", "Legacy"};
std::vector<MonoVoicePriorityMode> vals = {
ALWAYS_LATEST, ALWAYS_HIGHEST, ALWAYS_LOWEST,
NOTE_ON_LATEST_RETRIGGER_HIGHEST};

contextMenu.addSectionHeader("NOTE PRIORITY");
contextMenu.addSectionHeader("NOTE PRIORITY");

for (int i = 0; i < 4; ++i)
{
bool isChecked = (vals[i] == synth->storage.getPatch()
.scene[current_scene]
.monoVoicePriorityMode);
contextMenu.addItem(Surge::GUI::toOSCase(labels[i]), true,
isChecked, [this, isChecked, vals, i]() {
synth->storage.getPatch()
.scene[current_scene]
.monoVoicePriorityMode = vals[i];
if (!isChecked)
synth->storage.getPatch().isDirty =
true;
});
for (int i = 0; i < 4; ++i)
{
bool isChecked = (vals[i] == synth->storage.getPatch()
.scene[current_scene]
.monoVoicePriorityMode);
contextMenu.addItem(Surge::GUI::toOSCase(labels[i]), true,
isChecked, [this, isChecked, vals, i]() {
synth->storage.getPatch()
.scene[current_scene]
.monoVoicePriorityMode = vals[i];
if (!isChecked)
synth->storage.getPatch().isDirty =
true;
});
}
}

{
std::vector<std::string> labels = {"Zero", "Current"};
std::vector<MonoVoiceEnvelopeMode> vals = {RESTART_FROM_ZERO,
RESTART_FROM_LATEST};

contextMenu.addSectionHeader("ENVELOPE RESTART FROM");

for (int i = 0; i < 2; ++i)
{
bool isChecked = (vals[i] == synth->storage.getPatch()
.scene[current_scene]
.monoVoiceEnvelopeMode);
contextMenu.addItem(Surge::GUI::toOSCase(labels[i]), true,
isChecked, [this, isChecked, vals, i]() {
synth->storage.getPatch()
.scene[current_scene]
.monoVoiceEnvelopeMode = vals[i];
if (!isChecked)
synth->storage.getPatch().isDirty =
true;
});
}
}
contextMenu.addSeparator();

contextMenu.addSubMenu(
Expand Down

0 comments on commit a9c5252

Please sign in to comment.