diff --git a/src/common/SurgePatch.cpp b/src/common/SurgePatch.cpp index 7f61037349e..7b91d7fe33b 100644 --- a/src/common/SurgePatch.cpp +++ b/src/common/SurgePatch.cpp @@ -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; + } } } } @@ -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) { @@ -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) diff --git a/src/common/SurgeStorage.h b/src/common/SurgeStorage.h index 48e1daa7ad2..9a3b6a80256 100644 --- a/src/common/SurgeStorage.h +++ b/src/common/SurgeStorage.h @@ -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 @@ -487,6 +488,12 @@ enum MonoVoicePriorityMode ALWAYS_LOWEST, }; +enum MonoVoiceEnvelopeMode +{ + RESTART_FROM_ZERO, + RESTART_FROM_LATEST +}; + struct MidiKeyState { int keystate; @@ -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; diff --git a/src/common/SurgeSynthesizer.cpp b/src/common/SurgeSynthesizer.cpp index 7a2f4a6ef18..650ed8f3286 100644 --- a/src/common/SurgeSynthesizer.cpp +++ b/src/common/SurgeSynthesizer.cpp @@ -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; } @@ -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; @@ -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(); } @@ -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); @@ -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 @@ -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; @@ -896,6 +912,8 @@ void SurgeSynthesizer::playVoice(int scene, char channel, char key, char velocit if (createVoice) { list::const_iterator iter; + + float aegStart{0.}, fegStart{0.}; for (iter = voices[scene].begin(); iter != voices[scene].end(); iter++) { SurgeVoice *v = *iter; @@ -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) @@ -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 diff --git a/src/common/dsp/SurgeVoice.cpp b/src/common/dsp/SurgeVoice.cpp index 0f19630e201..b5bf3e4a97a 100644 --- a/src/common/dsp/SurgeVoice.cpp +++ b/src/common/dsp/SurgeVoice.cpp @@ -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 @@ -327,8 +328,8 @@ SurgeVoice::SurgeVoice(SurgeStorage *storage, SurgeSceneStorage *oscene, pdata * applyModulationToLocalcopy(); - ampEGSource.attack(); - filterEGSource.attack(); + ampEGSource.attackFrom(aegStart); + filterEGSource.attackFrom(fegStart); for (int i = 0; i < n_lfos_voice; i++) { diff --git a/src/common/dsp/SurgeVoice.h b/src/common/dsp/SurgeVoice.h index bb381aa6226..f7f10ec1d5d 100644 --- a/src/common/dsp/SurgeVoice.h +++ b/src/common/dsp/SurgeVoice.h @@ -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(); @@ -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); diff --git a/src/common/dsp/modulators/ADSRModulationSource.h b/src/common/dsp/modulators/ADSRModulationSource.h index a0ef00a7ffc..71e6a49e870 100644 --- a/src/common/dsp/modulators/ADSRModulationSource.h +++ b/src/common/dsp/modulators/ADSRModulationSource.h @@ -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; diff --git a/src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp b/src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp index a62c6bdbe45..614542430b9 100644 --- a/src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp +++ b/src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp @@ -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 labels = {"Last", "High", "Low", "Legacy"}; - std::vector vals = { - ALWAYS_LATEST, ALWAYS_HIGHEST, ALWAYS_LOWEST, - NOTE_ON_LATEST_RETRIGGER_HIGHEST}; + { + std::vector labels = {"Last", "High", "Low", "Legacy"}; + std::vector 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 labels = {"Zero", "Current"}; + std::vector 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(