diff --git a/src/common/ModulationSource.h b/src/common/ModulationSource.h index 6e37f094447..9148ef75c31 100644 --- a/src/common/ModulationSource.h +++ b/src/common/ModulationSource.h @@ -262,6 +262,7 @@ struct ModulationRouting int destination_id; float depth; bool muted = false; + int source_index{0}; }; class ModulationSource @@ -275,13 +276,15 @@ class ModulationSource virtual void attack(){}; virtual void release(){}; virtual void reset(){}; + // override these if you support indices + virtual int get_active_outputs() { return 1; } virtual float get_output(int which) { return output; } virtual float get_output01(int which) { return output; } virtual void set_output(int which, float f) { output = f; } + virtual bool per_voice() { return false; } virtual bool is_bipolar() { return false; } virtual void set_bipolar(bool b) {} - virtual int get_num_outputs() { return 1; } protected: float output; diff --git a/src/common/SurgePatch.cpp b/src/common/SurgePatch.cpp index c93a6a1b706..6393811a7b6 100644 --- a/src/common/SurgePatch.cpp +++ b/src/common/SurgePatch.cpp @@ -1358,6 +1358,12 @@ void SurgePatch::load_xml(const void *data, int datasize, bool is_preset) else t.muted = false; + int source_index = 0; + if (mr->QueryIntAttribute("source_index", &source_index) == TIXML_SUCCESS) + t.source_index = source_index; + else + t.source_index = 0; + if (sceneId != 0) t.destination_id = paramIdInScene; else @@ -2219,6 +2225,7 @@ unsigned int SurgePatch::save_xml(void **data) // allocates mem, must be freed b mr.SetAttribute("source", r->at(b).source_id); mr.SetAttribute("depth", float_to_str(r->at(b).depth, tempstr)); mr.SetAttribute("muted", r->at(b).muted); + mr.SetAttribute("source_index", r->at(b).source_index); p.InsertEndChild(mr); } } diff --git a/src/common/SurgeStorage.cpp b/src/common/SurgeStorage.cpp index 106b55375d1..bb1af0d04a4 100644 --- a/src/common/SurgeStorage.cpp +++ b/src/common/SurgeStorage.cpp @@ -727,7 +727,7 @@ void SurgeStorage::initializePatchDb() } } -SurgePatch &SurgeStorage::getPatch() { return *_patch.get(); } +SurgePatch &SurgeStorage::getPatch() const { return *_patch.get(); } struct PEComparer { @@ -1384,6 +1384,7 @@ void SurgeStorage::clipboard_copy(int type, int scene, int entry) { ModulationRouting m; m.source_id = getPatch().scene[scene].modulation_voice[i].source_id; + m.source_index = getPatch().scene[scene].modulation_voice[i].source_index; m.depth = getPatch().scene[scene].modulation_voice[i].depth; m.destination_id = getPatch().scene[scene].modulation_voice[i].destination_id + idoffset; @@ -1395,6 +1396,7 @@ void SurgeStorage::clipboard_copy(int type, int scene, int entry) { ModulationRouting m; m.source_id = getPatch().scene[scene].modulation_scene[i].source_id; + m.source_index = getPatch().scene[scene].modulation_scene[i].source_index; m.depth = getPatch().scene[scene].modulation_scene[i].depth; m.destination_id = getPatch().scene[scene].modulation_scene[i].destination_id + idoffset; @@ -1502,6 +1504,7 @@ void SurgeStorage::clipboard_paste(int type, int scene, int entry) { ModulationRouting m; m.source_id = clipboard_modulation_voice[i].source_id; + m.source_index = clipboard_modulation_voice[i].source_index; m.depth = clipboard_modulation_voice[i].depth; m.destination_id = clipboard_modulation_voice[i].destination_id + id - n_global_params; @@ -1512,6 +1515,7 @@ void SurgeStorage::clipboard_paste(int type, int scene, int entry) { ModulationRouting m; m.source_id = clipboard_modulation_scene[i].source_id; + m.source_index = clipboard_modulation_scene[i].source_index; m.depth = clipboard_modulation_scene[i].depth; m.destination_id = clipboard_modulation_scene[i].destination_id + id - n_global_params; @@ -1540,6 +1544,7 @@ void SurgeStorage::clipboard_paste(int type, int scene, int entry) { ModulationRouting m; m.source_id = clipboard_modulation_voice[i].source_id; + m.source_index = clipboard_modulation_voice[i].source_index; m.depth = clipboard_modulation_voice[i].depth; m.destination_id = clipboard_modulation_voice[i].destination_id; getPatch().scene[scene].modulation_voice.push_back(m); @@ -1549,6 +1554,7 @@ void SurgeStorage::clipboard_paste(int type, int scene, int entry) { ModulationRouting m; m.source_id = clipboard_modulation_scene[i].source_id; + m.source_index = clipboard_modulation_scene[i].source_index; m.depth = clipboard_modulation_scene[i].depth; m.destination_id = clipboard_modulation_scene[i].destination_id; getPatch().scene[scene].modulation_scene.push_back(m); diff --git a/src/common/SurgeStorage.h b/src/common/SurgeStorage.h index 37165af1f8a..7cb5cb85054 100644 --- a/src/common/SurgeStorage.h +++ b/src/common/SurgeStorage.h @@ -949,7 +949,8 @@ class alignas(16) SurgeStorage std::unique_ptr _patch; - SurgePatch &getPatch(); + // SurgePatch &getPatch(); + SurgePatch &getPatch() const; float pitch_bend; diff --git a/src/common/SurgeSynthesizer.cpp b/src/common/SurgeSynthesizer.cpp index db3a7fb2cca..b15ae25bfb9 100644 --- a/src/common/SurgeSynthesizer.cpp +++ b/src/common/SurgeSynthesizer.cpp @@ -2263,8 +2263,9 @@ bool SurgeSynthesizer::loadFx(bool initp, bool force_reload_all) { for (auto &t : fxmodsync[s]) { - setModulation(storage.getPatch().fx[s].p[std::get<1>(t)].id, - (modsources)std::get<0>(t), std::get<2>(t)); + setModulation(storage.getPatch().fx[s].p[std::get<2>(t)].id, + (modsources)std::get<0>(t), std::get<1>(t), + std::get<3>(t)); } fxmodsync[s].clear(); fx_reload_mod[s] = false; @@ -2405,7 +2406,7 @@ bool SurgeSynthesizer::loadOscalgos() return true; } -bool SurgeSynthesizer::isValidModulation(long ptag, modsources modsource) +bool SurgeSynthesizer::isValidModulation(long ptag, modsources modsource) const { if (!modsource) return false; @@ -2435,10 +2436,13 @@ bool SurgeSynthesizer::isValidModulation(long ptag, modsources modsource) return true; } -ModulationRouting *SurgeSynthesizer::getModRouting(long ptag, modsources modsource) +std::vector SurgeSynthesizer::getModulationIndicesBetween(long ptag, + modsources modsource) const { + std::vector res; + if (!isValidModulation(ptag, modsource)) - return nullptr; + return res; int scene = storage.getPatch().param_ptr[ptag]->scene; vector *modlist = nullptr; @@ -2463,6 +2467,43 @@ ModulationRouting *SurgeSynthesizer::getModRouting(long ptag, modsources modsour for (int i = 0; i < n; i++) { if ((modlist->at(i).destination_id == id) && (modlist->at(i).source_id == modsource)) + { + res.push_back(modlist->at(i).source_index); + } + } + + return res; +} + +ModulationRouting *SurgeSynthesizer::getModRouting(long ptag, modsources modsource, int index) const +{ + if (!isValidModulation(ptag, modsource)) + return nullptr; + + int scene = storage.getPatch().param_ptr[ptag]->scene; + vector *modlist = nullptr; + + if (!scene) + { + modlist = &storage.getPatch().modulation_global; + } + else + { + if (isScenelevel(modsource)) + modlist = &storage.getPatch().scene[scene - 1].modulation_scene; + else + modlist = &storage.getPatch().scene[scene - 1].modulation_voice; + } + + int id = storage.getPatch().param_ptr[ptag]->param_id_in_scene; + if (!scene) + id = ptag; + int n = modlist->size(); + + for (int i = 0; i < n; i++) + { + if ((modlist->at(i).destination_id == id) && (modlist->at(i).source_id == modsource) && + (modlist->at(i).source_index == index)) { return &modlist->at(i); } @@ -2471,9 +2512,9 @@ ModulationRouting *SurgeSynthesizer::getModRouting(long ptag, modsources modsour return nullptr; } -float SurgeSynthesizer::getModDepth(long ptag, modsources modsource) +float SurgeSynthesizer::getModDepth(long ptag, modsources modsource, int index) const { - ModulationRouting *r = getModRouting(ptag, modsource); + auto *r = getModRouting(ptag, modsource, index); float d = 0.f; if (r) d = r->depth; @@ -2483,15 +2524,20 @@ float SurgeSynthesizer::getModDepth(long ptag, modsources modsource) return d; } -bool SurgeSynthesizer::isActiveModulation(long ptag, modsources modsource) +bool SurgeSynthesizer::isActiveModulation(long ptag, modsources modsource, int index) const { // if(!isValidModulation(ptag,modsource)) return false; - if (getModRouting(ptag, modsource)) + if (getModRouting(ptag, modsource, index)) return true; return false; } -bool SurgeSynthesizer::isBipolarModulation(modsources tms) +bool SurgeSynthesizer::isAnyActiveModulation(long ptag, modsources modsource) const +{ + return !getModulationIndicesBetween(ptag, modsource).empty(); +} + +bool SurgeSynthesizer::isBipolarModulation(modsources tms) const { int scene_ms = storage.getPatch().scene_active.val.i; @@ -2523,7 +2569,7 @@ bool SurgeSynthesizer::isBipolarModulation(modsources tms) return false; } -bool SurgeSynthesizer::isModDestUsed(long ptag) +bool SurgeSynthesizer::isModDestUsed(long ptag) const { int scene_ms = storage.getPatch().scene_active.val.i; int scene_p = storage.getPatch().param_ptr[ptag]->scene; @@ -2537,7 +2583,7 @@ bool SurgeSynthesizer::isModDestUsed(long ptag) // if((scene && (j>0))||(!scene && (j==0))) if ((scene_p && (j > 0)) || (!scene_p && (j == 0))) { - vector *modlist; + const vector *modlist{nullptr}; switch (j) { @@ -2654,36 +2700,36 @@ bool SurgeSynthesizer::isModsourceUsed(modsources modsource) return modsourceused[modsource]; } -float SurgeSynthesizer::getModulation(long ptag, modsources modsource) +float SurgeSynthesizer::getModulation(long ptag, modsources modsource, int index) const { if (!isValidModulation(ptag, modsource)) return 0.0f; - ModulationRouting *r = getModRouting(ptag, modsource); + auto *r = getModRouting(ptag, modsource, index); if (r) return storage.getPatch().param_ptr[ptag]->get_modulation_f01(r->depth); return storage.getPatch().param_ptr[ptag]->get_modulation_f01(0); } -bool SurgeSynthesizer::isModulationMuted(long ptag, modsources modsource) +bool SurgeSynthesizer::isModulationMuted(long ptag, modsources modsource, int index) const { if (!isValidModulation(ptag, modsource)) return false; - ModulationRouting *r = getModRouting(ptag, modsource); + auto *r = getModRouting(ptag, modsource, index); if (r) return r->muted; return false; } -void SurgeSynthesizer::muteModulation(long ptag, modsources modsource, bool mute) +void SurgeSynthesizer::muteModulation(long ptag, modsources modsource, int index, bool mute) { if (!isValidModulation(ptag, modsource)) return; - ModulationRouting *r = getModRouting(ptag, modsource); + ModulationRouting *r = getModRouting(ptag, modsource, index); if (r) r->muted = mute; } @@ -2717,7 +2763,31 @@ void SurgeSynthesizer::clear_osc_modulation(int scene, int entry) storage.modRoutingMutex.unlock(); } -void SurgeSynthesizer::clearModulation(long ptag, modsources modsource, bool clearEvenIfInvalid) +bool SurgeSynthesizer::supportsIndexedModulator(int scene, modsources modsource) const +{ + if (modsource >= ms_lfo1 && modsource <= ms_lfo6) + { + auto lf = &(storage.getPatch().scene[scene].lfo[modsource - ms_lfo1]); + return lf->shape.val.i == lt_formula; + } + return false; +} + +int SurgeSynthesizer::getMaxModulationIndex(int scene, modsources modsource) const +{ + if (modsource >= ms_lfo1 && modsource <= ms_lfo6) + { + auto lf = &(storage.getPatch().scene[scene].lfo[modsource - ms_lfo1]); + if (lf->shape.val.i == lt_formula) + { + return Surge::Formula::max_formula_outputs; + } + } + return 1; +} + +void SurgeSynthesizer::clearModulation(long ptag, modsources modsource, int index, + bool clearEvenIfInvalid) { if (!isValidModulation(ptag, modsource) && !clearEvenIfInvalid) return; @@ -2745,7 +2815,8 @@ void SurgeSynthesizer::clearModulation(long ptag, modsources modsource, bool cle for (int i = 0; i < n; i++) { - if ((modlist->at(i).destination_id == id) && (modlist->at(i).source_id == modsource)) + if ((modlist->at(i).destination_id == id) && (modlist->at(i).source_id == modsource) && + (modlist->at(i).source_index == index)) { storage.modRoutingMutex.lock(); modlist->erase(modlist->begin() + i); @@ -2755,7 +2826,7 @@ void SurgeSynthesizer::clearModulation(long ptag, modsources modsource, bool cle } } -bool SurgeSynthesizer::setModulation(long ptag, modsources modsource, float val) +bool SurgeSynthesizer::setModulation(long ptag, modsources modsource, int index, float val) { if (!isValidModulation(ptag, modsource)) return false; @@ -2785,7 +2856,8 @@ bool SurgeSynthesizer::setModulation(long ptag, modsources modsource, float val) int found_id = -1; for (int i = 0; i < n; i++) { - if ((modlist->at(i).destination_id == id) && (modlist->at(i).source_id == modsource)) + if ((modlist->at(i).destination_id == id) && (modlist->at(i).source_id == modsource) && + (modlist->at(i).source_index == index)) { found_id = i; break; @@ -2808,6 +2880,7 @@ bool SurgeSynthesizer::setModulation(long ptag, modsources modsource, float val) t.source_id = modsource; t.destination_id = id; t.muted = false; + t.source_index = index; modlist->push_back(t); } else @@ -3226,12 +3299,14 @@ void SurgeSynthesizer::processControl() for (int i = 0; i < n; i++) { int src_id = storage.getPatch().scene[s].modulation_scene[i].source_id; + int src_index = storage.getPatch().scene[s].modulation_scene[i].source_index; if (storage.getPatch().scene[s].modsources[src_id]) { int dst_id = storage.getPatch().scene[s].modulation_scene[i].destination_id; float depth = storage.getPatch().scene[s].modulation_scene[i].depth; storage.getPatch().scenedata[s][dst_id].f += - depth * storage.getPatch().scene[s].modsources[src_id]->get_output(0) * + depth * + storage.getPatch().scene[s].modsources[src_id]->get_output(src_index) * (1.0 - storage.getPatch().scene[s].modulation_scene[i].muted); } } @@ -4015,9 +4090,10 @@ void SurgeSynthesizer::reorderFx(int source, int target, FXReorderMode m) whichForReal = q; } } - auto depth = - getModulation(fxsync[source].p[whichForReal].id, (modsources)mv->at(i).source_id); - fxmodsync[target].push_back(std::make_tuple(mv->at(i).source_id, whichForReal, depth)); + auto depth = getModulation(fxsync[source].p[whichForReal].id, + (modsources)mv->at(i).source_id, mv->at(i).source_index); + fxmodsync[target].push_back( + std::make_tuple(mv->at(i).source_id, mv->at(i).source_index, whichForReal, depth)); } if (m == FXReorderMode::SWAP) { @@ -4034,9 +4110,9 @@ void SurgeSynthesizer::reorderFx(int source, int target, FXReorderMode m) } } auto depth = getModulation(fxsync[target].p[whichForReal].id, - (modsources)mv->at(i).source_id); - fxmodsync[source].push_back( - std::make_tuple(mv->at(i).source_id, whichForReal, depth)); + (modsources)mv->at(i).source_id, mv->at(i).source_index); + fxmodsync[source].push_back(std::make_tuple( + mv->at(i).source_id, mv->at(i).source_index, whichForReal, depth)); } } } diff --git a/src/common/SurgeSynthesizer.h b/src/common/SurgeSynthesizer.h index 1a1db1e7f93..e882426907f 100644 --- a/src/common/SurgeSynthesizer.h +++ b/src/common/SurgeSynthesizer.h @@ -260,18 +260,25 @@ class alignas(16) SurgeSynthesizer public: void updateDisplay(); - bool isValidModulation(long ptag, modsources modsource); - bool isActiveModulation(long ptag, modsources modsource); - bool isBipolarModulation(modsources modsources); - bool isModsourceUsed(modsources modsource); - bool isModDestUsed(long moddest); - ModulationRouting *getModRouting(long ptag, modsources modsource); - bool setModulation(long ptag, modsources modsource, float value); - float getModulation(long ptag, modsources modsource); - void muteModulation(long ptag, modsources modsource, bool mute); - bool isModulationMuted(long ptag, modsources modsource); - float getModDepth(long ptag, modsources modsource); - void clearModulation(long ptag, modsources modsource, bool clearEvenIfInvalid = false); + bool isValidModulation(long ptag, modsources modsource) const; + bool isActiveModulation(long ptag, modsources modsource, int index) const; + bool isAnyActiveModulation(long ptag, modsources modsource) const; // independent of index + bool isBipolarModulation(modsources modsources) const; + bool isModsourceUsed(modsources modsource); // FIXME - this should be const + bool isModDestUsed(long moddest) const; + + bool supportsIndexedModulator(int scene, modsources modsource) const; + int getMaxModulationIndex(int scene, modsources modsource) const; + std::vector getModulationIndicesBetween(long ptag, modsources modsource) const; + ModulationRouting *getModRouting(long ptag, modsources modsource, int index) const; + + bool setModulation(long ptag, modsources modsource, int index, float value); + float getModulation(long ptag, modsources modsource, int index) const; + void muteModulation(long ptag, modsources modsource, int index, bool mute); + bool isModulationMuted(long ptag, modsources modsource, int index) const; + float getModDepth(long ptag, modsources modsource, int index) const; + void clearModulation(long ptag, modsources modsource, int index, + bool clearEvenIfInvalid = false); void clear_osc_modulation( int scene, int entry); // clear the modulation routings on the algorithm-specific sliders public: @@ -346,7 +353,7 @@ class alignas(16) SurgeSynthesizer bool fx_reload[n_fx_slots]; // if true, reload new effect parameters from fxsync FxStorage fxsync[n_fx_slots]; // used for synchronisation of parameter init bool fx_reload_mod[n_fx_slots]; - std::array>, n_fx_slots> fxmodsync; + std::array>, n_fx_slots> fxmodsync; int32_t fx_suspend_bitmask; // hold pedal stuff diff --git a/src/common/dsp/SurgeVoice.cpp b/src/common/dsp/SurgeVoice.cpp index b216e6b5923..06bb0fdc484 100644 --- a/src/common/dsp/SurgeVoice.cpp +++ b/src/common/dsp/SurgeVoice.cpp @@ -583,7 +583,8 @@ template void SurgeVoice::calc_ctrldata(QuadFilterChainState *Q, in if (modsources[src_id]) { - localcopy[dst_id].f += depth * modsources[src_id]->get_output(0) * (1.0 - iter->muted); + localcopy[dst_id].f += + depth * modsources[src_id]->get_output(iter->source_index) * (1.0 - iter->muted); } iter++; } diff --git a/src/common/dsp/modulators/FormulaModulationHelper.cpp b/src/common/dsp/modulators/FormulaModulationHelper.cpp index 6002a55c903..ff76973252f 100644 --- a/src/common/dsp/modulators/FormulaModulationHelper.cpp +++ b/src/common/dsp/modulators/FormulaModulationHelper.cpp @@ -216,13 +216,16 @@ bool initEvaluatorState(EvaluatorState &s) s.L = nullptr; return true; } -float valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs, EvaluatorState *s) +void valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs, EvaluatorState *s, + float output[max_formula_outputs]) { + s->activeoutputs = 1; + memset(output, 0, max_formula_outputs * sizeof(float)); if (s->L == nullptr) - return 0; + return; if (!s->isvalid) - return 0; + return; auto gs = Surge::LuaSupport::SGLD("valueAt", s->L); struct OnErrorReplaceWithZero @@ -250,7 +253,7 @@ float valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs { s->isvalid = false; lua_pop(s->L, 1); - return 0; + return; } lua_getglobal(s->L, s->stateName); // Stack is now func > table so we can update the table @@ -295,7 +298,8 @@ float valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs // OK so you returned a value. Just use it auto r = lua_tonumber(s->L, -1); lua_pop(s->L, 1); - return r; + output[0] = r; + return; } if (!lua_istable(s->L, -1)) { @@ -304,7 +308,7 @@ float valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs "output set."); s->isvalid = false; lua_pop(s->L, 1); - return 0; + return; } // Store the value and keep it on top of the stack lua_setglobal(s->L, s->stateName); @@ -316,11 +320,38 @@ float valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs float res = 0.0; if (lua_isnumber(s->L, -1)) { - res = lua_tonumber(s->L, -1); + output[0] = lua_tonumber(s->L, -1); + } + else if (lua_istable(s->L, -1)) + { + auto len = 0; + + lua_pushnil(s->L); + while (lua_next(s->L, -2)) // because we pushed nil + { + int idx = -1; + // now key is -2, value is -1 + if (lua_isnumber(s->L, -2)) + { + idx = lua_tointeger(s->L, -2); + } + if (idx < 0 || idx >= max_formula_outputs) + { + idx = 0; + s->adderror("Please index the output array with numbers 1-8"); + } + + // Remember - LUA is 0 based + output[idx - 1] = lua_tonumber(s->L, -1); + lua_pop(s->L, 1); + len = std::max(len, idx - 1); + } + s->activeoutputs = len + 1; } else { - s->adderror("You must define the 'output' field in the returned table as a number"); + s->adderror("You must define the 'output' field in the returned table as a number or " + "float array"); s->isvalid = false; }; // pop the result and the function @@ -345,7 +376,7 @@ float valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs // Finally pop the table result lua_pop(s->L, 1); onerr.replace = false; - return res; + return; } else { @@ -354,7 +385,7 @@ float valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs oss << "Failed to evaluate 'process' function." << lua_tostring(s->L, -1); s->adderror(oss.str()); lua_pop(s->L, 1); - return 0; + return; } } diff --git a/src/common/dsp/modulators/FormulaModulationHelper.h b/src/common/dsp/modulators/FormulaModulationHelper.h index fb3451568f6..ed515ca70fc 100644 --- a/src/common/dsp/modulators/FormulaModulationHelper.h +++ b/src/common/dsp/modulators/FormulaModulationHelper.h @@ -24,6 +24,8 @@ namespace Surge { namespace Formula { +static constexpr int max_formula_outputs{8}; + struct EvaluatorState { bool released; @@ -41,12 +43,14 @@ struct EvaluatorState std::string error; bool raisedError = false; - void adderror(const std::string &s) + void adderror(const std::string &msg) { - error += s; + error += msg; raisedError = true; } + int activeoutputs; + lua_State *L; // This is assigned by prepareForEvaluation to be one per thread }; @@ -54,8 +58,8 @@ bool initEvaluatorState(EvaluatorState &s); bool cleanEvaluatorState(EvaluatorState &s); bool prepareForEvaluation(FormulaModulatorStorage *fs, EvaluatorState &s, bool is_display); -float valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs, - EvaluatorState *state); +void valueAt(int phaseIntPart, float phaseFracPart, FormulaModulatorStorage *fs, + EvaluatorState *state, float output[max_formula_outputs]); void createInitFormula(FormulaModulatorStorage *fs); diff --git a/src/common/dsp/modulators/LFOModulationSource.cpp b/src/common/dsp/modulators/LFOModulationSource.cpp index d54792ce905..afc498c7081 100644 --- a/src/common/dsp/modulators/LFOModulationSource.cpp +++ b/src/common/dsp/modulators/LFOModulationSource.cpp @@ -386,6 +386,7 @@ void LFOModulationSource::process_block() { initPhaseFromStartPhase(); } + actout = 1; retrigger_FEG = false; retrigger_AEG = false; @@ -877,7 +878,8 @@ void LFOModulationSource::process_block() formulastate.tempo = storage->temposyncratio * 120.0; formulastate.songpos = storage->songpos; - iout = Surge::Formula::valueAt(unwrappedphase_intpart, phase, fs, &formulastate); + float tmpout[Surge::Formula::max_formula_outputs] = {0, 0, 0, 0, 0, 0, 0, 0}; + Surge::Formula::valueAt(unwrappedphase_intpart, phase, fs, &formulastate, tmpout); if (!formulastate.useEnvelope) { @@ -894,11 +896,25 @@ void LFOModulationSource::process_block() storage->reportError(em, "Formula Evaluator Error"); std::cout << "ERROR: " << em << std::endl; } + /* + * Since I'm (right now) the only vector valued modulator just do a little + * chute and ladder dance here on the output and return + */ + auto magnf = limit_range(lfo->magnitude.get_extended(localcopy[magn].f), -3.f, 3.f); + auto uni = lfo->unipolar.val.b; + for (auto i = 0; i < formulastate.activeoutputs; ++i) + { + if (uni) + tmpout[i] = 0.5f + 0.5f * tmpout[i]; + output_multi[i] = useenvval * magnf * tmpout[i]; + } + return; break; }; float io2 = iout; + // change this? pls check formula if (lfo->unipolar.val.b) { if (s != lt_stepseq) @@ -912,7 +928,7 @@ void LFOModulationSource::process_block() } auto magnf = limit_range(lfo->magnitude.get_extended(localcopy[magn].f), -3.f, 3.f); - output = useenvval * magnf * io2; + output_multi[0] = useenvval * magnf * io2; } void LFOModulationSource::completedModulation() diff --git a/src/common/dsp/modulators/LFOModulationSource.h b/src/common/dsp/modulators/LFOModulationSource.h index 89b792b65ac..60b962a5771 100644 --- a/src/common/dsp/modulators/LFOModulationSource.h +++ b/src/common/dsp/modulators/LFOModulationSource.h @@ -51,6 +51,16 @@ class LFOModulationSource : public ModulationSource virtual void process_block() override; virtual void completedModulation(); + int get_active_outputs() override { return actout; } + float get_output(int which) override { return output_multi[which]; } + float get_output01(int which) override { return output_multi[which]; } + void set_output(int which, float f) override + { + // set_output on an LFO should never be called. Blow up in debug if it is + assert(false); + output_multi[which] = f; + } + virtual const char *get_title() override { return "LFO"; } virtual int get_type() override { return mst_lfo; } virtual bool is_bipolar() override { return true; } @@ -68,6 +78,8 @@ class LFOModulationSource : public ModulationSource FormulaModulatorStorage *fs; + float output_multi[Surge::Formula::max_formula_outputs]; + public: Surge::MSEG::EvaluatorState msegstate; Surge::Formula::EvaluatorState formulastate; @@ -81,6 +93,7 @@ class LFOModulationSource : public ModulationSource float phase, target, noise, noised1, env_phase, priorPhase; int unwrappedphase_intpart; int priorStep = -1; + int actout; float ratemult; float env_releasestart; float iout; diff --git a/src/gui/SurgeGUIEditor.cpp b/src/gui/SurgeGUIEditor.cpp index 04322807f68..440dd360788 100644 --- a/src/gui/SurgeGUIEditor.cpp +++ b/src/gui/SurgeGUIEditor.cpp @@ -752,9 +752,9 @@ void SurgeGUIEditor::refresh_mod() { s->setIsEditingModulation(mod_editor); s->setModulationState(synth->isModDestUsed(i), - synth->isActiveModulation(i, thisms)); + synth->isActiveModulation(i, thisms, modsource_index)); s->setIsModulationBipolar(synth->isBipolarModulation(thisms)); - s->setModValue(synth->getModulation(i, thisms)); + s->setModValue(synth->getModulation(i, thisms, modsource_index)); } else { @@ -3350,7 +3350,7 @@ void SurgeGUIEditor::broadcastPluginAutomationChangeFor(Parameter *p) } //------------------------------------------------------------------------------------------------ -void SurgeGUIEditor::promptForUserValueEntry(Parameter *p, juce::Component *c, int ms) +void SurgeGUIEditor::promptForUserValueEntry(Parameter *p, juce::Component *c, int ms, int modidx) { if (typeinParamEditor->isVisible()) { @@ -3360,7 +3360,6 @@ void SurgeGUIEditor::promptForUserValueEntry(Parameter *p, juce::Component *c, i typeinParamEditor->setSkin(currentSkin, bitmapStore); bool ismod = p && ms > 0; - jassert(c); if (p) { @@ -3409,9 +3408,9 @@ void SurgeGUIEditor::promptForUserValueEntry(Parameter *p, juce::Component *c, i if (ismod) { char txt2[256]; - p->get_display_of_modulation_depth(txt, synth->getModDepth(p->id, (modsources)ms), - synth->isBipolarModulation((modsources)ms), - Parameter::TypeIn); + p->get_display_of_modulation_depth( + txt, synth->getModDepth(p->id, (modsources)ms, modidx), + synth->isBipolarModulation((modsources)ms), Parameter::TypeIn); p->get_display(txt2); sprintf(ptext, "mod: %s", txt); sprintf(ptext2, "current: %s", txt2); @@ -3438,12 +3437,13 @@ void SurgeGUIEditor::promptForUserValueEntry(Parameter *p, juce::Component *c, i if (ismod) { - std::string mls = std::string("by ") + (char *)modulatorName(ms, true).c_str(); + std::string mls = std::string("by ") + modulatorName(ms, true) + + modulatorIndexExtension(current_scene, ms, modidx); typeinParamEditor->setModByLabel(mls); } typeinParamEditor->setEditedParam(p); - typeinParamEditor->setModulation(p && ms > 0, (modsources)ms); + typeinParamEditor->setModulation(p && ms > 0, (modsources)ms, modidx); if (frame->getIndexOfChildComponent(typeinParamEditor.get()) < 0) { @@ -3641,7 +3641,7 @@ void SurgeGUIEditor::sliderHoverStart(int tag) for (int k = 1; k < n_modsources; k++) { modsources ms = (modsources)k; - if (synth->isActiveModulation(ptag, ms)) + if (synth->isActiveModulation(ptag, ms, modsource_index)) { if (gui_modsrc[k]) { @@ -3796,8 +3796,9 @@ void SurgeGUIEditor::openModTypeinOnDrop(int modt, Surge::Widgets::ModulatableCo auto p = synth->storage.getPatch().param_ptr[slidertag - start_paramtags]; int ms = modt - tag_mod_source0; + jassert(false); // the index below needs fixing if (synth->isValidModulation(p->id, (modsources)ms)) - promptForUserValueEntry(p, sl->asControlValueInterface()->asJuceComponent(), ms); + promptForUserValueEntry(p, sl->asControlValueInterface()->asJuceComponent(), ms, 0); } void SurgeGUIEditor::resetSmoothing(ControllerModulationSource::SmoothingMode t) @@ -4042,10 +4043,10 @@ SurgeGUIEditor::layoutComponentForSkin(std::shared_ptrsetIsEditingModulation(mod_editor); hs->setModulationState(synth->isModDestUsed(p->id), - synth->isActiveModulation(p->id, modsource)); + synth->isActiveModulation(p->id, modsource, modsource_index)); if (synth->isValidModulation(p->id, modsource)) { - hs->setModValue(synth->getModulation(p->id, modsource)); + hs->setModValue(synth->getModulation(p->id, modsource, modsource_index)); hs->setIsModulationBipolar(synth->isBipolarModulation(modsource)); } @@ -4939,4 +4940,13 @@ void SurgeGUIEditor::setAccessibilityInformationByTitleAndAction(juce::Component #endif #endif +} + +std::string SurgeGUIEditor::modulatorIndexExtension(int scene, int ms, int index) +{ + if (synth->supportsIndexedModulator(scene, (modsources)ms)) + { + return std::string(" Out ") + std::to_string(index + 1); + } + return ""; } \ No newline at end of file diff --git a/src/gui/SurgeGUIEditor.h b/src/gui/SurgeGUIEditor.h index b83e17882c3..e3bcbabb98f 100644 --- a/src/gui/SurgeGUIEditor.h +++ b/src/gui/SurgeGUIEditor.h @@ -158,6 +158,7 @@ class SurgeGUIEditor : public Surge::GUI::IComponentTagValue::Listener, bool editor_open = false; bool mod_editor = false; modsources modsource = ms_lfo1, modsource_editor[n_scenes] = {ms_lfo1, ms_lfo1}; + int modsource_index{0}; int fxbypass_tag = 0, f1subtypetag = 0, f2subtypetag = 0, filterblock_tag = 0, fmconfig_tag = 0; double lastTempo = 0; int lastTSNum = 0, lastTSDen = 0; @@ -481,7 +482,8 @@ class SurgeGUIEditor : public Surge::GUI::IComponentTagValue::Listener, std::unique_ptr typeinParamEditor; bool setParameterFromString(Parameter *p, const std::string &s); - bool setParameterModulationFromString(Parameter *p, modsources ms, const std::string &s); + bool setParameterModulationFromString(Parameter *p, modsources ms, int modidx, + const std::string &s); bool setControlFromString(modsources ms, const std::string &s); friend struct Surge::Overlays::TypeinParamEditor; friend struct Surge::Overlays::PatchStoreDialog; @@ -542,6 +544,7 @@ class SurgeGUIEditor : public Surge::GUI::IComponentTagValue::Listener, public: std::string modulatorName(int ms, bool forButton); + std::string modulatorIndexExtension(int scene, int ms, int index); private: Parameter *typeinEditTarget = nullptr; @@ -607,7 +610,11 @@ class SurgeGUIEditor : public Surge::GUI::IComponentTagValue::Listener, static std::string fullyResolvedHelpURL(const std::string &helpurl); private: - void promptForUserValueEntry(Parameter *p, juce::Component *c, int modulationSource = -1); + void promptForUserValueEntry(Parameter *p, juce::Component *c) + { + promptForUserValueEntry(p, c, -1, -1); + } + void promptForUserValueEntry(Parameter *p, juce::Component *c, int modsource, int modindex); /* ** Skin support diff --git a/src/gui/SurgeGUIEditorInfowindow.cpp b/src/gui/SurgeGUIEditorInfowindow.cpp index 9b956b0b9cb..c5a0ba6b7d6 100644 --- a/src/gui/SurgeGUIEditorInfowindow.cpp +++ b/src/gui/SurgeGUIEditorInfowindow.cpp @@ -79,9 +79,9 @@ void SurgeGUIEditor::updateInfowindowContents(int ptag, bool isModulated) synth->getParameterName(ptagid, txt); sprintf(pname, "%s -> %s", modulatorName(modsource, true).c_str(), txt); ModulationDisplayInfoWindowStrings mss; - p->get_display_of_modulation_depth(pdisp, synth->getModDepth(pid, modsource), - synth->isBipolarModulation(modsource), - Parameter::InfoWindow, &mss); + p->get_display_of_modulation_depth( + pdisp, synth->getModDepth(pid, modsource, modsource_index), + synth->isBipolarModulation(modsource), Parameter::InfoWindow, &mss); if (mss.val != "") { paramInfowindow->setLabels(pname, pdisp); diff --git a/src/gui/SurgeGUIEditorValueCallbacks.cpp b/src/gui/SurgeGUIEditorValueCallbacks.cpp index 31c70f192d1..5ca997077e9 100644 --- a/src/gui/SurgeGUIEditorValueCallbacks.cpp +++ b/src/gui/SurgeGUIEditorValueCallbacks.cpp @@ -367,42 +367,52 @@ int32_t SurgeGUIEditor::controlModifierClicked(Surge::GUI::IComponentTagValue *c Parameter *parameter = synth->storage.getPatch().param_ptr[md]; if (((md < n_global_params) || ((parameter->scene - 1) == activeScene)) && - synth->isActiveModulation(md, thisms)) + synth->isAnyActiveModulation(md, thisms)) { char modtxt[TXT_SIZE]; auto pmd = synth->storage.getPatch().param_ptr[md]; - pmd->get_display_of_modulation_depth(modtxt, synth->getModDepth(md, thisms), - synth->isBipolarModulation(thisms), - Parameter::Menu); - char tmptxt[1024]; // leave room for that ubuntu 20.0 error - - if (pmd->ctrlgroup == cg_LFO) + auto indices = synth->getModulationIndicesBetween(md, thisms); + auto hasIdx = synth->supportsIndexedModulator(current_scene, thisms); + for (auto modidx : indices) { - char pname[TXT_SIZE]; - pmd->create_fullname(pmd->get_name(), pname, pmd->ctrlgroup, - pmd->ctrlgroup_entry, - modulatorName(pmd->ctrlgroup_entry, true).c_str()); - snprintf(tmptxt, TXT_SIZE, "Edit %s -> %s: %s", - (char *)modulatorName(thisms, true).c_str(), pname, modtxt); - } - else - { - snprintf(tmptxt, TXT_SIZE, "Edit %s -> %s: %s", - (char *)modulatorName(thisms, true).c_str(), - pmd->get_full_name(), modtxt); - } + pmd->get_display_of_modulation_depth( + modtxt, synth->getModDepth(md, thisms, modidx), + synth->isBipolarModulation(thisms), Parameter::Menu); + char tmptxt[1024]; // leave room for that ubuntu 20.0 error + + if (pmd->ctrlgroup == cg_LFO) + { + char pname[TXT_SIZE]; + pmd->create_fullname( + pmd->get_name(), pname, pmd->ctrlgroup, pmd->ctrlgroup_entry, + modulatorName(pmd->ctrlgroup_entry, true).c_str()); + snprintf( + tmptxt, TXT_SIZE, "Edit %s%s -> %s: %s", + (char *)modulatorName(thisms, true).c_str(), + modulatorIndexExtension(current_scene, thisms, modidx).c_str(), + pname, modtxt); + } + else + { + snprintf( + tmptxt, TXT_SIZE, "Edit %s%s -> %s: %s", + (char *)modulatorName(thisms, true).c_str(), + modulatorIndexExtension(current_scene, thisms, modidx).c_str(), + pmd->get_full_name(), modtxt); + } - auto clearOp = [this, parameter, bvf, thisms]() { - this->promptForUserValueEntry(parameter, bvf, thisms); - }; + auto clearOp = [this, parameter, bvf, thisms, modidx]() { + this->promptForUserValueEntry(parameter, bvf, thisms, modidx); + }; - if (first_destination) - { - contextMenu.addSeparator(); - first_destination = false; - } + if (first_destination) + { + contextMenu.addSeparator(); + first_destination = false; + } - contextMenu.addItem(tmptxt, clearOp); + contextMenu.addItem(tmptxt, clearOp); + } } } @@ -411,109 +421,122 @@ int32_t SurgeGUIEditor::controlModifierClicked(Surge::GUI::IComponentTagValue *c { auto activeScene = synth->storage.getPatch().scene_active.val.i; Parameter *parameter = synth->storage.getPatch().param_ptr[md]; - if (((md < n_global_params) || ((parameter->scene - 1) == activeScene)) && - synth->isActiveModulation(md, thisms)) + synth->isAnyActiveModulation(md, thisms)) { - char tmptxt[1024]; - - if (parameter->ctrlgroup == cg_LFO) - { - char pname[512]; - parameter->create_fullname( - parameter->get_name(), pname, parameter->ctrlgroup, - parameter->ctrlgroup_entry, - modulatorName(parameter->ctrlgroup_entry, true).c_str()); - snprintf(tmptxt, TXT_SIZE, "Clear %s -> %s", - (char *)modulatorName(thisms, true).c_str(), pname); - } - else + auto pmd = synth->storage.getPatch().param_ptr[md]; + auto indices = synth->getModulationIndicesBetween(md, thisms); + for (auto modidx : indices) { - snprintf(tmptxt, TXT_SIZE, "Clear %s -> %s", - (char *)modulatorName(thisms, true).c_str(), - parameter->get_full_name()); - } - - auto clearOp = [this, first_destination, md, n_total_md, thisms, bvf, - control]() { - bool resetName = false; // Should I reset the name? - std::string newName = ""; // And to what? - int ccid = thisms - ms_ctrl1; + char tmptxt[1024]; - if (first_destination) + if (parameter->ctrlgroup == cg_LFO) { - if (strncmp(synth->storage.getPatch().CustomControllerLabel[ccid], - synth->storage.getPatch().param_ptr[md]->get_name(), - CUSTOM_CONTROLLER_LABEL_SIZE - 1) == 0) - { - // So my modulator is named after my short name. I haven't been - // renamed. So I want to reset at least to "-" unless someone is - // after me - resetName = true; - newName = "-"; + char pname[512]; + parameter->create_fullname( + parameter->get_name(), pname, parameter->ctrlgroup, + parameter->ctrlgroup_entry, + modulatorName(parameter->ctrlgroup_entry, true).c_str()); + snprintf( + tmptxt, TXT_SIZE, "Clear %s%s -> %s", + (char *)modulatorName(thisms, true).c_str(), + modulatorIndexExtension(current_scene, thisms, modidx).c_str(), + pname); + } + else + { + snprintf( + tmptxt, TXT_SIZE, "Clear %s%s -> %s", + (char *)modulatorName(thisms, true).c_str(), + modulatorIndexExtension(current_scene, thisms, modidx).c_str(), + parameter->get_full_name()); + } - // Now we have to find if there's another modulation below me - int nextmd = md + 1; + auto clearOp = [this, first_destination, md, n_total_md, thisms, modidx, + bvf, control]() { + bool resetName = false; // Should I reset the name? + std::string newName = ""; // And to what? + int ccid = thisms - ms_ctrl1; - while (nextmd < n_total_md && - !synth->isActiveModulation(nextmd, thisms)) + if (first_destination) + { + if (strncmp( + synth->storage.getPatch().CustomControllerLabel[ccid], + synth->storage.getPatch().param_ptr[md]->get_name(), + CUSTOM_CONTROLLER_LABEL_SIZE - 1) == 0) { - nextmd++; - } + // So my modulator is named after my short name. I haven't + // been renamed. So I want to reset at least to "-" unless + // someone is after me + resetName = true; + newName = "-"; + + // Now we have to find if there's another modulation below + // me + int nextmd = md + 1; + + while (nextmd < n_total_md && + !synth->isAnyActiveModulation(nextmd, thisms)) + { + nextmd++; + } - if (nextmd < n_total_md && strlen(synth->storage.getPatch() - .param_ptr[nextmd] - ->get_name()) > 1) - { - newName = - synth->storage.getPatch().param_ptr[nextmd]->get_name(); + if (nextmd < n_total_md && strlen(synth->storage.getPatch() + .param_ptr[nextmd] + ->get_name()) > 1) + { + newName = synth->storage.getPatch() + .param_ptr[nextmd] + ->get_name(); + } } } - } - - synth->clearModulation(md, thisms); - refresh_mod(); - - if (bvf) - { - bvf->repaint(); - } - if (resetName) - { - // And this is where we apply the name refresh, of course. - strxcpy(synth->storage.getPatch().CustomControllerLabel[ccid], - newName.c_str(), CUSTOM_CONTROLLER_LABEL_SIZE - 1); - synth->storage.getPatch() - .CustomControllerLabel[ccid][CUSTOM_CONTROLLER_LABEL_SIZE - 1] = - 0; - auto msb = - dynamic_cast(control); + synth->clearModulation(md, thisms, modidx); + refresh_mod(); - if (msb) + if (bvf) { - msb->setLabel( - synth->storage.getPatch().CustomControllerLabel[ccid]); + bvf->repaint(); } - if (bvf) + if (resetName) { - bvf->repaint(); + // And this is where we apply the name refresh, of course. + strxcpy(synth->storage.getPatch().CustomControllerLabel[ccid], + newName.c_str(), CUSTOM_CONTROLLER_LABEL_SIZE - 1); + synth->storage.getPatch() + .CustomControllerLabel[ccid][CUSTOM_CONTROLLER_LABEL_SIZE - + 1] = 0; + auto msb = + dynamic_cast( + control); + + if (msb) + { + msb->setLabel( + synth->storage.getPatch().CustomControllerLabel[ccid]); + } + + if (bvf) + { + bvf->repaint(); + } + + synth->updateDisplay(); } + }; - synth->updateDisplay(); + if (first_destination) + { + contextMenu.addSeparator(); + first_destination = false; } - }; - - if (first_destination) - { - contextMenu.addSeparator(); - first_destination = false; - } - contextMenu.addItem(tmptxt, clearOp); + contextMenu.addItem(tmptxt, clearOp); - n_md++; + n_md++; + } } } @@ -531,7 +554,8 @@ int32_t SurgeGUIEditor::controlModifierClicked(Surge::GUI::IComponentTagValue *c contextMenu.addItem(clearLab, [this, n_total_md, thisms, control]() { for (int md = 1; md < n_total_md; md++) { - synth->clearModulation(md, thisms); + for (auto idx : synth->getModulationIndicesBetween(md, thisms)) + synth->clearModulation(md, thisms, idx); } refresh_mod(); @@ -575,7 +599,8 @@ int32_t SurgeGUIEditor::controlModifierClicked(Surge::GUI::IComponentTagValue *c Surge::GUI::toOSCaseForMenu("Edit Value").c_str(), (detailedMode ? 6 : 2), 100 * cms->get_output(0)); contextMenu.addItem(vtxt, [this, bvf, modsource]() { - promptForUserValueEntry(nullptr, bvf, modsource); + promptForUserValueEntry(nullptr, bvf, modsource, + 0); // controllers aren't indexed }); contextMenu.addSeparator(); @@ -643,20 +668,19 @@ int32_t SurgeGUIEditor::controlModifierClicked(Surge::GUI::IComponentTagValue *c std::string learnTag = cancellearn ? "Abort Macro MIDI Learn" : "MIDI Learn Macro..."; - contextMenu.addItem(Surge::GUI::toOSCaseForMenu(learnTag), - [this, cancellearn, control, ccid, viewSize] { - if (cancellearn) - { - hideMidiLearnOverlay(); - synth->learn_custom = -1; - } - else - { - showMidiLearnOverlay( - control->asJuceComponent()->getBounds()); - synth->learn_custom = ccid; - } - }); + contextMenu.addItem( + Surge::GUI::toOSCaseForMenu(learnTag), [this, cancellearn, control, ccid] { + if (cancellearn) + { + hideMidiLearnOverlay(); + synth->learn_custom = -1; + } + else + { + showMidiLearnOverlay(control->asJuceComponent()->getBounds()); + synth->learn_custom = ccid; + } + }); if (synth->storage.controllers[ccid] >= 0) { @@ -1719,7 +1743,7 @@ int32_t SurgeGUIEditor::controlModifierClicked(Surge::GUI::IComponentTagValue *c for (int ms = 1; ms < n_modsources; ms++) { - if (synth->isActiveModulation(ptag, (modsources)ms)) + if (synth->isAnyActiveModulation(ptag, (modsources)ms)) { n_ms++; } @@ -1742,7 +1766,11 @@ int32_t SurgeGUIEditor::controlModifierClicked(Surge::GUI::IComponentTagValue *c { modsources ms = (modsources)modsource_display_order[k]; - if (!synth->isActiveModulation(ptag, ms) && + bool isAnyOn = synth->isAnyActiveModulation(ptag, ms); + bool isValud = synth->isValidModulation(ptag, ms); + bool isIndexed = synth->supportsIndexedModulator(current_scene, ms); + + if ((!synth->isAnyActiveModulation(ptag, ms) || isIndexed) && synth->isValidModulation(ptag, ms)) { char tmptxt[512]; @@ -1752,37 +1780,71 @@ int32_t SurgeGUIEditor::controlModifierClicked(Surge::GUI::IComponentTagValue *c if (ms >= ms_ctrl1 && ms <= ms_ctrl1 + n_customcontrollers - 1) { addMacroSub.addItem(tmptxt, [this, p, bvf, ms]() { - this->promptForUserValueEntry(p, bvf, ms); + this->promptForUserValueEntry(p, bvf, ms, 0); }); } else if (ms >= ms_lfo1 && ms <= ms_lfo1 + n_lfos_voice - 1) { - addVLFOSub.addItem(tmptxt, [this, p, bvf, ms]() { - this->promptForUserValueEntry(p, bvf, ms); - }); + if (isIndexed) + { + int maxidx = synth->getMaxModulationIndex(current_scene, ms); + auto subm = juce::PopupMenu(); + for (int i = 0; i < maxidx; ++i) + { + auto subn = modulatorName(ms, false) + + modulatorIndexExtension(current_scene, ms, i); + subm.addItem(subn, [this, p, bvf, ms, i]() { + this->promptForUserValueEntry(p, bvf, ms, i); + }); + } + addVLFOSub.addSubMenu(tmptxt, subm); + } + else + { + addVLFOSub.addItem(tmptxt, [this, p, bvf, ms]() { + this->promptForUserValueEntry(p, bvf, ms, 0); + }); + } } else if (ms >= ms_slfo1 && ms <= ms_slfo1 + n_lfos_scene - 1) { - addSLFOSub.addItem(tmptxt, [this, p, bvf, ms]() { - this->promptForUserValueEntry(p, bvf, ms); - }); + if (isIndexed) + { + int maxidx = synth->getMaxModulationIndex(current_scene, ms); + auto subm = juce::PopupMenu(); + for (int i = 0; i < maxidx; ++i) + { + auto subn = modulatorName(ms, false) + + modulatorIndexExtension(current_scene, ms, i); + subm.addItem(subn, [this, p, bvf, ms, i]() { + this->promptForUserValueEntry(p, bvf, ms, i); + }); + } + addSLFOSub.addSubMenu(tmptxt, subm); + } + else + { + addSLFOSub.addItem(tmptxt, [this, p, bvf, ms]() { + this->promptForUserValueEntry(p, bvf, ms, 0); + }); + } } else if (ms >= ms_ampeg && ms <= ms_filtereg) { addEGSub.addItem(tmptxt, [this, p, bvf, ms]() { - this->promptForUserValueEntry(p, bvf, ms); + this->promptForUserValueEntry(p, bvf, ms, 0); }); } else if (ms >= ms_random_bipolar && ms <= ms_alternate_unipolar) { addMiscSub.addItem(tmptxt, [this, p, bvf, ms]() { - this->promptForUserValueEntry(p, bvf, ms); + this->promptForUserValueEntry(p, bvf, ms, 0); }); } else { addMIDISub.addItem(tmptxt, [this, p, bvf, ms]() { - this->promptForUserValueEntry(p, bvf, ms); + this->promptForUserValueEntry(p, bvf, ms, 0); }); } } @@ -1840,22 +1902,26 @@ int32_t SurgeGUIEditor::controlModifierClicked(Surge::GUI::IComponentTagValue *c for (int k = 1; k < n_modsources; k++) { modsources ms = (modsources)k; - - if (synth->isActiveModulation(ptag, ms)) + auto indices = synth->getModulationIndicesBetween(ptag, ms); + for (auto modidx : indices) { - char modtxt[256]; - p->get_display_of_modulation_depth(modtxt, synth->getModDepth(ptag, ms), - synth->isBipolarModulation(ms), - Parameter::Menu); - - char tmptxt[512]; - sprintf(tmptxt, "Edit %s -> %s: %s", - (char *)modulatorName(ms, true).c_str(), p->get_full_name(), - modtxt); - - contextMenu.addItem(tmptxt, [this, p, bvf, ms]() { - this->promptForUserValueEntry(p, bvf, ms); - }); + if (synth->isActiveModulation(ptag, ms, modidx)) + { + char modtxt[256]; + p->get_display_of_modulation_depth( + modtxt, synth->getModDepth(ptag, ms, modidx), + synth->isBipolarModulation(ms), Parameter::Menu); + + char tmptxt[512]; + sprintf(tmptxt, "Edit %s%s -> %s: %s", + (char *)modulatorName(ms, true).c_str(), + modulatorIndexExtension(current_scene, ms, modidx).c_str(), + p->get_full_name(), modtxt); + + contextMenu.addItem(tmptxt, [this, p, bvf, ms, modidx]() { + this->promptForUserValueEntry(p, bvf, ms, modidx); + }); + } } } @@ -1864,38 +1930,40 @@ int32_t SurgeGUIEditor::controlModifierClicked(Surge::GUI::IComponentTagValue *c for (int k = 1; k < n_modsources; k++) { modsources ms = (modsources)k; - - if (synth->isActiveModulation(ptag, ms)) + auto indices = synth->getModulationIndicesBetween(ptag, ms); + for (auto modidx : indices) { - char tmptxt[256]; - snprintf(tmptxt, 256, "Clear %s -> %s", - (char *)modulatorName(ms, true).c_str(), p->get_full_name()); - // clear_ms[ms] = eid; - // contextMenu->addEntry(tmptxt, eid++); - - contextMenu.addItem(tmptxt, [this, ms, ptag]() { - synth->clearModulation(ptag, (modsources)ms); - refresh_mod(); - /* - ** FIXME - this is a pretty big hammer to deal with - ** #1477 - can we be more parsimonious? - */ - synth->refresh_editor = true; - }); + if (synth->isActiveModulation(ptag, ms, modidx)) + { + char tmptxt[256]; + snprintf(tmptxt, 256, "Clear %s%s -> %s", + (char *)modulatorName(ms, true).c_str(), + modulatorIndexExtension(current_scene, ms, modidx).c_str(), + p->get_full_name()); + + contextMenu.addItem(tmptxt, [this, ms, ptag, modidx]() { + synth->clearModulation(ptag, (modsources)ms, modidx); + refresh_mod(); + synth->refresh_editor = true; + }); + } } } if (n_ms > 1) { - contextMenu.addItem(Surge::GUI::toOSCaseForMenu("Clear All"), - [this, ptag]() { - for (int ms = 1; ms < n_modsources; ms++) - { - synth->clearModulation(ptag, (modsources)ms); - } - refresh_mod(); - synth->refresh_editor = true; - }); + contextMenu.addItem( + Surge::GUI::toOSCaseForMenu("Clear All"), [this, ptag]() { + for (int ms = 1; ms < n_modsources; ms++) + { + auto indices = + synth->getModulationIndicesBetween(ptag, (modsources)ms); + for (auto idx : indices) + synth->clearModulation(ptag, (modsources)ms, idx); + } + refresh_mod(); + synth->refresh_editor = true; + }); } } } // end vt_float if statement @@ -1988,14 +2056,15 @@ int32_t SurgeGUIEditor::controlModifierClicked(Surge::GUI::IComponentTagValue *c if (cms->getHasAlternate() && cms->getUseAlternate()) thisms = cms->getAlternate(); - synth->clearModulation(ptag, thisms); + synth->clearModulation(ptag, thisms, modsource_index); auto ctrms = dynamic_cast(control); jassert(ctrms); if (ctrms) { - ctrms->setModValue(synth->getModulation(p->id, thisms)); - ctrms->setModulationState(synth->isModDestUsed(p->id), - synth->isActiveModulation(p->id, thisms)); + ctrms->setModValue(synth->getModulation(p->id, thisms, modsource_index)); + ctrms->setModulationState( + synth->isModDestUsed(p->id), + synth->isActiveModulation(p->id, thisms, modsource_index)); ctrms->setIsModulationBipolar(synth->isBipolarModulation(thisms)); } oscWaveform->repaint(); @@ -2521,9 +2590,9 @@ void SurgeGUIEditor::valueChanged(Surge::GUI::IComponentTagValue *control) // maybe setModValue here } - synth->setModulation(ptag, thisms, mv); + synth->setModulation(ptag, thisms, modsource_index, mv); mci->setModulationState(synth->isModDestUsed(p->id), - synth->isActiveModulation(p->id, thisms)); + synth->isActiveModulation(p->id, thisms, modsource_index)); mci->setIsModulationBipolar(synth->isBipolarModulation(thisms)); SurgeSynthesizer::ID ptagid; @@ -2531,9 +2600,9 @@ void SurgeGUIEditor::valueChanged(Surge::GUI::IComponentTagValue *control) synth->getParameterName(ptagid, txt); sprintf(pname, "%s -> %s", modulatorName(thisms, true).c_str(), txt); ModulationDisplayInfoWindowStrings mss; - p->get_display_of_modulation_depth(pdisp, synth->getModDepth(ptag, thisms), - synth->isBipolarModulation(thisms), - Parameter::InfoWindow, &mss); + p->get_display_of_modulation_depth( + pdisp, synth->getModDepth(ptag, thisms, modsource_index), + synth->isBipolarModulation(thisms), Parameter::InfoWindow, &mss); modulate = true; if (isCustomController(modsource)) @@ -2752,7 +2821,7 @@ bool SurgeGUIEditor::setParameterFromString(Parameter *p, const std::string &s) return false; } -bool SurgeGUIEditor::setParameterModulationFromString(Parameter *p, modsources ms, +bool SurgeGUIEditor::setParameterModulationFromString(Parameter *p, modsources ms, int modidx, const std::string &s) { if (!p || ms == 0) @@ -2767,7 +2836,7 @@ bool SurgeGUIEditor::setParameterModulationFromString(Parameter *p, modsources m } else { - synth->setModulation(p->id, ms, mv); + synth->setModulation(p->id, ms, modidx, mv); synth->refresh_editor = true; } return true; diff --git a/src/gui/overlays/ModulationEditor.cpp b/src/gui/overlays/ModulationEditor.cpp index 757890760f8..6cf8d9b8e95 100644 --- a/src/gui/overlays/ModulationEditor.cpp +++ b/src/gui/overlays/ModulationEditor.cpp @@ -37,7 +37,7 @@ struct ModulationListBoxModel : public juce::ListBoxModel explicit Datum(RowType t) : type(t) {} std::string hLab; - int dest_id = -1, source_id = -1; + int dest_id = -1, source_id = -1, source_index = -1; std::string dest_name, source_name; int modNum = -1; @@ -115,7 +115,8 @@ struct ModulationListBoxModel : public juce::ListBoxModel muteButton->addListener(this); muteButton->setToggleState( mod->moded->synth->isModulationMuted(mod->rows[row].dest_id, - (modsources)mod->rows[row].source_id), + (modsources)mod->rows[row].source_id, + mod->rows[row].source_index), juce::NotificationType::dontSendNotification); addAndMakeVisible(*muteButton); @@ -132,7 +133,7 @@ struct ModulationListBoxModel : public juce::ListBoxModel { auto rd = mod->rows[row]; auto um = rd.p->set_modulation_f01(slider->getValue()); - mod->moded->synth->setModulation(rd.dest_id, (modsources)rd.source_id, + mod->moded->synth->setModulation(rd.dest_id, (modsources)rd.source_id, rd.source_index, slider->getValue()); mod->updateRowByModnum(rd.modNum); mod->moded->repaint(); @@ -143,15 +144,16 @@ struct ModulationListBoxModel : public juce::ListBoxModel if (button == clearButton.get()) { mod->moded->synth->clearModulation(mod->rows[row].dest_id, - (modsources)mod->rows[row].source_id); + (modsources)mod->rows[row].source_id, + mod->rows[row].source_index); mod->updateRows(); mod->moded->listBox->updateContent(); } if (button == muteButton.get()) { - mod->moded->synth->muteModulation(mod->rows[row].dest_id, - (modsources)mod->rows[row].source_id, - button->getToggleState()); + mod->moded->synth->muteModulation( + mod->rows[row].dest_id, (modsources)mod->rows[row].source_id, + mod->rows[row].source_index, button->getToggleState()); } } void resized() override @@ -217,10 +219,10 @@ struct ModulationListBoxModel : public juce::ListBoxModel char pdisp[TXT_SIZE]; int ptag = r.p->id; modsources thisms = (modsources)r.source_id; - r.p->get_display_of_modulation_depth(pdisp, moded->synth->getModDepth(ptag, thisms), - moded->synth->isBipolarModulation(thisms), - Parameter::InfoWindow, &(r.mss)); - r.depth = moded->synth->getModDepth(ptag, thisms); + r.p->get_display_of_modulation_depth( + pdisp, moded->synth->getModDepth(ptag, thisms, r.source_index), + moded->synth->isBipolarModulation(thisms), Parameter::InfoWindow, &(r.mss)); + r.depth = moded->synth->getModDepth(ptag, thisms, r.source_index); } } } @@ -230,7 +232,8 @@ struct ModulationListBoxModel : public juce::ListBoxModel std::ostringstream oss; int modNum = 0; auto append = [&oss, &modNum, this](const std::string &type, - const std::vector &r, int idBase) { + const std::vector &r, int idBase, + int scene) { if (r.empty()) return; @@ -247,9 +250,12 @@ struct ModulationListBoxModel : public juce::ListBoxModel if (moded->synth->fromSynthSideId(q.destination_id + idBase, ptagid)) moded->synth->getParameterName(ptagid, nm); std::string sname = moded->ed->modulatorName(q.source_id, false); + if (scene >= 0) + sname += moded->ed->modulatorIndexExtension(scene, q.source_id, q.source_index); auto rDisp = Datum(Datum::SHOW_ROW); rDisp.source_id = q.source_id; rDisp.dest_id = q.destination_id + idBase; + rDisp.source_index = q.source_index; rDisp.source_name = sname; rDisp.dest_name = nm; rDisp.modNum = modNum++; @@ -259,10 +265,10 @@ struct ModulationListBoxModel : public juce::ListBoxModel auto p = moded->synth->storage.getPatch().param_ptr[q.destination_id + idBase]; int ptag = p->id; modsources thisms = (modsources)q.source_id; - p->get_display_of_modulation_depth(pdisp, moded->synth->getModDepth(ptag, thisms), - moded->synth->isBipolarModulation(thisms), - Parameter::InfoWindow, &(rDisp.mss)); - rDisp.depth = moded->synth->getModDepth(ptag, thisms); + p->get_display_of_modulation_depth( + pdisp, moded->synth->getModDepth(ptag, thisms, q.source_index), + moded->synth->isBipolarModulation(thisms), Parameter::InfoWindow, &(rDisp.mss)); + rDisp.depth = moded->synth->getModDepth(ptag, thisms, q.source_index); rDisp.isBipolar = moded->synth->isBipolarModulation(thisms); rows.push_back(rDisp); @@ -273,19 +279,19 @@ struct ModulationListBoxModel : public juce::ListBoxModel << nm << " at " << q.depth << "\n"; } }; - append("Global Modulators", moded->synth->storage.getPatch().modulation_global, 0); + append("Global Modulators", moded->synth->storage.getPatch().modulation_global, 0, -1); append("Scene A - Voice Modulators", moded->synth->storage.getPatch().scene[0].modulation_voice, - moded->synth->storage.getPatch().scene_start[0]); + moded->synth->storage.getPatch().scene_start[0], 0); append("Scene A - Scene Modulators", moded->synth->storage.getPatch().scene[0].modulation_scene, - moded->synth->storage.getPatch().scene_start[0]); + moded->synth->storage.getPatch().scene_start[0], 0); append("Scene B - Voice Modulators", moded->synth->storage.getPatch().scene[1].modulation_voice, - moded->synth->storage.getPatch().scene_start[1]); + moded->synth->storage.getPatch().scene_start[1], 1); append("Scene B - Scene Modulators", moded->synth->storage.getPatch().scene[1].modulation_scene, - moded->synth->storage.getPatch().scene_start[0]); + moded->synth->storage.getPatch().scene_start[1], 1); debugRows = oss.str(); } diff --git a/src/gui/overlays/TypeinParamEditor.cpp b/src/gui/overlays/TypeinParamEditor.cpp index 70fead394fb..7ba59b8cd3b 100644 --- a/src/gui/overlays/TypeinParamEditor.cpp +++ b/src/gui/overlays/TypeinParamEditor.cpp @@ -116,7 +116,8 @@ void TypeinParamEditor::textEditorReturnKeyPressed(juce::TextEditor &te) { if (p && ms > 0) { - res = editor->setParameterModulationFromString(p, ms, te.getText().toStdString()); + res = + editor->setParameterModulationFromString(p, ms, modidx, te.getText().toStdString()); } else { diff --git a/src/gui/overlays/TypeinParamEditor.h b/src/gui/overlays/TypeinParamEditor.h index 10edfe8289b..2ddc4ea2427 100644 --- a/src/gui/overlays/TypeinParamEditor.h +++ b/src/gui/overlays/TypeinParamEditor.h @@ -46,10 +46,12 @@ struct TypeinParamEditor : public juce::Component, void setEditedParam(Parameter *pin) { p = pin; }; bool isMod{false}; modsources ms{ms_original}; - void setModulation(bool isMod, modsources ms) + int modidx{0}; + void setModulation(bool isMod, modsources ms, int modidx) { this->isMod = isMod; this->ms = ms; + this->modidx = modidx; } std::string mainLabel; diff --git a/src/headless/UnitTestsLUA.cpp b/src/headless/UnitTestsLUA.cpp index 24d5a6d3600..a0cbcfd5e3a 100644 --- a/src/headless/UnitTestsLUA.cpp +++ b/src/headless/UnitTestsLUA.cpp @@ -328,8 +328,9 @@ std::vector runFormula(FormulaModulatorStorage *fs, float dP release = true; es.released = release; - auto r = Surge::Formula::valueAt(iphase, phase, fs, &es); - res.push_back(formulaObservation(iphase, phase, r)); + float r[Surge::Formula::max_formula_outputs]; + Surge::Formula::valueAt(iphase, phase, fs, &es, r); + res.push_back(formulaObservation(iphase, phase, r[0])); phase += dPhase; if (phase > 1) { diff --git a/src/headless/UnitTestsMOD.cpp b/src/headless/UnitTestsMOD.cpp index fc709a267ff..1e121d6b543 100644 --- a/src/headless/UnitTestsMOD.cpp +++ b/src/headless/UnitTestsMOD.cpp @@ -988,10 +988,12 @@ TEST_CASE("High Low Latest Note Modulator in All Modes", "[mod]") // Assign highest note keytrack to any parameter. Lets do this with highest latest and // lowest - surge->setModulation(surge->storage.getPatch().scene[0].osc[0].pitch.id, ms_highest_key, + surge->setModulation(surge->storage.getPatch().scene[0].osc[0].pitch.id, ms_highest_key, 0, + 0.2); + surge->setModulation(surge->storage.getPatch().scene[0].osc[0].p[0].id, ms_latest_key, 0, + 0.2); + surge->setModulation(surge->storage.getPatch().scene[0].osc[0].p[1].id, ms_lowest_key, 0, 0.2); - surge->setModulation(surge->storage.getPatch().scene[0].osc[0].p[0].id, ms_latest_key, 0.2); - surge->setModulation(surge->storage.getPatch().scene[0].osc[0].p[1].id, ms_lowest_key, 0.2); return surge; }; @@ -1168,15 +1170,19 @@ TEST_CASE("High Low Latest with splits", "[mod]") // Assign highest note keytrack to any parameter. Lets do this with highest latest and // lowest - surge->setModulation(surge->storage.getPatch().scene[0].osc[0].pitch.id, ms_highest_key, + surge->setModulation(surge->storage.getPatch().scene[0].osc[0].pitch.id, ms_highest_key, 0, + 0.2); + surge->setModulation(surge->storage.getPatch().scene[0].osc[0].p[0].id, ms_latest_key, 0, + 0.2); + surge->setModulation(surge->storage.getPatch().scene[0].osc[0].p[1].id, ms_lowest_key, 0, 0.2); - surge->setModulation(surge->storage.getPatch().scene[0].osc[0].p[0].id, ms_latest_key, 0.2); - surge->setModulation(surge->storage.getPatch().scene[0].osc[0].p[1].id, ms_lowest_key, 0.2); - surge->setModulation(surge->storage.getPatch().scene[1].osc[0].pitch.id, ms_highest_key, + surge->setModulation(surge->storage.getPatch().scene[1].osc[0].pitch.id, ms_highest_key, 0, + 0.2); + surge->setModulation(surge->storage.getPatch().scene[1].osc[0].p[0].id, ms_latest_key, 0, + 0.2); + surge->setModulation(surge->storage.getPatch().scene[1].osc[0].p[1].id, ms_lowest_key, 0, 0.2); - surge->setModulation(surge->storage.getPatch().scene[1].osc[0].p[0].id, ms_latest_key, 0.2); - surge->setModulation(surge->storage.getPatch().scene[1].osc[0].p[1].id, ms_lowest_key, 0.2); return surge; }; diff --git a/src/python_bindings/surgepy.cpp b/src/python_bindings/surgepy.cpp index 3f47c0d0c63..b89f3549fcb 100644 --- a/src/python_bindings/surgepy.cpp +++ b/src/python_bindings/surgepy.cpp @@ -594,11 +594,13 @@ class SurgeSynthesizerWithPythonExtensions : public SurgeSynthesizer void setModulationPy(const SurgePyNamedParam &to, SurgePyModSource const &from, float amt) { - setModulation(to.getID().getSynthSideId(), (modsources)from.getModSource(), amt); + // FIXME - 4871 + setModulation(to.getID().getSynthSideId(), (modsources)from.getModSource(), amt, 0); } float getModulationPy(const SurgePyNamedParam &to, const SurgePyModSource &from) { - return getModulation(to.getID().getSynthSideId(), (modsources)from.getModSource()); + // FIXME - 4871 + return getModulation(to.getID().getSynthSideId(), (modsources)from.getModSource(), 0); } bool isValidModulationPy(const SurgePyNamedParam &to, const SurgePyModSource &from) { @@ -606,7 +608,8 @@ class SurgeSynthesizerWithPythonExtensions : public SurgeSynthesizer } bool isActiveModulationPy(const SurgePyNamedParam &to, const SurgePyModSource &from) { - return isActiveModulation(to.getID().getSynthSideId(), (modsources)from.getModSource()); + // FIXME - 4871 + return isActiveModulation(to.getID().getSynthSideId(), (modsources)from.getModSource(), 0); } bool isBipolarModulationPy(const SurgePyModSource &from) { @@ -729,7 +732,8 @@ class SurgeSynthesizerWithPythonExtensions : public SurgeSynthesizer r.source = SurgePyModSource((modsources)gm.source_id); r.dest = surgePyNamedParamById(gm.destination_id); r.depth = gm.depth; - r.normalizedDepth = getModulation(gm.destination_id, (modsources)gm.source_id); + // FIXME 4871 + r.normalizedDepth = getModulation(gm.destination_id, (modsources)gm.source_id, 0); gmr.append(r); } @@ -748,9 +752,10 @@ class SurgeSynthesizerWithPythonExtensions : public SurgeSynthesizer r.dest = surgePyNamedParamById(gm.destination_id + storage.getPatch().scene_start[sc]); r.depth = gm.depth; + // FIXME 4871 r.normalizedDepth = getModulation(gm.destination_id + storage.getPatch().scene_start[sc], - (modsources)gm.source_id); + (modsources)gm.source_id, 0); sms.append(r); } ts["scene"] = sms; @@ -764,9 +769,10 @@ class SurgeSynthesizerWithPythonExtensions : public SurgeSynthesizer r.dest = surgePyNamedParamById(gm.destination_id + storage.getPatch().scene_start[sc]); r.depth = gm.depth; + // FIXME 4871 r.normalizedDepth = getModulation(gm.destination_id + storage.getPatch().scene_start[sc], - (modsources)gm.source_id); + (modsources)gm.source_id, 0); smv.append(r); } ts["voice"] = smv;