diff --git a/changelog.md b/changelog.md index 44c5aeffc..bb28e58c5 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,8 @@ +### September 5, 2024 - V1.9.1. + +- Add full realtime visual modulation for the graphs (so they show what the audio engine is actually doing if you f.e. set LFO to filter freq). +- Also added the option to back down on realtime repaint (nothing / params only / graphs) since this is quite cpu expensive. + ### August 29, 2024 - V1.9.0. - Load/save/init/clear patch split out to separate buttons again. diff --git a/demos/host_test/visual_modulation_demo_no_automation_reaper_clap.rpp b/demos/host_test/visual_modulation_demo_no_automation_reaper_clap.rpp new file mode 100644 index 000000000..1f40ac435 --- /dev/null +++ b/demos/host_test/visual_modulation_demo_no_automation_reaper_clap.rpp @@ -0,0 +1,3970 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 129 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + ENVFADESZ10 40 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + PANLAWFLAGS 3 + CURSOR 4.90909090909091 + ZOOM 69.44444444444446 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 1 + LOOPGRAN 0 4 + RECORD_PATH "Media" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 110 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_PANLAWFLAGS 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + + > + FLOATPOS 0 0 0 0 + FXID {BCFBC2C5-3B24-476D-906F-4314F8B2A251} + WAK 0 0 + > + + > + > +> diff --git a/demos/host_test/visual_modulation_demo_no_automation_reaper_vst3.rpp b/demos/host_test/visual_modulation_demo_no_automation_reaper_vst3.rpp new file mode 100644 index 000000000..bcc542afb --- /dev/null +++ b/demos/host_test/visual_modulation_demo_no_automation_reaper_vst3.rpp @@ -0,0 +1,3315 @@ + + RIPPLE 0 + GROUPOVERRIDE 0 0 0 + AUTOXFADE 1 + ENVATTACH 3 + POOLEDENVATTACH 0 + MIXERUIFLAGS 11 48 + ENVFADESZ10 40 + PEAKGAIN 1 + FEEDBACK 0 + PANLAW 1 + PROJOFFS 0 0 0 + MAXPROJLEN 0 0 + GRID 3199 8 1 8 1 0 0 0 + TIMEMODE 1 5 -1 30 0 0 -1 + VIDEO_CONFIG 0 0 256 + PANMODE 3 + PANLAWFLAGS 3 + CURSOR 4.90909090909091 + ZOOM 69.44444444444446 0 0 + VZOOMEX 6 0 + USE_REC_CFG 0 + RECMODE 1 + SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0 + LOOP 1 + LOOPGRAN 0 4 + RECORD_PATH "Media" "" + + + RENDER_FILE "" + RENDER_PATTERN "" + RENDER_FMT 0 2 0 + RENDER_1X 0 + RENDER_RANGE 1 0 0 18 1000 + RENDER_RESAMPLE 3 0 1 + RENDER_ADDTOPROJ 0 + RENDER_STEMS 0 + RENDER_DITHER 0 + TIMELOCKMODE 1 + TEMPOENVLOCKMODE 1 + ITEMMIX 1 + DEFPITCHMODE 589824 0 + TAKELANE 1 + SAMPLERATE 44100 0 0 + + LOCK 1 + + GLOBAL_AUTO -1 + TEMPO 110 4 4 + PLAYRATE 1 0 0.25 4 + SELECTION 0 0 + SELECTION2 0 0 + MASTERAUTOMODE 0 + MASTERTRACKHEIGHT 0 0 + MASTERPEAKCOL 16576 + MASTERMUTESOLO 0 + MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 + MASTERHWOUT 0 0 1 0 0 0 0 -1 + MASTER_NCH 2 2 + MASTER_VOLUME 1 0 -1 -1 1 + MASTER_PANMODE 3 + MASTER_PANLAWFLAGS 3 + MASTER_FX 1 + MASTER_SEL 0 + + + + + FLOATPOS 0 0 0 0 + FXID {C06EB589-F71B-4701-BEA4-732E2EC51756} + WAK 0 0 + > + + > + > +> diff --git a/macos/Info.plist b/macos/Info.plist index 60e1826e1..63c5a5e67 100644 --- a/macos/Info.plist +++ b/macos/Info.plist @@ -20,9 +20,9 @@ CFBundleSignature ???? CFBundleShortVersionString - 1.9.0 + 1.9.1 CFBundleVersion - 1.9.0 + 1.9.1 NSHumanReadableCopyright NSHighResolutionCapable diff --git a/manual.md b/manual.md index 84a06d5b8..5d6629766 100644 --- a/manual.md +++ b/manual.md @@ -12,6 +12,18 @@ It's fully resizable by scaling (by dragging the bottom right corner) and also r A knob with a circle in it or a slider with a small dot in it means it can be modulated by the CV matrices.
Hover over a parameter to see a more detailed description. +### Realtime visualization + +By default the UI shows the realtime state of the audio engine for both graphs and parameters.
+Since this is quite CPU expensive (sorry i didn't do fancy gpu offloading) there's an option to back down +on the amount of realtime repainting, see Visuals dropdown in the master section. + +- Parameter modulation global or single active voice paints the exact audio engine state. +- Parameter modulation multiple active voices paints the minimum and maximum values across all active voices. +- Graph modulation global or single active voice paints the exact audio engine state. +- Graph modulation multiple active voices paints the most recent voice state. +- Graph modulation zero active voices paints the static automation state. + ### Drag-and-drop to mod matrix support - Drag any parameter that can act as a mod source (Aux N, Mod Wheel etc) by dragging the parameter label diff --git a/param_reference.html b/param_reference.html index 2ec032063..567a1d012 100644 --- a/param_reference.html +++ b/param_reference.html @@ -1,10 +1,10 @@ -Firefly Synth 1.9.0 +Firefly Synth 1.9.1 -

Firefly Synth 1.9.0

+

Firefly Synth 1.9.1

Module Overview

@@ -261,7 +261,7 @@

Master

- + @@ -278,7 +278,7 @@

Master

- + @@ -295,7 +295,7 @@

Master

- + @@ -311,6 +311,23 @@

Master

+ + + + + + + + + + + + + + + + +
Factory preset.
MIDI SmoothingMIDI Smooth Smoothing Yes Instance Smoothing MIDI controller changes.
BPM SmoothingBPM Smooth Smoothing Yes Instance Smoothing host BPM parameter changes. Affects tempo-synced delay lines.
Automation SmoothingAutomation Smooth Smoothing Yes Instance
Smoothing automation parameter changes.
Realtime VisualsVisualsYesInstance1InputBlockNoneNoneParams And GraphsParams And GraphsN/A
Realtime visualization mode

GCV-CV

diff --git a/plugin_base/src/plugin_base.clap/plugin_base.clap/pb_plugin.cpp b/plugin_base/src/plugin_base.clap/plugin_base.clap/pb_plugin.cpp index 65e322861..6b048c621 100644 --- a/plugin_base/src/plugin_base.clap/plugin_base.clap/pb_plugin.cpp +++ b/plugin_base/src/plugin_base.clap/plugin_base.clap/pb_plugin.cpp @@ -64,7 +64,7 @@ pb_plugin:: { PB_LOG_FUNC_ENTRY_EXIT(); stopTimer(); - _gui_state.remove_any_listener(this); + _automation_state.remove_any_listener(this); MTS_DeregisterClient(_mts_client); } @@ -77,21 +77,21 @@ _mts_client(MTS_RegisterClient()), _desc(std::make_unique(topo, this)), _splice_engine(_desc.get(), false, forward_thread_pool_voice_processor, this), _extra_state(gui_extra_state_keyset(*_desc->plugin)), -_gui_state(_desc.get(), true), +_automation_state(_desc.get(), true), _to_gui_events(std::make_unique(default_q_size)), _to_audio_events(std::make_unique(default_q_size)), -_mod_indicator_queue(std::make_unique(default_q_size)) +_modulation_output_queue(std::make_unique(default_q_size)) { PB_LOG_FUNC_ENTRY_EXIT(); - _gui_state.add_any_listener(this); - _mod_indicator_states.reserve(default_q_size); + _automation_state.add_any_listener(this); + _modulation_outputs.reserve(default_q_size); _block_automation_seen.resize(_splice_engine.state().desc().param_count); } void pb_plugin::gui_param_begin_changes(int index) { - _gui_state.begin_undo_region(); + _automation_state.begin_undo_region(); push_to_audio(index, sync_event_type::begin_edit); } @@ -99,16 +99,16 @@ void pb_plugin::gui_param_end_changes(int index) { push_to_audio(index, sync_event_type::end_edit); - _gui_state.end_undo_region("Change", _gui_state.desc().params[index]->full_name); + _automation_state.end_undo_region("Change", _automation_state.desc().params[index]->full_name); } void pb_plugin::param_state_changed(int index, plain_value plain) { if(_inside_timer_callback) return; - if (_gui_state.desc().params[index]->param->dsp.direction == param_direction::output) return; + if (_automation_state.desc().params[index]->param->dsp.direction == param_direction::output) return; push_to_audio(index, plain); - _gui_state.set_plain_at_index(index, plain); + _automation_state.set_plain_at_index(index, plain); } bool @@ -118,7 +118,7 @@ pb_plugin::init() noexcept // Need to start timer on the main thread. // Constructor is not guaranteed to run there. - startTimerHz(60); + startTimerHz(20); return true; } @@ -128,13 +128,13 @@ pb_plugin::timerCallback() sync_event sevent; _inside_timer_callback = true; while (_to_gui_events->try_dequeue(sevent)) - _gui_state.set_plain_at_index(sevent.index, sevent.plain); + _automation_state.set_plain_at_index(sevent.index, sevent.plain); - mod_indicator_state mostate; - _mod_indicator_states.clear(); - while (_mod_indicator_queue->try_dequeue(mostate)) - _mod_indicator_states.push_back(mostate); - if (_gui) _gui->mod_indicator_states_changed(); + modulation_output mod_output; + _modulation_outputs.clear(); + while (_modulation_output_queue->try_dequeue(mod_output)) + _modulation_outputs.push_back(mod_output); + if (_gui) _gui->modulation_outputs_changed(); _inside_timer_callback = false; } @@ -148,7 +148,7 @@ pb_plugin::stateSave(clap_ostream const* stream) noexcept // don't bother with that and just write byte-for-byte int written = 1; int total_written = 0; - std::vector data(plugin_io_save_all_state(_gui_state, &_extra_state, false)); + std::vector data(plugin_io_save_all_state(_automation_state, &_extra_state, false)); while(written == 1 && total_written < data.size()) { written = stream->write(stream, data.data() + total_written, 1); @@ -172,15 +172,15 @@ pb_plugin::stateLoad(clap_istream const* stream) noexcept data.push_back(byte); } while(true); - _gui_state.begin_undo_region(); - if (!plugin_io_load_all_state(data, _gui_state, &_extra_state, false).ok()) + _automation_state.begin_undo_region(); + if (!plugin_io_load_all_state(data, _automation_state, &_extra_state, false).ok()) { - _gui_state.discard_undo_region(); + _automation_state.discard_undo_region(); return false; } for (int p = 0; p < _splice_engine.state().desc().param_count; p++) - gui_param_changed(p, _gui_state.get_plain_at_index(p)); - _gui_state.discard_undo_region(); + gui_param_changed(p, _automation_state.get_plain_at_index(p)); + _automation_state.discard_undo_region(); _splice_engine.automation_state_dirty(); return true; } @@ -276,7 +276,7 @@ bool pb_plugin::guiCreate(char const* api, bool is_floating) noexcept { PB_LOG_FUNC_ENTRY_EXIT(); - _gui = std::make_unique(&_gui_state, &_extra_state, &_mod_indicator_states); + _gui = std::make_unique(&_automation_state, &_extra_state, &_modulation_outputs); return true; } @@ -344,7 +344,9 @@ pb_plugin::push_to_gui(int index, clap_value clap) e.index = index; e.type = sync_event_type::value_changing; e.plain = topo.domain.normalized_to_plain(clap_to_normalized(topo, clap)); - _to_gui_events->enqueue(e); + bool enqueued = _to_gui_events->try_enqueue(e); + assert(enqueued); + (void)enqueued; } std::int32_t @@ -373,8 +375,8 @@ pb_plugin::paramsValue(clap_id param_id, double* value) noexcept // need to pull in any outstanding audio-to-ui-values from // the queue before we can report the current value to the host! timerCallback(); - auto const& topo = *_gui_state.desc().param_at_tag(param_id).param; - auto normalized = _gui_state.get_normalized_at_tag(param_id); + auto const& topo = *_automation_state.desc().param_at_tag(param_id).param; + auto normalized = _automation_state.get_normalized_at_tag(param_id); *value = normalized_to_clap(topo, normalized).value(); return true; } @@ -456,7 +458,8 @@ pb_plugin::paramsFlush(clap_input_events const* in, clap_output_events const* ou auto normalized = clap_to_normalized(param, clap_value(event->value)); if (main_thread) { - _gui_state.set_normalized_at_index(index, normalized); + _automation_state.set_normalized_at_index(index, normalized); + if(_gui) _gui->automation_state_changed(index, normalized); push_to_audio(index, param.domain.normalized_to_plain(normalized)); } else { @@ -782,12 +785,15 @@ pb_plugin::process(clap_process const* process) noexcept auto const& out_event = block.events.output_params[e]; to_gui_event.index = out_event.param; to_gui_event.plain = _splice_engine.state().desc().normalized_to_plain_at_index(out_event.param, out_event.normalized); - _to_gui_events->enqueue(to_gui_event); + bool enqueued = _to_gui_events->try_enqueue(to_gui_event); + assert(enqueued); + (void)enqueued; } - // modulation indicators - for (int e = 0; e < block.events.mod_indicator_states.size(); e++) - _mod_indicator_queue->enqueue(block.events.mod_indicator_states[e]); + // modulation outputs - dont check if it happened + // gui is written to deal with missing events, and there's a lot of them + for (int e = 0; e < block.events.modulation_outputs.size(); e++) + _modulation_output_queue->try_enqueue(block.events.modulation_outputs[e]); _splice_engine.release_block(); return CLAP_PROCESS_CONTINUE; diff --git a/plugin_base/src/plugin_base.clap/plugin_base.clap/pb_plugin.hpp b/plugin_base/src/plugin_base.clap/plugin_base.clap/pb_plugin.hpp index 7a6191259..e5e063970 100644 --- a/plugin_base/src/plugin_base.clap/plugin_base.clap/pb_plugin.hpp +++ b/plugin_base/src/plugin_base.clap/plugin_base.clap/pb_plugin.hpp @@ -36,7 +36,7 @@ public any_state_listener, public gui_param_listener { typedef moodycamel::ReaderWriterQueue event_queue; - typedef moodycamel::ReaderWriterQueue mod_indicator_queue; + typedef moodycamel::ReaderWriterQueue modulation_output_queue; // MTS-ESP support MTSClient* _mts_client = {}; @@ -45,14 +45,14 @@ public gui_param_listener std::unique_ptr _desc; plugin_splice_engine _splice_engine; extra_state _extra_state; - plugin_state _gui_state = {}; + plugin_state _automation_state = {}; std::atomic _is_active = {}; std::unique_ptr _gui = {}; std::vector _block_automation_seen = {}; std::unique_ptr _to_gui_events = {}; std::unique_ptr _to_audio_events = {}; - std::unique_ptr _mod_indicator_queue = {}; - std::vector _mod_indicator_states = {}; + std::vector _modulation_outputs = {}; + std::unique_ptr _modulation_output_queue = {}; // see param_state_changed and timerCallback() // and vst3 pb_controller _inside_set_param_normalized diff --git a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_component.cpp b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_component.cpp index 352fe1129..144f8c8ae 100644 --- a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_component.cpp +++ b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_component.cpp @@ -39,10 +39,10 @@ _splice_engine(_desc.get(), false, nullptr, nullptr) } } - // fetch mod indicator param tags - _mod_indicator_count_param_tag = stable_hash(mod_indicator_count_param_guid); - for (int i = 0; i < mod_indicator_output_param_count; i++) - _mod_indicator_param_tags[i] = stable_hash(mod_indicator_param_guids[i]); + // fetch mod output param tags + _mod_output_count_param_tag = stable_hash(modulation_output_count_param_guid); + for (int i = 0; i < modulation_output_param_count; i++) + _mod_output_param_tags[i] = stable_hash(modulation_output_param_guids[i]); } tresult PLUGIN_API @@ -232,19 +232,19 @@ pb_component::process(ProcessData& data) queue->addPoint(0, event.normalized.value(), unused_index); } - // module modulation indicators + // module modulation outputs unused_index = 0; if (data.outputParameterChanges) { - auto num_mod_indicators = block.events.mod_indicator_states.size(); - num_mod_indicators = std::min((int)num_mod_indicators, mod_indicator_output_param_count); - queue = data.outputParameterChanges->addParameterData(_mod_indicator_count_param_tag, unused_index); - queue->addPoint(0, *reinterpret_cast(&num_mod_indicators), unused_index); - for (int e = 0; e < block.events.mod_indicator_states.size(); e++) + auto num_modulation_outputs = block.events.modulation_outputs.size(); + num_modulation_outputs = std::min((int)num_modulation_outputs, modulation_output_param_count); + queue = data.outputParameterChanges->addParameterData(_mod_output_count_param_tag, unused_index); + queue->addPoint(0, *reinterpret_cast(&num_modulation_outputs), unused_index); + for (int e = 0; e < block.events.modulation_outputs.size(); e++) { - auto const& this_mod_indicator_state = block.events.mod_indicator_states[e]; - queue = data.outputParameterChanges->addParameterData(_mod_indicator_param_tags[e], unused_index); - queue->addPoint(0, *reinterpret_cast(&this_mod_indicator_state.packed), unused_index); + auto const& this_modulation_outputs = block.events.modulation_outputs[e]; + queue = data.outputParameterChanges->addParameterData(_mod_output_param_tags[e], unused_index); + queue->addPoint(0, *reinterpret_cast(&this_modulation_outputs.packed), unused_index); } } diff --git a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_component.hpp b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_component.hpp index ffd3dad98..0f442e759 100644 --- a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_component.hpp +++ b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_component.hpp @@ -28,9 +28,9 @@ public Steinberg::Vst::AudioEffect { std::vector _scratch_out_l; std::vector _scratch_out_r; - // for modulation indicators - int _mod_indicator_count_param_tag = -1; - std::array _mod_indicator_param_tags = {}; + // for modulation outputs + int _mod_output_count_param_tag = -1; + std::array _mod_output_param_tags = {}; public: PB_PREVENT_ACCIDENTAL_COPY(pb_component); diff --git a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_controller.cpp b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_controller.cpp index 38a447488..4387b9c56 100644 --- a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_controller.cpp +++ b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_controller.cpp @@ -30,39 +30,39 @@ pb_basic_config::instance() pb_controller:: ~pb_controller() -{ _gui_state.remove_any_listener(this); } +{ _automation_state.remove_any_listener(this); } pb_controller:: pb_controller(plugin_topo const* topo): _desc(std::make_unique(topo, this)), -_gui_state(_desc.get(), true), +_automation_state(_desc.get(), true), _extra_state(gui_extra_state_keyset(*_desc->plugin)) { PB_LOG_FUNC_ENTRY_EXIT(); - _gui_state.add_any_listener(this); + _automation_state.add_any_listener(this); - // fetch mod indicator param tags - _mod_indicator_states_to_gui.resize(mod_indicator_output_param_count); - _mod_indicator_count_param_tag = stable_hash(mod_indicator_count_param_guid); - for (int i = 0; i < mod_indicator_output_param_count; i++) + // fetch mod output param tags + _modulation_outputs_to_gui.resize(modulation_output_param_count); + _modulation_output_count_param_tag = stable_hash(modulation_output_count_param_guid); + for (int i = 0; i < modulation_output_param_count; i++) { - _mod_indicator_param_tags[i] = stable_hash(mod_indicator_param_guids[i]); - _tag_to_mod_indicator_index[_mod_indicator_param_tags[i]] = i; + _modulation_output_param_tags[i] = stable_hash(modulation_output_param_guids[i]); + _tag_to_modulation_output_index[_modulation_output_param_tags[i]] = i; } } void pb_controller::gui_param_begin_changes(int index) { - _gui_state.begin_undo_region(); - beginEdit(gui_state().desc().param_mappings.index_to_tag[index]); + _automation_state.begin_undo_region(); + beginEdit(automation_state().desc().param_mappings.index_to_tag[index]); } void pb_controller::gui_param_end_changes(int index) { - endEdit(gui_state().desc().param_mappings.index_to_tag[index]); - gui_state().end_undo_region("Change", gui_state().desc().params[index]->full_name); + endEdit(automation_state().desc().param_mappings.index_to_tag[index]); + automation_state().end_undo_region("Change", automation_state().desc().params[index]->full_name); } IPlugView* PLUGIN_API @@ -70,14 +70,14 @@ pb_controller::createView(char const* name) { PB_LOG_FUNC_ENTRY_EXIT(); if (ConstString(name) != ViewType::kEditor) return nullptr; - return _editor = new pb_editor(this, &_mod_indicator_states_to_gui); + return _editor = new pb_editor(this, &_modulation_outputs_to_gui); } tresult PLUGIN_API pb_controller::getState(IBStream* state) { PB_LOG_FUNC_ENTRY_EXIT(); - std::vector data(plugin_io_save_extra_state(*_gui_state.desc().plugin, _extra_state)); + std::vector data(plugin_io_save_extra_state(*_automation_state.desc().plugin, _extra_state)); return state->write(data.data(), data.size()); } @@ -85,7 +85,7 @@ tresult PLUGIN_API pb_controller::setState(IBStream* state) { PB_LOG_FUNC_ENTRY_EXIT(); - if (!plugin_io_load_extra_state(*_gui_state.desc().plugin, load_ibstream(state), _extra_state).ok()) + if (!plugin_io_load_extra_state(*_automation_state.desc().plugin, load_ibstream(state), _extra_state).ok()) return kResultFalse; return kResultOk; } @@ -94,15 +94,15 @@ tresult PLUGIN_API pb_controller::setComponentState(IBStream* state) { PB_LOG_FUNC_ENTRY_EXIT(); - gui_state().begin_undo_region(); - if (!plugin_io_load_instance_state(load_ibstream(state), gui_state(), false).ok()) + automation_state().begin_undo_region(); + if (!plugin_io_load_instance_state(load_ibstream(state), automation_state(), false).ok()) { - gui_state().discard_undo_region(); + automation_state().discard_undo_region(); return kResultFalse; } - for (int p = 0; p < gui_state().desc().param_count; p++) - gui_param_changed(p, gui_state().get_plain_at_index(p)); - gui_state().discard_undo_region(); + for (int p = 0; p < automation_state().desc().param_count; p++) + gui_param_changed(p, automation_state().get_plain_at_index(p)); + automation_state().discard_undo_region(); return kResultOk; } @@ -117,47 +117,50 @@ pb_controller::setParamNormalized(ParamID tag, ParamValue value) } // fake midi params are not mapped - auto mapping_iter = gui_state().desc().param_mappings.tag_to_index.find(tag); - if(mapping_iter != gui_state().desc().param_mappings.tag_to_index.end()) - _gui_state.set_normalized_at_index(mapping_iter->second, normalized_value(value)); + auto mapping_iter = automation_state().desc().param_mappings.tag_to_index.find(tag); + if (mapping_iter != automation_state().desc().param_mappings.tag_to_index.end()) + { + _automation_state.set_normalized_at_index(mapping_iter->second, normalized_value(value)); + if (_editor) _editor->automation_state_changed(mapping_iter->second, normalized_value(value)); + } - // mod indicator support + // modulation output support // this is a bit of a cop out but at least it should be working without resorting to messaging // upon receiving the "count" param, update the count // upon receiving any other param, set the fill bit // whenever the first N consecutive fill bits >= count, repaint and reset - bool needs_mod_indicator_rescan = false; - if (tag == _mod_indicator_count_param_tag) + bool needs_mod_output_rescan = false; + if (tag == _modulation_output_count_param_tag) { - _mod_indicator_count = *reinterpret_cast(&value); - needs_mod_indicator_rescan = true; + _modulation_output_count = *reinterpret_cast(&value); + needs_mod_output_rescan = true; } - auto mod_ind_iter = _tag_to_mod_indicator_index.find(tag); - if (mod_ind_iter != _tag_to_mod_indicator_index.end()) + auto mod_output_iter = _tag_to_modulation_output_index.find(tag); + if (mod_output_iter != _tag_to_modulation_output_index.end()) { - _mod_indicator_param_set[mod_ind_iter->second] = true; - _mod_indicator_states_from_audio[mod_ind_iter->second].packed = *reinterpret_cast(&value); - needs_mod_indicator_rescan = true; + _modulation_output_param_set[mod_output_iter->second] = true; + _modulation_outputs_from_audio[mod_output_iter->second].packed = *reinterpret_cast(&value); + needs_mod_output_rescan = true; } - if (needs_mod_indicator_rescan) + if (needs_mod_output_rescan) { bool filled_to_count = true; - for (int i = 0; i < _mod_indicator_count; i++) - if (!_mod_indicator_param_set[i]) + for (int i = 0; i < _modulation_output_count; i++) + if (!_modulation_output_param_set[i]) { filled_to_count = false; break; } if (filled_to_count) { - _mod_indicator_param_set.fill(false); - _mod_indicator_states_to_gui.clear(); - _mod_indicator_states_to_gui.insert( - _mod_indicator_states_to_gui.begin(), - _mod_indicator_states_from_audio.begin(), - _mod_indicator_states_from_audio.begin() + _mod_indicator_count); - _mod_indicator_count = 0; - if (_editor) _editor->mod_indicator_states_changed(); + _modulation_output_param_set.fill(false); + _modulation_outputs_to_gui.clear(); + _modulation_outputs_to_gui.insert( + _modulation_outputs_to_gui.begin(), + _modulation_outputs_from_audio.begin(), + _modulation_outputs_from_audio.begin() + _modulation_output_count); + _modulation_output_count = 0; + if (_editor) _editor->modulation_outputs_changed(); } } @@ -179,9 +182,9 @@ void pb_controller::param_state_changed(int index, plain_value plain) { if(_inside_set_param_normalized) return; - if (_gui_state.desc().params[index]->param->dsp.direction == param_direction::output) return; - int tag = gui_state().desc().param_mappings.index_to_tag[index]; - auto normalized = gui_state().desc().plain_to_normalized_at_index(index, plain).value(); + if (_automation_state.desc().params[index]->param->dsp.direction == param_direction::output) return; + int tag = automation_state().desc().param_mappings.index_to_tag[index]; + auto normalized = automation_state().desc().plain_to_normalized_at_index(index, plain).value(); // Per-the-spec we should not have to call setParamNormalized here but not all hosts agree. performEdit(tag, normalized); @@ -248,9 +251,9 @@ pb_controller::initialize(FUnknown* context) if(EditController::initialize(context) != kResultTrue) return kResultFalse; - for(int m = 0; m < gui_state().desc().modules.size(); m++) + for(int m = 0; m < automation_state().desc().modules.size(); m++) { - auto const& module = gui_state().desc().modules[m]; + auto const& module = automation_state().desc().modules[m]; UnitInfo unit_info; unit_info.id = unit_id++; unit_info.parentUnitId = kRootUnitId; @@ -279,15 +282,15 @@ pb_controller::initialize(FUnknown* context) param_info.stepCount = 0; if (!param.param->domain.is_real()) param_info.stepCount = param.param->domain.max - param.param->domain.min; - parameters.addParameter(new pb_param(&_gui_state, module.params[p].param, module.params[p].info.global, param_info)); + parameters.addParameter(new pb_param(&_automation_state, module.params[p].param, module.params[p].info.global, param_info)); } } // be sure to append fake midi params *after* the real ones // to not mess up the tag to index mapping - for (int m = 0; m < gui_state().desc().modules.size(); m++) + for (int m = 0; m < automation_state().desc().modules.size(); m++) { - auto const& module = gui_state().desc().modules[m]; + auto const& module = automation_state().desc().modules[m]; for (int ms = 0; ms < module.midi_sources.size(); ms++) { ParameterInfo param_info = {}; @@ -302,24 +305,24 @@ pb_controller::initialize(FUnknown* context) } } - // add fake mod indicator parameters - ParameterInfo mod_indicator_count_param = {}; - mod_indicator_count_param.unitId = kRootUnitId; - mod_indicator_count_param.defaultNormalizedValue = 0; - mod_indicator_count_param.stepCount = mod_indicator_output_param_count + 1; - mod_indicator_count_param.id = stable_hash(mod_indicator_count_param_guid); - mod_indicator_count_param.flags = ParameterInfo::kIsReadOnly | ParameterInfo::kIsHidden; - parameters.addParameter(new Parameter(mod_indicator_count_param)); + // add fake mod output parameters + ParameterInfo mod_output_count_param = {}; + mod_output_count_param.unitId = kRootUnitId; + mod_output_count_param.defaultNormalizedValue = 0; + mod_output_count_param.stepCount = modulation_output_param_count + 1; + mod_output_count_param.id = stable_hash(modulation_output_count_param_guid); + mod_output_count_param.flags = ParameterInfo::kIsReadOnly | ParameterInfo::kIsHidden; + parameters.addParameter(new Parameter(mod_output_count_param)); - for (int i = 0; i < mod_indicator_output_param_count; i++) + for (int i = 0; i < modulation_output_param_count; i++) { - ParameterInfo mod_indicator_param = {}; - mod_indicator_param.stepCount = 0; - mod_indicator_param.unitId = kRootUnitId; - mod_indicator_param.defaultNormalizedValue = 0; - mod_indicator_param.id = stable_hash(mod_indicator_param_guids[i]); - mod_indicator_param.flags = ParameterInfo::kIsReadOnly | ParameterInfo::kIsHidden; - parameters.addParameter(new Parameter(mod_indicator_param)); + ParameterInfo mod_output_param = {}; + mod_output_param.stepCount = 0; + mod_output_param.unitId = kRootUnitId; + mod_output_param.defaultNormalizedValue = 0; + mod_output_param.id = stable_hash(modulation_output_param_guids[i]); + mod_output_param.flags = ParameterInfo::kIsReadOnly | ParameterInfo::kIsHidden; + parameters.addParameter(new Parameter(mod_output_param)); } // make sure no clashes diff --git a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_controller.hpp b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_controller.hpp index db6c55904..9776e0750 100644 --- a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_controller.hpp +++ b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_controller.hpp @@ -35,18 +35,18 @@ public Steinberg::Vst::EditControllerEx1 // needs to be first, everyone else needs it std::unique_ptr _desc; pb_editor* _editor = {}; - plugin_state _gui_state = {}; + plugin_state _automation_state = {}; extra_state _extra_state; std::map _midi_id_to_param = {}; - // modulation indicator states - int _mod_indicator_count = 0; - int _mod_indicator_count_param_tag = -1; - std::map _tag_to_mod_indicator_index = {}; - std::array _mod_indicator_param_set = {}; - std::array _mod_indicator_param_tags = {}; - std::vector _mod_indicator_states_to_gui = {}; - std::array _mod_indicator_states_from_audio = {}; + // modulation output states + int _modulation_output_count = 0; + int _modulation_output_count_param_tag = -1; + std::map _tag_to_modulation_output_index = {}; + std::array _modulation_output_param_set = {}; + std::array _modulation_output_param_tags = {}; + std::vector _modulation_outputs_to_gui = {}; + std::array _modulation_outputs_from_audio = {}; // see param_state_changed and setParamNormalized // when host comes at us with an automation value, that is @@ -71,8 +71,8 @@ public Steinberg::Vst::EditControllerEx1 REFCOUNT_METHODS(EditControllerEx1) PB_PREVENT_ACCIDENTAL_COPY(pb_controller); - plugin_state& gui_state() { return _gui_state; } extra_state& extra_state_() { return _extra_state; } + plugin_state& automation_state() { return _automation_state; } void editorDestroyed(Steinberg::Vst::EditorView*) override { _editor = nullptr; } std::unique_ptr context_menu(int param_id) const override; diff --git a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_editor.cpp b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_editor.cpp index ce863df27..00c2358f2 100644 --- a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_editor.cpp +++ b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_editor.cpp @@ -10,9 +10,9 @@ using namespace Steinberg; namespace plugin_base::vst3 { pb_editor:: -pb_editor(pb_controller* controller, std::vector* mod_indicator_states) : +pb_editor(pb_controller* controller, std::vector* modulation_outputs) : EditorView(controller), _controller(controller), -_gui(std::make_unique(&controller->gui_state(), &controller->extra_state_(), mod_indicator_states)) {} +_gui(std::make_unique(&controller->automation_state(), &controller->extra_state_(), modulation_outputs)) {} tresult PLUGIN_API pb_editor::getSize(ViewRect* new_size) @@ -50,7 +50,7 @@ pb_editor::checkSizeConstraint(ViewRect* new_size) { if (!_gui.get()) return kResultTrue; auto settings = _gui->get_lnf()->global_settings(); - auto const& topo = *_controller->gui_state().desc().plugin; + auto const& topo = *_controller->automation_state().desc().plugin; bool is_fx = topo.type == plugin_type::fx; int min_width = (int)(settings.get_default_width(is_fx) * settings.min_scale * _gui->get_system_dpi_scale()); int max_width = (int)(settings.get_default_width(is_fx) * settings.max_scale * _gui->get_system_dpi_scale()); diff --git a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_editor.hpp b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_editor.hpp index 32bb9d8cf..4a2b69028 100644 --- a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_editor.hpp +++ b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/pb_editor.hpp @@ -21,7 +21,7 @@ public Steinberg::IPlugViewContentScaleSupport pb_controller* const _controller = {}; public: - pb_editor(pb_controller* controller, std::vector* mod_indicator_states); + pb_editor(pb_controller* controller, std::vector* outputs); PB_PREVENT_ACCIDENTAL_COPY(pb_editor); #if (defined __linux__) || (defined __FreeBSD__) @@ -41,7 +41,8 @@ public Steinberg::IPlugViewContentScaleSupport Steinberg::uint32 PLUGIN_API release() override { return EditorView::release(); } Steinberg::tresult PLUGIN_API queryInterface(Steinberg::TUID const iid, void** obj) override; - void mod_indicator_states_changed() { if (_gui) _gui->mod_indicator_states_changed(); } + void modulation_outputs_changed() { if (_gui) _gui->modulation_outputs_changed(); } + void automation_state_changed(int param_index, normalized_value normalized) { if (_gui) _gui->automation_state_changed(param_index, normalized); } }; } diff --git a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/utility.cpp b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/utility.cpp index 4afc3bf24..ad7750d05 100644 --- a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/utility.cpp +++ b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/utility.cpp @@ -24,8 +24,8 @@ load_ibstream(IBStream* stream) return data; } -extern char const* mod_indicator_count_param_guid = "{0ED52D09-8FD0-4A4A-8D71-BEDED941ADAF}"; -char const* mod_indicator_param_guids[mod_indicator_output_param_count] = { +extern char const* modulation_output_count_param_guid = "{0ED52D09-8FD0-4A4A-8D71-BEDED941ADAF}"; +char const* modulation_output_param_guids[modulation_output_param_count] = { "{00F56C57-6B95-4117-8DA1-D011CC0CC042}", "{2F254575-DBDC-4916-9E37-58B59A606DBD}", "{05FBD39B-9C33-4D4F-8C12-287F4F878645}", diff --git a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/utility.hpp b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/utility.hpp index 378c31929..9d16cefa1 100644 --- a/plugin_base/src/plugin_base.vst3/plugin_base.vst3/utility.hpp +++ b/plugin_base/src/plugin_base.vst3/plugin_base.vst3/utility.hpp @@ -7,9 +7,9 @@ namespace plugin_base::vst3 { // fake these as output params to prevent having to deal with output events -inline int constexpr mod_indicator_output_param_count = 4096; -extern char const* mod_indicator_count_param_guid; -extern char const* mod_indicator_param_guids[mod_indicator_output_param_count]; +inline int constexpr modulation_output_param_count = 4096; +extern char const* modulation_output_count_param_guid; +extern char const* modulation_output_param_guids[modulation_output_param_count]; Steinberg::FUID fuid_from_text(char const* text); std::vector load_ibstream(Steinberg::IBStream* stream); diff --git a/plugin_base/src/plugin_base/plugin_base/dsp/block/host.cpp b/plugin_base/src/plugin_base/plugin_base/dsp/block/host.cpp index 4ea86f4d2..ebd327fde 100644 --- a/plugin_base/src/plugin_base/plugin_base/dsp/block/host.cpp +++ b/plugin_base/src/plugin_base/plugin_base/dsp/block/host.cpp @@ -11,7 +11,7 @@ host_block::prepare() events.notes.clear(); events.block.clear(); events.output_params.clear(); - events.mod_indicator_states.clear(); + events.modulation_outputs.clear(); events.accurate_automation.clear(); events.accurate_modulation.clear(); events.accurate_automation_and_modulation.clear(); @@ -28,7 +28,7 @@ host_events::deactivate() notes = {}; block = {}; output_params = {}; - mod_indicator_states = {}; + modulation_outputs = {}; accurate_automation = {}; accurate_modulation = {}; accurate_automation_and_modulation = {}; @@ -44,16 +44,16 @@ host_events::activate(bool graph, int module_count, int param_count, int midi_co int block_events_guess = graph? 0: param_count; int note_limit_guess = polyphony * fill_guess; int midi_events_guess = midi_count * fill_guess; + int mod_outputs_guess = param_count * polyphony; int accurate_events_guess = param_count * fill_guess; - int mod_indicator_states_guess = param_count * polyphony; - + midi.reserve(midi_events_guess); notes.reserve(note_limit_guess); block.reserve(block_events_guess); output_params.reserve(block_events_guess); // see also plugin_engine::ctor - mod_indicator_states.reserve(mod_indicator_states_guess); + modulation_outputs.reserve(mod_outputs_guess); accurate_automation.reserve(accurate_events_guess); accurate_modulation.reserve(accurate_events_guess); diff --git a/plugin_base/src/plugin_base/plugin_base/dsp/block/host.hpp b/plugin_base/src/plugin_base/plugin_base/dsp/block/host.hpp index d23880e53..82236d7d9 100644 --- a/plugin_base/src/plugin_base/plugin_base/dsp/block/host.hpp +++ b/plugin_base/src/plugin_base/plugin_base/dsp/block/host.hpp @@ -51,8 +51,8 @@ struct host_events final { std::vector accurate_modulation; // regular output params eg cpu usage, needs registering output params by the module std::vector output_params; - // lfo / env modulator indicators - std::vector mod_indicator_states; + // lfo / env / parameter modulation outputs + std::vector modulation_outputs; // plugin_engine interpolates these as one std::vector accurate_automation_and_modulation; diff --git a/plugin_base/src/plugin_base/plugin_base/dsp/block/plugin.hpp b/plugin_base/src/plugin_base/plugin_base/dsp/block/plugin.hpp index 585441ecf..20bc17a4a 100644 --- a/plugin_base/src/plugin_base/plugin_base/dsp/block/plugin.hpp +++ b/plugin_base/src/plugin_base/plugin_base/dsp/block/plugin.hpp @@ -134,7 +134,7 @@ struct plugin_block final { // for vst3 this is just the host block out events // but for clap threadpool we will have to consolidate after voice stage - std::vector* mod_indicator_states = {}; + std::vector* modulation_outputs = {}; void* module_context(int mod, int slot) const; jarray const& module_cv(int mod, int slot) const; @@ -145,8 +145,8 @@ struct plugin_block final { float pitch_to_freq_with_tuning(float pitch); void set_out_param(int param, int slot, double raw) const; - void push_mod_indicator_state(mod_indicator_state const& indicator_state) - { mod_indicator_states->push_back(indicator_state); } + void push_modulation_output(modulation_output const& output) + { modulation_outputs->push_back(output); } template float normalized_to_raw_fast(int module_, int param_, float normalized) const; diff --git a/plugin_base/src/plugin_base/plugin_base/dsp/block/shared.hpp b/plugin_base/src/plugin_base/plugin_base/dsp/block/shared.hpp index c9ffc89d8..9ce09250f 100644 --- a/plugin_base/src/plugin_base/plugin_base/dsp/block/shared.hpp +++ b/plugin_base/src/plugin_base/plugin_base/dsp/block/shared.hpp @@ -17,17 +17,141 @@ struct shared_block final { float const* const* audio_in; }; -struct mod_indicator_state_data final { - std::uint8_t voice_index; +// once per block output data +// we send back from audio->gui +// so it can paint fancy stuff +// it was at one point modulation output only +// but now also voice start/end +// but i dont feel like renaming a gazillion occurrences +// also it must fit in sizeof(double) to simplify the +// vst3 implementation + +enum output_event_type +{ + out_event_voice_activation, + out_event_cv_state, + out_event_param_state +}; + +struct mod_out_voice_state +{ + std::uint8_t event_type; + std::int8_t voice_index; + bool is_active; + std::uint8_t padding; + std::uint32_t stream_time_low; // assumption is notes dont go more apart than maxuint frames +}; + +struct mod_out_cv_state +{ + std::uint8_t event_type; + std::int8_t voice_index; // -1 for global std::uint8_t module_global; - std::int16_t param_global; - float value; + std::uint8_t padding; + float position_normalized; }; -// visual modulation indication -union mod_indicator_state final { +struct mod_out_param_state +{ + std::uint8_t event_type; + std::int8_t voice_index; // -1 for global + std::uint8_t module_global; + std::uint8_t padding; + std::uint16_t param_global; + std::uint16_t value_normalized; + + float normalized_real() const + { return std::clamp((float)value_normalized / std::numeric_limits::max(), 0.0f, 1.0f); } +}; + +union mod_out_state +{ + mod_out_cv_state cv; + mod_out_voice_state voice; + mod_out_param_state param; +}; + +union modulation_output +{ + mod_out_state state; std::uint64_t packed; - mod_indicator_state_data data; + + // stuff overlaps, all should match + output_event_type event_type() const + { return (output_event_type)state.cv.event_type; } + + static modulation_output + make_mod_out_voice_state(std::uint8_t voice_index, bool is_active, std::uint32_t stream_time_low) + { + modulation_output result; + result.state.voice.is_active = is_active; + result.state.voice.voice_index = voice_index; + result.state.voice.stream_time_low = stream_time_low; + result.state.voice.event_type = out_event_voice_activation; + return result; + } + + static modulation_output + make_mod_output_cv_state(std::int8_t voice_index, std::uint8_t module_global, float position_normalized) + { + assert(voice_index >= -1); + assert(-1e-3 <= position_normalized && position_normalized <= 1 + 1e-3); + modulation_output result; + result.state.cv.voice_index = voice_index; + result.state.cv.module_global = module_global; + result.state.cv.event_type = out_event_cv_state; + result.state.cv.position_normalized = position_normalized; + return result; + } + + static modulation_output + make_mod_output_param_state(std::int8_t voice_index, std::uint8_t module_global, std::uint16_t param_global, float value_normalized) + { + assert(voice_index >= -1); + assert(-1e-3 <= value_normalized && value_normalized <= 1 + 1e-3); + modulation_output result; + result.state.param.voice_index = voice_index; + result.state.param.param_global = param_global; + result.state.param.module_global = module_global; + result.state.param.event_type = out_event_param_state; + result.state.param.value_normalized = (std::uint16_t)(std::clamp(value_normalized, 0.0f, 1.0f) * std::numeric_limits::max()); + return result; + } }; +inline bool operator < +(modulation_output const& l, modulation_output const& r) +{ + if (l.event_type() < r.event_type()) return true; + if (l.event_type() > r.event_type()) return false; + if (l.event_type() == out_event_voice_activation) + { + if (l.state.voice.voice_index < r.state.voice.voice_index) return true; + if (l.state.voice.voice_index > r.state.voice.voice_index) return false; + if (l.state.voice.stream_time_low < r.state.voice.stream_time_low) return true; + if (l.state.voice.stream_time_low > r.state.voice.stream_time_low) return false; + return false; + } + if (l.event_type() == out_event_cv_state) + { + if (l.state.cv.module_global < r.state.cv.module_global) return true; + if (l.state.cv.module_global > r.state.cv.module_global) return false; + if (l.state.cv.voice_index < r.state.cv.voice_index) return true; + if (l.state.cv.voice_index > r.state.cv.voice_index) return false; + return false; + } + if (l.event_type() == out_event_param_state) + { + if (l.state.param.module_global < r.state.param.module_global) return true; + if (l.state.param.module_global > r.state.param.module_global) return false; + if (l.state.param.param_global < r.state.param.param_global) return true; + if (l.state.param.param_global > r.state.param.param_global) return false; + if (l.state.param.voice_index < r.state.param.voice_index) return true; + if (l.state.param.voice_index > r.state.param.voice_index) return false; + return false; + } + assert(false); + return false; +} + } \ No newline at end of file diff --git a/plugin_base/src/plugin_base/plugin_base/dsp/engine.cpp b/plugin_base/src/plugin_base/plugin_base/dsp/engine.cpp index ee75d78bf..7f308fa44 100644 --- a/plugin_base/src/plugin_base/plugin_base/dsp/engine.cpp +++ b/plugin_base/src/plugin_base/plugin_base/dsp/engine.cpp @@ -65,10 +65,10 @@ _voice_processor_context(voice_processor_context) _current_voice_tuning_channel.resize(_polyphony); // see also host_events::activate - _global_mod_indicator_states.resize(desc->module_count); - _voice_mod_indicator_states.resize(_polyphony); + _global_modulation_outputs.resize(desc->module_count); + _voice_modulation_outputs.resize(_polyphony); for(int i = 0; i < _polyphony; i++) - _voice_mod_indicator_states[i].resize(desc->module_count); + _voice_modulation_outputs[i].resize(desc->module_count); } engine_tuning_mode @@ -140,9 +140,9 @@ plugin_engine::make_plugin_block( ? _block_automation.state() : _voice_automation[voice].state(); - std::vector* mod_indicator_states = voice < 0 - ? &_global_mod_indicator_states - : &_voice_mod_indicator_states[voice]; + std::vector* modulation_outputs = voice < 0 + ? &_global_modulation_outputs + : &_voice_modulation_outputs[voice]; plugin_block_state state = { _last_note_key, context_out, _mono_note_stream, @@ -187,7 +187,7 @@ plugin_engine::make_plugin_block( _sample_rate, state, nullptr, nullptr, _host_block->shared, _state.desc(), _state.desc().modules[module_global], - mod_indicator_states + modulation_outputs }; } @@ -658,10 +658,10 @@ plugin_engine::process() int frame_count = _host_block->frame_count; _host_block->events.output_params.clear(); - _host_block->events.mod_indicator_states.clear(); - _global_mod_indicator_states.clear(); + _host_block->events.modulation_outputs.clear(); + _global_modulation_outputs.clear(); for (int i = 0; i < _polyphony; i++) - _voice_mod_indicator_states[i].clear(); + _voice_modulation_outputs[i].clear(); std::pair denormal_state = disable_denormals(); // set automation values to current state, events may overwrite @@ -1272,16 +1272,22 @@ plugin_engine::process() } } - // fill output mod indicators + // fill modulation outputs // these are proteced by mfence in case of clap threadpool - for (int i = 0; i < _global_mod_indicator_states.size(); i++) - _host_block->events.mod_indicator_states.push_back(_global_mod_indicator_states[i]); + for (int i = 0; i < _global_modulation_outputs.size(); i++) + _host_block->events.modulation_outputs.push_back(_global_modulation_outputs[i]); for (int i = 0; i < _polyphony; i++) - for (int j = 0; j < _voice_mod_indicator_states[i].size(); j++) - _host_block->events.mod_indicator_states.push_back(_voice_mod_indicator_states[i][j]); + for (int j = 0; j < _voice_modulation_outputs[i].size(); j++) + _host_block->events.modulation_outputs.push_back(_voice_modulation_outputs[i][j]); // Note: custom output events are already filled here. // It's up to the plugin bindings to communicate them back to the gui. + // Only need to communicate the voice states now. + for (int i = 0; i < _polyphony; i++) + _host_block->events.modulation_outputs.push_back( + modulation_output::make_mod_out_voice_state( + i, _voice_states[i].stage != voice_stage::unused, + (std::uint32_t)_voice_states[i].time)); /*******************/ /* STEP 9: Wrap-up */ diff --git a/plugin_base/src/plugin_base/plugin_base/dsp/engine.hpp b/plugin_base/src/plugin_base/plugin_base/dsp/engine.hpp index 294700403..f651523a0 100644 --- a/plugin_base/src/plugin_base/plugin_base/dsp/engine.hpp +++ b/plugin_base/src/plugin_base/plugin_base/dsp/engine.hpp @@ -113,8 +113,8 @@ class plugin_engine final { thread_pool_voice_processor _voice_processor = {}; // strictly only need for clap threadpool, but used also for vst3 - std::vector _global_mod_indicator_states = {}; - std::vector> _voice_mod_indicator_states = {}; + std::vector _global_modulation_outputs = {}; + std::vector> _voice_modulation_outputs = {}; jarray, 3> _voice_engines = {}; jarray, 2> _input_engines = {}; diff --git a/plugin_base/src/plugin_base/plugin_base/dsp/splice_engine.cpp b/plugin_base/src/plugin_base/plugin_base/dsp/splice_engine.cpp index 25b3adf8c..f6d1b1fc7 100644 --- a/plugin_base/src/plugin_base/plugin_base/dsp/splice_engine.cpp +++ b/plugin_base/src/plugin_base/plugin_base/dsp/splice_engine.cpp @@ -136,7 +136,7 @@ plugin_splice_engine::process() int total_block_count = spliced_block_count + (rest_block_frames == 0? 0: 1); _host_block.events.output_params.clear(); - _host_block.events.mod_indicator_states.clear(); + _host_block.events.modulation_outputs.clear(); splice_accurate_events( _host_block.events.accurate_automation, _spliced_accurate_automation_events, @@ -225,12 +225,14 @@ plugin_splice_engine::process() inner_block.events.output_params.end()); // only care about the last one + // to not overwhelm the ui with events + // ui is written to take this stream as "not perfect, will catch up eventually" if (i == total_block_count - 1) { - _host_block.events.mod_indicator_states.insert( - _host_block.events.mod_indicator_states.begin(), - inner_block.events.mod_indicator_states.begin(), - inner_block.events.mod_indicator_states.end()); + _host_block.events.modulation_outputs.insert( + _host_block.events.modulation_outputs.begin(), + inner_block.events.modulation_outputs.begin(), + inner_block.events.modulation_outputs.end()); } _engine.release_block(); diff --git a/plugin_base/src/plugin_base/plugin_base/gui/components.cpp b/plugin_base/src/plugin_base/plugin_base/gui/components.cpp index 6300a5f48..388112cd1 100644 --- a/plugin_base/src/plugin_base/plugin_base/gui/components.cpp +++ b/plugin_base/src/plugin_base/plugin_base/gui/components.cpp @@ -18,22 +18,22 @@ binding_component:: ~binding_component() { for(int i = 0; i < _visibility_params.size(); i++) - _gui->gui_state()->remove_listener(_visibility_params[i], this); + _gui->automation_state()->remove_listener(_visibility_params[i], this); for (int i = 0; i < _enabled_params.size(); i++) - _gui->gui_state()->remove_listener(_enabled_params[i], this); + _gui->automation_state()->remove_listener(_enabled_params[i], this); - auto const& param_topo_to_index = _gui->gui_state()->desc().param_mappings.topo_to_index; + auto const& param_topo_to_index = _gui->automation_state()->desc().param_mappings.topo_to_index; auto const& global_enabled = _bindings->global_enabled; if (global_enabled.is_bound()) { int global_index = param_topo_to_index[global_enabled.module][0][global_enabled.param][0]; - _gui->gui_state()->remove_listener(global_index, this); + _gui->automation_state()->remove_listener(global_index, this); } auto const& global_visible = _bindings->global_visible; if (global_visible.is_bound()) { int global_index = param_topo_to_index[global_visible.module][0][global_visible.param][0]; - _gui->gui_state()->remove_listener(global_index, this); + _gui->automation_state()->remove_listener(global_index, this); } } @@ -44,7 +44,7 @@ binding_component::bind_param( { values.clear(); for (int i = 0; i < params.size(); i++) - values.push_back(_gui->gui_state()->get_plain_at_index(params[i]).step()); + values.push_back(_gui->automation_state()->get_plain_at_index(params[i]).step()); return binding.param_selector(values); } @@ -59,10 +59,10 @@ binding_component::init() _bindings->enabled.slot_selector(_module->info.slot)); if (_enabled_params.size() != 0) state_changed(_enabled_params[0], - _gui->gui_state()->get_plain_at_index(_enabled_params[0])); + _gui->automation_state()->get_plain_at_index(_enabled_params[0])); if (_visibility_params.size() != 0) state_changed(_visibility_params[0], - _gui->gui_state()->get_plain_at_index(_visibility_params[0])); + _gui->automation_state()->get_plain_at_index(_visibility_params[0])); } void @@ -70,19 +70,19 @@ binding_component::setup_param_bindings( gui_global_binding const& global_binding, std::vector const& topo_params, std::vector& params) { - auto const& param_topo_to_index = _gui->gui_state()->desc().param_mappings.topo_to_index; + auto const& param_topo_to_index = _gui->automation_state()->desc().param_mappings.topo_to_index; for (int i = 0; i < topo_params.size(); i++) { auto const& slots = param_topo_to_index[_module->info.topo][_module->info.slot][topo_params[i]]; bool single_slot = _module->module->params[topo_params[i]].info.slot_count == 1; int state_index = single_slot ? slots[0] : slots[_own_slot_index]; params.push_back(state_index); - _gui->gui_state()->add_listener(state_index, this); + _gui->automation_state()->add_listener(state_index, this); } if (global_binding.is_bound()) { int global_index = param_topo_to_index[global_binding.module][0][global_binding.param][0]; - _gui->gui_state()->add_listener(global_index, this); + _gui->automation_state()->add_listener(global_index, this); } } @@ -98,7 +98,7 @@ binding_component::state_changed(int index, plain_value plain) auto const& global_binding = _bindings->global_enabled; if(global_binding.is_bound()) { - int global_value = _gui->gui_state()->get_plain_at(global_binding.module, 0, global_binding.param, 0).step(); + int global_value = _gui->automation_state()->get_plain_at(global_binding.module, 0, global_binding.param, 0).step(); global_enabled = global_binding.selector(global_value); } if(!global_enabled) @@ -121,7 +121,7 @@ binding_component::state_changed(int index, plain_value plain) auto const& global_binding = _bindings->global_visible; if(global_binding.is_bound()) { - int global_value = _gui->gui_state()->get_plain_at(global_binding.module, 0, global_binding.param, 0).step(); + int global_value = _gui->automation_state()->get_plain_at(global_binding.module, 0, global_binding.param, 0).step(); global_visible = global_binding.selector(global_value); } if (!global_visible) diff --git a/plugin_base/src/plugin_base/plugin_base/gui/containers.cpp b/plugin_base/src/plugin_base/plugin_base/gui/containers.cpp index de815a97c..88f5da93d 100644 --- a/plugin_base/src/plugin_base/plugin_base/gui/containers.cpp +++ b/plugin_base/src/plugin_base/plugin_base/gui/containers.cpp @@ -93,14 +93,14 @@ param_section_container(plugin_gui* gui, lnf* lnf, module_desc const* module, pa tabbed_module_section_container:: tabbed_module_section_container(plugin_gui* gui, int section_index, std::function(int module_index)> factory): -extra_state_container(gui, module_section_tab_key(*gui->gui_state()->desc().plugin, section_index)), +extra_state_container(gui, module_section_tab_key(*gui->automation_state()->desc().plugin, section_index)), _section_index(section_index), _factory(factory) {} std::unique_ptr tabbed_module_section_container::create_child() { int tab_index = gui()->extra_state_()->get_int(state_key(), 0); - auto const& tab_order = gui()->gui_state()->desc().plugin->gui.module_sections[_section_index].tab_order; + auto const& tab_order = gui()->automation_state()->desc().plugin->gui.module_sections[_section_index].tab_order; tab_index = std::clamp(tab_index, 0, (int)tab_order.size() - 1); return _factory(tab_order[tab_index]); } diff --git a/plugin_base/src/plugin_base/plugin_base/gui/controls.cpp b/plugin_base/src/plugin_base/plugin_base/gui/controls.cpp index 0ebb99489..8d6447b81 100644 --- a/plugin_base/src/plugin_base/plugin_base/gui/controls.cpp +++ b/plugin_base/src/plugin_base/plugin_base/gui/controls.cpp @@ -186,7 +186,7 @@ param_value_label::value_ref_text(plugin_gui* gui, param_desc const* param) auto const& ref_text = param->param->gui.value_reference_text; if (ref_text.size()) return ref_text; auto plain = param->param->domain.raw_to_plain(param->param->domain.max); - return gui->gui_state()->plain_to_text_at_index(false, param->info.global, plain); + return gui->automation_state()->plain_to_text_at_index(false, param->info.global, plain); } // Just guess max value is representative of the longest text. @@ -199,7 +199,7 @@ autofit_label(lnf, value_ref_text(gui, param)) void param_value_label::own_param_changed(plain_value plain) { - std::string text = _gui->gui_state()->plain_to_text_at_index(false, _param->info.global, plain); + std::string text = _gui->automation_state()->plain_to_text_at_index(false, _param->info.global, plain); setText(text, dontSendNotification); setTooltip(_param->info.name + ": " + text); } @@ -219,16 +219,16 @@ last_tweaked_label:: last_tweaked_label(plugin_gui* gui, lnf* lnf): _gui(gui) { - gui->gui_state()->add_any_listener(this); - any_state_changed(0, gui->gui_state()->get_plain_at_index(0)); + gui->automation_state()->add_any_listener(this); + any_state_changed(0, gui->automation_state()->get_plain_at_index(0)); setColour(textColourId, lnf->colors().label_text); } void last_tweaked_label::any_state_changed(int index, plain_value plain) { - if(_gui->gui_state()->desc().params[index]->param->dsp.direction == param_direction::output) return; - setText(_gui->gui_state()->desc().params[index]->full_name, dontSendNotification); + if(_gui->automation_state()->desc().params[index]->param->dsp.direction == param_direction::output) return; + setText(_gui->automation_state()->desc().params[index]->full_name, dontSendNotification); } last_tweaked_editor:: @@ -275,9 +275,9 @@ last_tweaked_editor::textEditorTextChanged(TextEditor& te) theme_combo:: theme_combo(plugin_gui* gui, lnf* lnf) : autofit_combobox(lnf, true, false), -_gui(gui), _themes(gui->gui_state()->desc().plugin->themes()) +_gui(gui), _themes(gui->automation_state()->desc().plugin->themes()) { - auto const& topo = *gui->gui_state()->desc().plugin; + auto const& topo = *gui->automation_state()->desc().plugin; std::string default_theme = topo.gui.default_theme; std::string theme = user_io_load_list(topo.vendor, topo.full_name, user_io::base, user_state_theme_key, default_theme, _themes); for (int i = 0; i < _themes.size(); i++) @@ -354,7 +354,7 @@ autofit_combobox::autofit() param_component:: param_component(plugin_gui* gui, module_desc const* module, param_desc const* param) : binding_component(gui, module, ¶m->param->gui.bindings, param->info.slot), _param(param) -{ _gui->gui_state()->add_listener(_param->info.global, this); } +{ _gui->automation_state()->add_listener(_param->info.global, this); } void param_component::state_changed(int index, plain_value plain) @@ -369,7 +369,7 @@ void param_component::init() { // Must be called by subclass constructor as we dynamic_cast to Component inside. - state_changed(_param->info.global, _gui->gui_state()->get_plain_at_index(_param->info.global)); + state_changed(_param->info.global, _gui->automation_state()->get_plain_at_index(_param->info.global)); binding_component::init(); @@ -398,7 +398,7 @@ param_component::mouseUp(MouseEvent const& evt) std::unique_ptr plugin_handler = {}; param_menu_handler_factory plugin_handler_factory = _param->param->gui.menu_handler_factory; if(plugin_handler_factory) - plugin_handler = plugin_handler_factory(_gui->gui_state()); + plugin_handler = plugin_handler_factory(_gui->automation_state()); if (plugin_handler) { auto plugin_menus = plugin_handler->menus(); @@ -412,7 +412,7 @@ param_component::mouseUp(MouseEvent const& evt) } } - auto host_menu = _gui->gui_state()->desc().menu_handler->context_menu(_param->info.id_hash); + auto host_menu = _gui->automation_state()->desc().menu_handler->context_menu(_param->info.id_hash); if (host_menu && host_menu->root.children.size()) { have_menu = true; @@ -429,9 +429,9 @@ param_component::mouseUp(MouseEvent const& evt) auto plugin_menus = plugin_handler->menus(); auto const& menu = plugin_menus[(id - 10000) / 1000]; auto const& action_entry = menu.entries[((id - 10000) % 1000) / 100]; - _gui->gui_state()->begin_undo_region(); + _gui->automation_state()->begin_undo_region(); std::string item = plugin_handler->execute(menu.menu_id, action_entry.action, _module->info.topo, _module->info.slot, _param->info.topo, _param->info.slot); - _gui->gui_state()->end_undo_region(action_entry.title, item); + _gui->automation_state()->end_undo_region(action_entry.title, item); } delete host_menu; delete plugin_handler; @@ -460,7 +460,7 @@ get_longest_module_name(plugin_gui* gui) std::string result; std::string full_name; std::string display_name; - auto const& desc = gui->gui_state()->desc(); + auto const& desc = gui->automation_state()->desc(); for (int i = 0; i < desc.modules.size(); i++) if(desc.modules[i].module->gui.visible) @@ -485,7 +485,7 @@ autofit_label(lnf, get_longest_module_name(gui)) void module_name_label::own_param_changed(plain_value plain) { - auto const& desc = _gui->gui_state()->desc().modules[plain.step()]; + auto const& desc = _gui->automation_state()->desc().modules[plain.step()]; if (!desc.module->gui.visible) { setTooltip(""); @@ -530,9 +530,9 @@ param_component(gui, module, param), autofit_togglebutton(lnf, param->param->gui param_slider:: ~param_slider() { - // parameter modulation indicators + // parameter modulation if (_param->param->dsp.can_modulate(_param->info.slot)) - _gui->remove_mod_indicator_state_listener(this); + _gui->remove_modulation_output_listener(this); } param_slider:: @@ -549,9 +549,9 @@ param_component(gui, module, param), Slider() default: assert(false); break; } - // parameter modulation indicators + // parameter modulation outputs if (param->param->dsp.can_modulate(param->info.slot)) - gui->add_mod_indicator_state_listener(this); + gui->add_modulation_output_listener(this); setTextBoxStyle(Slider::NoTextBox, true, 0, 0); if(param->param->domain.unit.size()) @@ -574,42 +574,54 @@ param_slider::fixed_width(int parent_w, int parent_h) const } void -param_slider::mod_indicator_state_changed(std::vector const& states) +param_slider::modulation_outputs_reset() { - float prev_min = _min_mod_indicator; - float prev_max = _max_mod_indicator; + _min_modulation_output = -1.0f; + _max_modulation_output = -1.0f; + repaint(); +} + +void +param_slider::modulation_outputs_changed(std::vector const& outputs) +{ + _this_mod_outputs.clear(); + std::copy_if(outputs.begin(), outputs.end(), std::back_inserter(_this_mod_outputs), + [this](auto const& output) { return output.event_type() == output_event_type::out_event_param_state && + output.state.param.param_global == _param->info.global; }); - if (states.size() > 0) + float prev_min = _min_modulation_output; + float prev_max = _max_modulation_output; + + if (_this_mod_outputs.size() > 0) { - _min_mod_indicator = -1.0f; - _max_mod_indicator = -1.0f; + _min_modulation_output = -1.0f; + _max_modulation_output = -1.0f; } - bool any_indicator_found = false; - for(int i = 0; i < states.size(); i++) - if (states[i].data.param_global == _param->info.global) - { - any_indicator_found = true; - if (_min_mod_indicator < 0.0f) _min_mod_indicator = states[i].data.value; - if (_max_mod_indicator < 0.0f) _max_mod_indicator = states[i].data.value; - _min_mod_indicator = std::min(_min_mod_indicator, states[i].data.value); - _max_mod_indicator = std::max(_max_mod_indicator, states[i].data.value); - _mod_indicator_activated_time_seconds = seconds_since_epoch(); - } + bool any_mod_output_found = false; + for(int i = 0; i < _this_mod_outputs.size(); i++) + { + any_mod_output_found = true; + if (_min_modulation_output < 0.0f) _min_modulation_output = _this_mod_outputs[i].state.param.normalized_real(); + if (_max_modulation_output < 0.0f) _max_modulation_output = _this_mod_outputs[i].state.param.normalized_real(); + _min_modulation_output = std::min(_min_modulation_output, _this_mod_outputs[i].state.param.normalized_real()); + _max_modulation_output = std::max(_max_modulation_output, _this_mod_outputs[i].state.param.normalized_real()); + _modulation_output_activated_time_seconds = seconds_since_epoch(); + } // check if we expired - if (!any_indicator_found) + if (!any_mod_output_found) { double invalidate_after = 0.05; double time_now = seconds_since_epoch(); - if (_mod_indicator_activated_time_seconds < time_now - invalidate_after) + if (_modulation_output_activated_time_seconds < time_now - invalidate_after) { - _min_mod_indicator = -1.0f; - _max_mod_indicator = -1.0f; + _min_modulation_output = -1.0f; + _max_modulation_output = -1.0f; } } - if (prev_min != _min_mod_indicator || prev_max != _max_mod_indicator) + if (prev_min != _min_modulation_output || prev_max != _max_modulation_output) repaint(); } @@ -646,7 +658,7 @@ param_combobox::comboBoxChanged(ComboBox* box) _gui->param_changed(_param->info.global, _param->param->domain.raw_to_plain(index)); if (_param->param->gui.is_preset_selector) { - auto presets = _gui->gui_state()->desc().plugin->presets(); + auto presets = _gui->automation_state()->desc().plugin->presets(); if (0 <= index && index < presets.size()) _gui->load_patch(presets[index].path, true); } @@ -675,13 +687,13 @@ param_combobox::update_all_items_enabled_state() { auto m = _param->param->gui.item_enabled.param; if(m.param_slot == gui_item_binding::match_param_slot) m.param_slot = _param->info.slot; - auto other = _gui->gui_state()->get_plain_at(m.module_index, m.module_slot, m.param_index, m.param_slot); + auto other = _gui->automation_state()->get_plain_at(m.module_index, m.module_slot, m.param_index, m.param_slot); for(int i = 0; i < items.size(); i++) setItemEnabled(i + 1, _param->param->gui.item_enabled.selector(other.step(), _param->param->domain.min + i)); } if (_param->param->gui.item_enabled.auto_bind) { - auto const& topo = *_gui->gui_state()->desc().plugin; + auto const& topo = *_gui->automation_state()->desc().plugin; for (int i = 0; i < items.size(); i++) { bool enabled = true; @@ -708,7 +720,7 @@ param_combobox::update_all_items_enabled_state() else assert(that_bound_topo.info.slot_count == that_topo.info.slot_count); - that_values.push_back(_gui->gui_state()->get_plain_at(m).step()); + that_values.push_back(_gui->automation_state()->get_plain_at(m).step()); } enabled &= that_topo.gui.bindings.enabled.param_selector(that_values); } @@ -729,7 +741,7 @@ param_combobox::update_all_items_enabled_state() else assert(that_bound_topo.info.slot_count == that_topo.info.slot_count); - that_values.push_back(_gui->gui_state()->get_plain_at(m).step()); + that_values.push_back(_gui->automation_state()->get_plain_at(m).step()); } enabled &= that_topo.gui.bindings.visible.param_selector(that_values); } @@ -789,11 +801,11 @@ param_combobox::itemDropped(DragAndDropTarget::SourceDetails const& details) } int item_index = tag - 1; - _gui->gui_state()->begin_undo_region(); + _gui->automation_state()->begin_undo_region(); // found corresponding drop target, select it // dont do setSelectedId -- that triggers async update and gets us 2 items in the undo history - _gui->gui_state()->set_plain_at_index(_param->info.global, + _gui->automation_state()->set_plain_at_index(_param->info.global, _param->param->domain.raw_to_plain(item_index)); // now figure out the matrix route enabled selector, and if its 0, set it to 1 @@ -807,13 +819,13 @@ param_combobox::itemDropped(DragAndDropTarget::SourceDetails const& details) int mi = _module->info.slot; int p = _module->module->params[i].info.index; int pi = _param->info.slot; - if (_gui->gui_state()->get_plain_at(m, mi, p, pi).step() == 0) - _gui->gui_state()->set_plain_at(m, mi, p, pi, + if (_gui->automation_state()->get_plain_at(m, mi, p, pi).step() == 0) + _gui->automation_state()->set_plain_at(m, mi, p, pi, _module->module->params[i].domain.raw_to_plain(_param->param->gui.drop_route_enabled_param_value)); itemDragExit(details); - _gui->gui_state()->end_undo_region("Drop", _gui->gui_state()->plain_to_text_at_index( - false, _param->info.global, _gui->gui_state()->get_plain_at_index(_param->info.global))); + _gui->automation_state()->end_undo_region("Drop", _gui->automation_state()->plain_to_text_at_index( + false, _param->info.global, _gui->automation_state()->get_plain_at_index(_param->info.global))); return; } assert(false); diff --git a/plugin_base/src/plugin_base/plugin_base/gui/controls.hpp b/plugin_base/src/plugin_base/plugin_base/gui/controls.hpp index b34441455..68a083284 100644 --- a/plugin_base/src/plugin_base/plugin_base/gui/controls.hpp +++ b/plugin_base/src/plugin_base/plugin_base/gui/controls.hpp @@ -91,7 +91,7 @@ public any_state_listener plugin_gui* const _gui; public: last_tweaked_label(plugin_gui* gui, lnf* lnf); - ~last_tweaked_label() { _gui->gui_state()->remove_any_listener(this); } + ~last_tweaked_label() { _gui->automation_state()->remove_any_listener(this); } void any_state_changed(int index, plain_value plain) override; }; @@ -136,7 +136,7 @@ public juce::MouseListener param_desc const* param() const { return _param; } void mouseUp(juce::MouseEvent const& e) override; void state_changed(int index, plain_value plain) override; - virtual ~param_component() { _gui->gui_state()->remove_listener(_param->info.global, this); } + virtual ~param_component() { _gui->automation_state()->remove_listener(_param->info.global, this); } protected: void init() override; @@ -231,11 +231,12 @@ class param_slider: public param_component, public juce::Slider, public autofit_component, -public mod_indicator_state_listener +public modulation_output_listener { - float _min_mod_indicator = -1.0f; - float _max_mod_indicator = -1.0f; - double _mod_indicator_activated_time_seconds = {}; + float _min_modulation_output = -1.0f; + float _max_modulation_output = -1.0f; + double _modulation_output_activated_time_seconds = {}; + std::vector _this_mod_outputs = {}; protected: void own_param_changed(plain_value plain) override final @@ -245,11 +246,13 @@ public mod_indicator_state_listener ~param_slider(); param_slider(plugin_gui* gui, module_desc const* module, param_desc const* param); - // param modulation indicators - void mod_indicator_state_changed(std::vector const& states) override; - float min_mod_indicator() const { return _min_mod_indicator; } - float max_mod_indicator() const { return _max_mod_indicator; } - double mod_indicator_activated_time_seconds() const { return _mod_indicator_activated_time_seconds; } + // param modulation outputs + float min_modulation_output() const { return _min_modulation_output; } + float max_modulation_output() const { return _max_modulation_output; } + + void modulation_outputs_reset() override; + void modulation_outputs_changed(std::vector const& outputs) override; + double modulation_output_activated_time_seconds() const { return _modulation_output_activated_time_seconds; } int fixed_width(int parent_w, int parent_h) const override; int fixed_height(int parent_w, int parent_h) const override { return -1; } diff --git a/plugin_base/src/plugin_base/plugin_base/gui/graph.cpp b/plugin_base/src/plugin_base/plugin_base/gui/graph.cpp index 4e3898ab8..028652997 100644 --- a/plugin_base/src/plugin_base/plugin_base/gui/graph.cpp +++ b/plugin_base/src/plugin_base/plugin_base/gui/graph.cpp @@ -12,31 +12,30 @@ module_graph:: { _done = true; stopTimer(); - if(_module_params.render_on_tweak) _gui->gui_state()->remove_any_listener(this); + _gui->remove_modulation_output_listener(this); + if(_module_params.render_on_tweak) _gui->automation_state()->remove_any_listener(this); if(_module_params.render_on_tab_change) _gui->remove_tab_selection_listener(this); - if (_module_params.render_on_mod_indicator_change) _gui->remove_mod_indicator_state_listener(this); if (_module_params.render_on_module_mouse_enter || _module_params.render_on_param_mouse_enter_modules.size()) _gui->remove_gui_mouse_listener(this); } module_graph:: module_graph(plugin_gui* gui, lnf* lnf, graph_params const& params, module_graph_params const& module_params): -graph(lnf, params), _gui(gui), _module_params(module_params) +graph(gui, lnf, params), _module_params(module_params) { assert(_module_params.fps > 0); assert(_module_params.render_on_tweak || _module_params.render_on_tab_change || _module_params.render_on_module_mouse_enter || _module_params.render_on_param_mouse_enter_modules.size()); if(_module_params.render_on_tab_change) assert(_module_params.module_index != -1); - if (_module_params.render_on_tweak) gui->gui_state()->add_any_listener(this); + if (_module_params.render_on_tweak) gui->automation_state()->add_any_listener(this); if(_module_params.render_on_module_mouse_enter || _module_params.render_on_param_mouse_enter_modules.size()) gui->add_gui_mouse_listener(this); - if (_module_params.render_on_mod_indicator_change) - _gui->add_mod_indicator_state_listener(this); if (_module_params.render_on_tab_change) { gui->add_tab_selection_listener(this); module_tab_changed(_module_params.module_index, 0); } + _gui->add_modulation_output_listener(this); startTimerHz(_module_params.fps); } @@ -55,96 +54,132 @@ module_graph::timerCallback() repaint(); } -void -module_graph::mod_indicator_state_changed(std::vector const& states) +void +module_graph::modulation_outputs_reset() { - if (_data.type() != graph_data_type::series) - return; + _mod_indicators.clear(); + _render_dirty = true; + render_if_dirty(); +} +void +module_graph::modulation_outputs_changed(std::vector const& outputs) +{ if (_hovered_or_tweaked_param == -1) return; - float w = getWidth(); - float h = getHeight(); - int count = _data.series().size(); - - int current_module_slot = -1; - int current_module_index = -1; + // we still get the events, so need to back off. + // it is this way because the param sliders still need the events for param-only mode + if (_gui->get_visuals_mode() != gui_visuals_mode_full) + return; - auto const& desc = _gui->gui_state()->desc(); + int orig_module_slot = -1; + int orig_module_index = -1; + auto const& desc = _gui->automation_state()->desc(); auto const& topo = *desc.plugin; auto const& mappings = desc.param_mappings.params; param_topo_mapping mapping = mappings[_hovered_or_tweaked_param].topo; if (_module_params.module_index != -1) { - current_module_slot = _activated_module_slot; - current_module_index = _module_params.module_index; + orig_module_slot = _activated_module_slot; + orig_module_index = _module_params.module_index; } else { - current_module_slot = desc.param_mappings.params[_hovered_or_tweaked_param].topo.module_slot; - current_module_index = desc.param_mappings.params[_hovered_or_tweaked_param].topo.module_index; + orig_module_slot = desc.param_mappings.params[_hovered_or_tweaked_param].topo.module_slot; + orig_module_index = desc.param_mappings.params[_hovered_or_tweaked_param].topo.module_index; } - if (topo.modules[current_module_index].mod_indicator_source_selector != nullptr) + // this is for stuff when someone else wants to react to us (eg cv matrix to lfo) + // as well as to itself + int mapped_module_slot = orig_module_slot; + int mapped_module_index = orig_module_index; + if (topo.modules[orig_module_index].mod_output_source_selector != nullptr) { - auto selected = topo.modules[current_module_index].mod_indicator_source_selector(*_gui->gui_state(), mapping); + auto selected = topo.modules[orig_module_index].mod_output_source_selector(*_gui->automation_state(), mapping); if (selected.module_index != -1 && selected.module_slot != -1) { - current_module_slot = selected.module_slot; - current_module_index = selected.module_index; + mapped_module_slot = selected.module_slot; + mapped_module_index = selected.module_index; } } - int current_indicator = 0; - int current_module_global = desc.module_topo_to_index.at(current_module_index) + current_module_slot; - for (int i = 0; i < states.size() && current_indicator < max_indicators; i++) - if (current_module_global == states[i].data.module_global && states[i].data.param_global == -1) + int orig_module_global = desc.module_topo_to_index.at(orig_module_index) + orig_module_slot; + int mapped_module_global = desc.module_topo_to_index.at(mapped_module_index) + mapped_module_slot; + int orig_param_first = desc.modules[orig_module_global].params[0].info.global; + + bool rerender_indicators = false; + bool any_mod_indicator_found = false; + for (int i = 0; i < outputs.size(); i++) + if (outputs[i].event_type() == output_event_type::out_event_cv_state) + if (mapped_module_global == outputs[i].state.cv.module_global) + { + if (!any_mod_indicator_found) + { + any_mod_indicator_found = true; + _mod_indicators.clear(); + _mod_indicators_activated = seconds_since_epoch(); + } + _mod_indicators.push_back(outputs[i].state.cv.position_normalized); + } + + if (!any_mod_indicator_found && seconds_since_epoch() >= _mod_indicators_activated + 1.0) + { + _mod_indicators.clear(); + rerender_indicators = true; + } + + bool rerender_full = false; + for (int i = 0; i < outputs.size(); i++) + if (outputs[i].event_type() == output_event_type::out_event_param_state) + { + // debugging + auto const& orig_desc = _gui->automation_state()->desc().modules[orig_module_global]; + auto const& mapped_desc = _gui->automation_state()->desc().modules[mapped_module_global]; + auto const& event_desc = _gui->automation_state()->desc().modules[outputs[i].state.param.module_global]; + (void)orig_desc; + (void)mapped_desc; + (void)event_desc; + + if (orig_module_global == outputs[i].state.param.module_global || + mapped_module_global == outputs[i].state.param.module_global) + { + rerender_full = true; + break; + } + } else if (outputs[i].event_type() == output_event_type::out_event_cv_state) { - float indicator_pos = states[i].data.value; - float x = indicator_pos * w; - int point = std::clamp((int)(indicator_pos * (count - 1)), 0, count - 1); - float y = (1 - std::clamp(_data.series()[point], 0.0f, 1.0f)) * h; - _indicators[current_indicator]->activate(); - _indicators[current_indicator]->setBounds(x - 3, y - 3, 6, 6); - _indicators[current_indicator]->repaint(); - current_indicator++; + if (orig_module_global == outputs[i].state.cv.module_global || + mapped_module_global == outputs[i].state.cv.module_global) + { + rerender_indicators = true; + // DONT break -- full rerender trumps indicators + } } - if (current_indicator > 0) - { - // if any data found for this round, invalidate stuff from the previous round - for (int i = current_indicator; i < max_indicators; i++) - _indicators[i]->setVisible(false); - } - else - { - // if no data found for this round, invalidate stuff that expired - double invalidate_after = 0.05; - double time_now = seconds_since_epoch(); - for (int i = 0; i < max_indicators; i++) - if (_indicators[i]->activated_time_seconds() < time_now - invalidate_after) - _indicators[i]->setVisible(false); - } + if (rerender_full) + request_rerender(orig_param_first, false); + else if(rerender_indicators) + repaint(); } void module_graph::module_tab_changed(int module, int slot) { // trigger re-render based on first new module param - auto const& desc = _gui->gui_state()->desc(); + auto const& desc = _gui->automation_state()->desc(); if(_module_params.module_index != -1 && _module_params.module_index != module) return; _activated_module_slot = slot; int index = desc.module_topo_to_index.at(module) + slot; _last_rerender_cause_param = desc.modules[index].params[0].info.global; - request_rerender(_last_rerender_cause_param); + request_rerender(_last_rerender_cause_param, false); } void module_graph::any_state_changed(int param, plain_value plain) { - auto const& desc = _gui->gui_state()->desc(); + auto const& desc = _gui->automation_state()->desc(); auto const& mapping = desc.param_mappings.params[param]; if(_module_params.module_index == -1 || _module_params.module_index == mapping.topo.module_index) { @@ -152,7 +187,7 @@ module_graph::any_state_changed(int param, plain_value plain) { if(_module_params.module_index != -1) _last_rerender_cause_param = param; - request_rerender(param); + request_rerender(param, false); } return; } @@ -167,41 +202,41 @@ module_graph::any_state_changed(int param, plain_value plain) if(_last_rerender_cause_param != -1) { - request_rerender(_last_rerender_cause_param); + request_rerender(_last_rerender_cause_param, false); return; } int index = desc.module_topo_to_index.at(_module_params.module_index) + _activated_module_slot; - request_rerender(desc.modules[index].params[0].info.global); + request_rerender(desc.modules[index].params[0].info.global, false); } void module_graph::module_mouse_enter(int module) { // trigger re-render based on first new module param - auto const& desc = _gui->gui_state()->desc().modules[module]; + auto const& desc = _gui->automation_state()->desc().modules[module]; if (_module_params.module_index != -1 && _module_params.module_index != desc.module->info.index) return; if(desc.params.size() == 0) return; if(_module_params.render_on_module_mouse_enter && !desc.module->force_rerender_on_param_hover) - request_rerender(desc.params[0].info.global); + request_rerender(desc.params[0].info.global, false); } void module_graph::param_mouse_enter(int param) { // trigger re-render based on specific param - auto const& mapping = _gui->gui_state()->desc().param_mappings.params[param]; + auto const& mapping = _gui->automation_state()->desc().param_mappings.params[param]; if (_module_params.module_index != -1 && _module_params.module_index != mapping.topo.module_index) return; auto end = _module_params.render_on_param_mouse_enter_modules.end(); auto begin = _module_params.render_on_param_mouse_enter_modules.begin(); if (std::find(begin, end, mapping.topo.module_index) != end || std::find(begin, end, -1) != end) - request_rerender(param); + request_rerender(param, true); } void -module_graph::request_rerender(int param) +module_graph::request_rerender(int param, bool hover) { - auto const& desc = _gui->gui_state()->desc(); + auto const& desc = _gui->automation_state()->desc(); auto const& mapping = desc.param_mappings.params[param]; int m = mapping.topo.module_index; int p = mapping.topo.param_index; @@ -209,11 +244,7 @@ module_graph::request_rerender(int param) _render_dirty = true; _hovered_or_tweaked_param = param; - - // will be picked up on the next round, - // need them to disappear first otherwise they may hang around on module switch - for (int i = 0; i < max_indicators; i++) - _indicators[i]->setVisible(false); + if (hover) _hovered_param = param; } bool @@ -222,48 +253,42 @@ module_graph::render_if_dirty() if (!_render_dirty) return false; if (_hovered_or_tweaked_param == -1) return false; - auto const& mappings = _gui->gui_state()->desc().param_mappings.params; - param_topo_mapping mapping = mappings[_hovered_or_tweaked_param].topo; - auto const& module = _gui->gui_state()->desc().plugin->modules[mapping.module_index]; + int render_request_param = _hovered_or_tweaked_param; + if(_module_params.hover_selects_different_graph && _hovered_param != -1) + render_request_param = _hovered_param; + if (render_request_param == -1) return false; + + auto const& mappings = _gui->automation_state()->desc().param_mappings.params; + param_topo_mapping mapping = mappings[render_request_param].topo; + auto const& module = _gui->automation_state()->desc().plugin->modules[mapping.module_index]; + + gui_visuals_mode visuals_mode = _gui->get_visuals_mode(); + plugin_state const* plug_state = _gui->automation_state(); + int module_global = plug_state->desc().param_mappings.params[render_request_param].module_global; + bool render_automation_state = plug_state->desc().modules[module_global].module->gui.render_automation_state; + if (visuals_mode == gui_visuals_mode_full && !render_automation_state) + { + // find the latest active voice, otherwise go with global + int voice_index = -1; + std::uint32_t latest_timestamp = 0; + if (module.dsp.stage == module_stage::voice) + for (int i = 0; i < _gui->engine_voices_active().size(); i++) + if (_gui->engine_voices_active()[i] != 0) + if (_gui->engine_voices_activated()[i] > latest_timestamp) + voice_index = i; + plug_state = voice_index == -1 ? &_gui->global_modulation_state() : &_gui->voice_modulation_state(voice_index); + } + if(module.graph_renderer != nullptr) render(module.graph_renderer( - *_gui->gui_state(), _gui->get_module_graph_engine(module), _hovered_or_tweaked_param, mapping)); + *plug_state, _gui->get_module_graph_engine(module), render_request_param, mapping)); _render_dirty = false; return true; } -graph_indicator:: -graph_indicator(lnf* lnf) : _lnf(lnf) -{ - setSize(6, 6); - setVisible(false); -} - -void -graph_indicator::paint(Graphics& g) -{ - g.setColour(_lnf->colors().graph_mod_indicator); - g.fillEllipse(0, 0, 6, 6); -} - -void -graph_indicator::activate() -{ - setVisible(true); - _activated_time_seconds = seconds_since_epoch(); -} - graph:: -graph(lnf* lnf, graph_params const& params) : - _lnf(lnf), _data(graph_data_type::na, {}), _params(params) -{ - _indicators.resize(max_indicators); - for (int i = 0; i < max_indicators; i++) - { - _indicators[i] = std::make_unique(_lnf); - addChildComponent(_indicators[i].get()); - } -} +graph(plugin_gui* gui, lnf* lnf, graph_params const& params) : +_lnf(lnf), _data(graph_data_type::na, {}), _params(params), _gui(gui) {} void graph::render(graph_data const& data) @@ -405,11 +430,27 @@ graph::paint(Graphics& g) } assert(_data.type() == graph_data_type::series); + + // paint the series jarray series(_data.series()); if(_data.bipolar()) for(int i = 0; i < series.size(); i++) series[i] = bipolar_to_unipolar(series[i]); paint_series(g, series, _data.bipolar(), _data.stroke_thickness(), 0.5f); + + if (_gui->get_visuals_mode() != gui_visuals_mode_full) return; + + // paint the indicator bubbles + int count = _data.series().size(); + g.setColour(_lnf->colors().graph_modulation_bubble); + for (int i = 0; i < _mod_indicators.size(); i++) + { + float output_pos = _mod_indicators[i]; + float x = output_pos * w; + int point = std::clamp((int)(output_pos * (count - 1)), 0, count - 1); + float y = (1 - std::clamp(_data.series()[point], 0.0f, 1.0f)) * h; + g.fillEllipse(x - 3, y - 3, 6, 6); + } } } \ No newline at end of file diff --git a/plugin_base/src/plugin_base/plugin_base/gui/graph.hpp b/plugin_base/src/plugin_base/plugin_base/gui/graph.hpp index 342e761ef..5b7f1abe0 100644 --- a/plugin_base/src/plugin_base/plugin_base/gui/graph.hpp +++ b/plugin_base/src/plugin_base/plugin_base/gui/graph.hpp @@ -19,20 +19,18 @@ struct graph_params partition_scale_type scale_type = {}; }; -// draw on top of graph to prevent repaint the whole component in realtime -class graph_indicator: -public juce::Component +struct module_graph_params { - lnf* const _lnf; - double _activated_time_seconds; - -public: - graph_indicator(lnf* lnf); - - void activate(); - void paint(juce::Graphics& g) override; - double activated_time_seconds() const { return _activated_time_seconds; } -}; + int fps = -1; + int module_index = -1; + bool render_on_tweak = false; + bool render_on_tab_change = false; + bool render_on_module_mouse_enter = false; + bool hover_selects_different_graph = false; // for cv matrices + std::vector render_on_param_mouse_enter_modules = {}; + // trigger also on changes in these + std::vector dependent_module_indices = {}; +}; class graph: public juce::Component @@ -46,29 +44,19 @@ public juce::Component bool bipolar, float stroke_thickness, float midpoint); protected: - static int const max_indicators = 64; graph_data _data; - std::vector> _indicators = {}; + plugin_gui* const _gui; + + // horizontal positions for lfo/env + double _mod_indicators_activated = {}; + std::vector _mod_indicators = {}; public: void render(graph_data const& data); void paint(juce::Graphics& g) override; - graph(lnf* lnf, graph_params const& params); -}; - -struct module_graph_params -{ - int fps = -1; - int module_index = -1; - bool render_on_tweak = false; - bool render_on_tab_change = false; - bool render_on_module_mouse_enter = false; - bool render_on_mod_indicator_change = false; - std::vector render_on_param_mouse_enter_modules = {}; - // trigger also on changes in these - std::vector dependent_module_indices = {}; + graph(plugin_gui* gui, lnf* lnf, graph_params const& params); }; // taps into module_topo.graph_renderer based on task tweaked/hovered param @@ -77,21 +65,21 @@ public graph, public any_state_listener, public gui_mouse_listener, public gui_tab_selection_listener, -public mod_indicator_state_listener, +public modulation_output_listener, public juce::Timer, public juce::SettableTooltipClient { bool _done = false; bool _render_dirty = true; int _activated_module_slot = 0; + int _hovered_param = -1; int _hovered_or_tweaked_param = -1; int _last_rerender_cause_param = -1; - plugin_gui* const _gui; module_graph_params const _module_params; bool render_if_dirty(); - void request_rerender(int param); + void request_rerender(int param, bool hover); public: ~module_graph(); @@ -106,7 +94,9 @@ public juce::SettableTooltipClient void module_mouse_enter(int module) override; void module_tab_changed(int module, int slot) override; void any_state_changed(int param, plain_value plain) override; - void mod_indicator_state_changed(std::vector const& states) override; + + void modulation_outputs_reset() override; + void modulation_outputs_changed(std::vector const& outputs) override; }; } \ No newline at end of file diff --git a/plugin_base/src/plugin_base/plugin_base/gui/gui.cpp b/plugin_base/src/plugin_base/plugin_base/gui/gui.cpp index 4fd212101..74d575450 100644 --- a/plugin_base/src/plugin_base/plugin_base/gui/gui.cpp +++ b/plugin_base/src/plugin_base/plugin_base/gui/gui.cpp @@ -117,6 +117,16 @@ static int module_header_height(int font_height) { return font_height + 6; } +std::vector +gui_visuals_items() +{ + std::vector result; + result.emplace_back("{998888CA-D63C-4FEE-8166-8A795DEE0F11}", "None", "Disabled (most efficient)"); + result.emplace_back("{8A447F5B-D026-47CE-B0B4-0D4104973ACF}", "Params Only", "Show param modulation only (less efficient)"); + result.emplace_back("{9274A8EB-3FBE-4B72-89ED-0C841235949D}", "Params And Graphs", "Show param and graph modulation (expensive)"); + return result; +} + std::vector gui_vertical_distribution(int total_height, int font_height, std::vector const& section_sizes) @@ -193,13 +203,13 @@ gui_undo_listener::mouseUp(MouseEvent const& event) options = options.withMousePosition(); juce::PopupMenu undo_menu; - auto undo_stack = _gui->gui_state()->undo_stack(); + auto undo_stack = _gui->automation_state()->undo_stack(); for(int i = 0; i < undo_stack.size(); i++) undo_menu.addItem(i + 1, undo_stack[i]); menu.addSubMenu("Undo", undo_menu); juce::PopupMenu redo_menu; - auto redo_stack = _gui->gui_state()->redo_stack(); + auto redo_stack = _gui->automation_state()->redo_stack(); for (int i = 0; i < redo_stack.size(); i++) redo_menu.addItem(i + 1000 + 1, redo_stack[i]); menu.addSubMenu("Redo", redo_menu); @@ -209,26 +219,26 @@ gui_undo_listener::mouseUp(MouseEvent const& event) menu.showMenuAsync(options, [this](int result) { if(1 <= result && result < 1000) - _gui->gui_state()->undo(result - 1); + _gui->automation_state()->undo(result - 1); else if(1001 <= result && result < 2000) - _gui->gui_state()->redo(result - 1001); + _gui->automation_state()->redo(result - 1001); else if (2001 == result) { - auto state = plugin_io_save_instance_state(*_gui->gui_state(), true); + auto state = plugin_io_save_instance_state(*_gui->automation_state(), true); state.push_back('\0'); juce::SystemClipboard::copyTextToClipboard(juce::String(state.data())); } else if (2002 == result) { - plugin_state new_state(&_gui->gui_state()->desc(), false); + plugin_state new_state(&_gui->automation_state()->desc(), false); auto clip_contents = juce::SystemClipboard::getTextFromClipboard().toStdString(); std::vector clip_data(clip_contents.begin(), clip_contents.end()); auto load_result = plugin_io_load_instance_state(clip_data, new_state, true); if (load_result.ok() && !load_result.warnings.size()) { - _gui->gui_state()->begin_undo_region(); - _gui->gui_state()->copy_from(new_state.state(), true); - _gui->gui_state()->end_undo_region("Paste", "Patch"); + _gui->automation_state()->begin_undo_region(); + _gui->automation_state()->copy_from(new_state.state(), true); + _gui->automation_state()->end_undo_region("Paste", "Patch"); } else { @@ -335,15 +345,25 @@ plugin_gui:: plugin_gui:: plugin_gui( - plugin_state* gui_state, plugin_base::extra_state* extra_state, - std::vector* mod_indicator_states): -_gui_state(gui_state), _undo_listener(this), _extra_state(extra_state), _mod_indicator_states(mod_indicator_states) + plugin_state* automation_state, plugin_base::extra_state* extra_state, + std::vector* modulation_outputs): +_automation_state(automation_state), _global_modulation_state(&automation_state->desc(), false), +_undo_listener(this), _extra_state(extra_state), _modulation_outputs(modulation_outputs) { PB_LOG_FUNC_ENTRY_EXIT(); setOpaque(true); addMouseListener(&_undo_listener, true); - auto const& topo = *gui_state->desc().plugin; - theme_changed(user_io_load_list(topo.vendor, topo.full_name, user_io::base, user_state_theme_key, topo.gui.default_theme, gui_state->desc().plugin->themes())); + auto const& topo = *automation_state->desc().plugin; + _engine_voices_active.resize(automation_state->desc().plugin->audio_polyphony); + _engine_voices_activated.resize(automation_state->desc().plugin->audio_polyphony); + _global_modulation_state.copy_from(automation_state->state(), false); + _voice_modulation_states.resize(automation_state->desc().plugin->audio_polyphony); + for (int i = 0; i < automation_state->desc().plugin->audio_polyphony; i++) + { + new(&_voice_modulation_states[i]) plugin_state(&automation_state->desc(), false); + _voice_modulation_states[i].copy_from(automation_state->state(), false); + } + theme_changed(user_io_load_list(topo.vendor, topo.full_name, user_io::base, user_state_theme_key, topo.gui.default_theme, automation_state->desc().plugin->themes())); } void @@ -373,15 +393,15 @@ plugin_gui::theme_changed(std::string const& theme_name) _module_lnfs.clear(); _custom_lnfs.clear(); - _lnf = std::make_unique(&gui_state()->desc(), theme_name, -1, -1, -1); + _lnf = std::make_unique(&automation_state()->desc(), theme_name, -1, -1, -1); setLookAndFeel(_lnf.get()); - auto const& topo = *gui_state()->desc().plugin; + auto const& topo = *automation_state()->desc().plugin; bool is_fx = topo.type == plugin_type::fx; - for (int i = 0; i < gui_state()->desc().plugin->gui.custom_sections.size(); i++) - _custom_lnfs[i] = std::make_unique(&_gui_state->desc(), _lnf->theme(), i, -1, -1); - for (int i = 0; i < gui_state()->desc().plugin->modules.size(); i++) - _module_lnfs[i] = std::make_unique(&_gui_state->desc(), _lnf->theme(), -1, gui_state()->desc().plugin->modules[i].gui.section, i); + for (int i = 0; i < automation_state()->desc().plugin->gui.custom_sections.size(); i++) + _custom_lnfs[i] = std::make_unique(&_automation_state->desc(), _lnf->theme(), i, -1, -1); + for (int i = 0; i < automation_state()->desc().plugin->modules.size(); i++) + _module_lnfs[i] = std::make_unique(&_automation_state->desc(), _lnf->theme(), -1, automation_state()->desc().plugin->modules[i].gui.section, i); // note: default width and aspect ratios are contained in theme add_and_make_visible(*this, make_content()); @@ -399,7 +419,7 @@ plugin_gui::theme_changed(std::string const& theme_name) void plugin_gui::param_changed(int index, plain_value plain) { - if (_gui_state->desc().params[index]->param->dsp.direction == param_direction::input) + if (_automation_state->desc().params[index]->param->dsp.direction == param_direction::input) for (int i = 0; i < _param_listeners.size(); i++) _param_listeners[i]->gui_param_changed(index, plain); } @@ -407,7 +427,7 @@ plugin_gui::param_changed(int index, plain_value plain) void plugin_gui::param_begin_changes(int index) { - if (_gui_state->desc().params[index]->param->dsp.direction == param_direction::input) + if (_automation_state->desc().params[index]->param->dsp.direction == param_direction::input) for (int i = 0; i < _param_listeners.size(); i++) _param_listeners[i]->gui_param_begin_changes(index); } @@ -415,7 +435,7 @@ plugin_gui::param_begin_changes(int index) void plugin_gui::param_end_changes(int index) { - if (_gui_state->desc().params[index]->param->dsp.direction == param_direction::input) + if (_automation_state->desc().params[index]->param->dsp.direction == param_direction::input) for (int i = 0; i < _param_listeners.size(); i++) _param_listeners[i]->gui_param_end_changes(index); } @@ -423,7 +443,7 @@ plugin_gui::param_end_changes(int index) void plugin_gui::param_changing(int index, plain_value plain) { - if (_gui_state->desc().params[index]->param->dsp.direction == param_direction::input) + if (_automation_state->desc().params[index]->param->dsp.direction == param_direction::input) for (int i = 0; i < _param_listeners.size(); i++) _param_listeners[i]->gui_param_changing(index, plain); } @@ -452,8 +472,8 @@ plugin_gui::remove_tab_selection_listener(gui_tab_selection_listener* listener) void plugin_gui::fire_state_loaded() { - for(int i = 0; i < _gui_state->desc().param_count; i++) - param_changed(i, _gui_state->get_plain_at_index(i)); + for(int i = 0; i < _automation_state->desc().param_count; i++) + param_changed(i, _automation_state->get_plain_at_index(i)); } void @@ -507,7 +527,7 @@ void plugin_gui::module_mouse_enter(int module) { if(_last_mouse_enter_module == module) return; - int index = gui_state()->desc().modules[module].module->info.index; + int index = automation_state()->desc().modules[module].module->info.index; // tooltip may not be there yet during theme switching if(_tooltip) _tooltip->setLookAndFeel(_module_lnfs[index].get()); for(int i = 0; i < _gui_mouse_listeners.size(); i++) @@ -516,50 +536,116 @@ plugin_gui::module_mouse_enter(int module) } void -plugin_gui::add_mod_indicator_state_listener(mod_indicator_state_listener* listener) +plugin_gui::add_modulation_output_listener(modulation_output_listener* listener) { - auto& listeners = _mod_indicator_state_listeners; + auto& listeners = _modulation_output_listeners; assert(std::find(listeners.begin(), listeners.end(), listener) == listeners.end()); listeners.push_back(listener); } void -plugin_gui::remove_mod_indicator_state_listener(mod_indicator_state_listener* listener) +plugin_gui::remove_modulation_output_listener(modulation_output_listener* listener) { - auto& listeners = _mod_indicator_state_listeners; + auto& listeners = _modulation_output_listeners; auto iter = std::find(listeners.begin(), listeners.end(), listener); assert(iter != listeners.end()); listeners.erase(iter); } void -plugin_gui::mod_indicator_states_changed() +plugin_gui::automation_state_changed(int param_index, normalized_value normalized) +{ + _global_modulation_state.set_normalized_at_index(param_index, normalized); + for (int i = 0; i < _voice_modulation_states.size(); i++) + _voice_modulation_states[i].set_normalized_at_index(param_index, normalized); +} + +gui_visuals_mode +plugin_gui::get_visuals_mode() const +{ + auto const& visuals_params = _automation_state->desc().plugin->engine.visuals; + if (visuals_params.module_index != -1) + return (gui_visuals_mode)_automation_state->get_plain_at(visuals_params.module_index, 0, visuals_params.param_index, 0).step(); + return gui_visuals_mode_off; +} + +void +plugin_gui::modulation_outputs_changed() { - auto compare = [](auto const& l, auto const& r) { - if (l.data.module_global < r.data.module_global) return false; - if (l.data.module_global > r.data.module_global) return true; - if (l.data.param_global < r.data.param_global) return false; - if (l.data.param_global > r.data.param_global) return true; - return l.data.voice_index < r.data.voice_index; - }; - std::sort(_mod_indicator_states->begin(), _mod_indicator_states->end(), compare); - - auto pred = [](auto const& l, auto const& r) { - if (l.data.module_global != r.data.module_global) return false; - if (l.data.param_global != r.data.param_global) return false; - return l.data.voice_index == r.data.voice_index; - }; - auto it = std::unique(_mod_indicator_states->begin(), _mod_indicator_states->end(), pred); - _mod_indicator_states->erase(it, _mod_indicator_states->end()); - - for(auto listener_it: _mod_indicator_state_listeners) - listener_it->mod_indicator_state_changed(*_mod_indicator_states); + // clear stuff and possibly pick up on the next round + // needed when we go from not-off to off + gui_visuals_mode new_visuals_mode = get_visuals_mode(); + if (new_visuals_mode != _prev_visual_mode) + { + for (auto listener_it : _modulation_output_listeners) + listener_it->modulation_outputs_reset(); + _prev_visual_mode = new_visuals_mode; + return; + } + + // no need to do anything + _prev_visual_mode = new_visuals_mode; + if (new_visuals_mode == gui_visuals_mode_off) + return; + + std::sort(_modulation_outputs->begin(), _modulation_outputs->end()); + auto pred = [](auto const& l, auto const& r) { return !(l < r) && !(r < l); }; + auto it = std::unique(_modulation_outputs->begin(), _modulation_outputs->end(), pred); + _modulation_outputs->erase(it, _modulation_outputs->end()); + + // automation state is kept in check for all _global/_voice stuff + // only need to update modulation, see automation_state_changed + // however modulation events may not be accurate (may skip events) + // so occasionally we need to reset to automation state + // f.e. in case a modulator was disabled and we dont want to + // stick it to the last value (only for global -- voice will sort out itself) + const double mod_reset_interval = 1.0; + double seconds_now = seconds_since_epoch(); + if (seconds_now - _last_mod_reset_seconds >= mod_reset_interval) + { + _global_modulation_state.copy_from(_automation_state->state(), false); + _last_mod_reset_seconds = seconds_now; + for (auto listener_it : _modulation_output_listeners) + listener_it->modulation_outputs_reset(); + } + + // set all voices to automation by default, + // we will overwrite if that voice is active + + for (int i = 0; i < _modulation_outputs->size(); i++) + if ((*_modulation_outputs)[i].event_type() == out_event_voice_activation) + { + auto const& voice_event = (*_modulation_outputs)[i].state.voice; + _engine_voices_active[voice_event.voice_index] = voice_event.is_active; + _engine_voices_activated[voice_event.voice_index] = voice_event.stream_time_low; + + // revert to automation if needed + if (!voice_event.is_active) + _voice_modulation_states[voice_event.voice_index].copy_from(_automation_state->state(), false); + + } else if ((*_modulation_outputs)[i].event_type() == out_event_param_state) + { + auto const& param_event = (*_modulation_outputs)[i].state.param; + int param_index = param_event.param_global; + + // debugging + auto const& desc = _automation_state->desc().params[param_index]; + (void)desc; + + if(param_event.voice_index == -1) + _global_modulation_state.set_normalized_at_index(param_index, normalized_value(param_event.normalized_real())); + else + _voice_modulation_states[param_event.voice_index].set_normalized_at_index(param_index, normalized_value(param_event.normalized_real())); + } + + for(auto listener_it: _modulation_output_listeners) + listener_it->modulation_outputs_changed(*_modulation_outputs); } void plugin_gui::add_tab_menu_listener(juce::TabBarButton& button, int module, int slot) { - auto listener = std::make_unique(this, gui_state(), _lnf.get(), &button, module, slot); + auto listener = std::make_unique(this, automation_state(), _lnf.get(), &button, module, slot); _tab_menu_listeners.push_back(std::move(listener)); } @@ -591,8 +677,8 @@ void plugin_gui::resized() { float w = getLocalBounds().getWidth(); - auto const& topo = *_gui_state->desc().plugin; - bool is_fx = _gui_state->desc().plugin->type == plugin_type::fx; + auto const& topo = *_automation_state->desc().plugin; + bool is_fx = _automation_state->desc().plugin->type == plugin_type::fx; float user_scale = (w / _lnf->global_settings().get_default_width(is_fx)) / _system_dpi_scale; getChildComponent(0)->setTransform(AffineTransform::scale(user_scale * _system_dpi_scale)); user_io_save_num(topo.vendor, topo.full_name, user_io::base, user_state_scale_key, user_scale); @@ -604,7 +690,7 @@ plugin_gui::get_module_graph_engine(module_topo const& module) if(module.graph_engine_factory == nullptr) return nullptr; auto iter = _module_graph_engines.find(module.info.index); if(iter != _module_graph_engines.end()) return iter->second.get(); - _module_graph_engines[module.info.index] = module.graph_engine_factory(&_gui_state->desc()); + _module_graph_engines[module.info.index] = module.graph_engine_factory(&_automation_state->desc()); return _module_graph_engines[module.info.index].get(); } @@ -636,9 +722,9 @@ void plugin_gui::reloaded() { PB_LOG_FUNC_ENTRY_EXIT(); - auto const& topo = *_gui_state->desc().plugin; + auto const& topo = *_automation_state->desc().plugin; auto settings = _lnf->global_settings(); - bool is_fx = _gui_state->desc().plugin->type == plugin_type::fx; + bool is_fx = _automation_state->desc().plugin->type == plugin_type::fx; int default_width = settings.get_default_width(is_fx); float ratio = settings.get_aspect_ratio_height(is_fx) / (float)settings.get_aspect_ratio_width(is_fx); float user_scale = user_io_load_num(topo.vendor, topo.full_name, user_io::base, user_state_scale_key, 1.0, @@ -650,7 +736,7 @@ plugin_gui::reloaded() Component& plugin_gui::make_content() { - auto const& topo = *_gui_state->desc().plugin; + auto const& topo = *_automation_state->desc().plugin; auto& grid = make_component(topo.gui.dimension_factory(_lnf->global_settings()), margin_module, margin_module, 0, 0); for(int s = 0; s < topo.gui.custom_sections.size(); s++) grid.add(make_custom_section(topo.gui.custom_sections[s]), topo.gui.custom_sections[s].position); @@ -700,9 +786,9 @@ plugin_gui::make_tab_component( void plugin_gui::add_component_tab(TabbedComponent& tc, Component& child, int module, std::string const& title) { - auto const& topo = *_gui_state->desc().plugin; - int module_slot = _gui_state->desc().modules[module].info.slot; - int module_index = _gui_state->desc().modules[module].info.topo; + auto const& topo = *_automation_state->desc().plugin; + int module_slot = _automation_state->desc().modules[module].info.slot; + int module_index = _automation_state->desc().modules[module].info.topo; auto& margin = make_component(&child, BorderSize(margin_module, 0, 0, 0)); tc.addTab(title, Colours::transparentBlack, &margin, false); auto tab_button = tc.getTabbedButtonBar().getTabButton(tc.getTabbedButtonBar().getNumTabs() - 1); @@ -994,7 +1080,7 @@ plugin_gui::make_param_section(module_desc const& module, param_section const& s Component& plugin_gui::make_module_section(module_section_gui const& section) { - auto const& modules = _gui_state->desc().modules; + auto const& modules = _automation_state->desc().modules; if (!section.tabbed) { auto& grid = make_component(section.dimension, margin_module, margin_module, 0, 0); @@ -1005,7 +1091,7 @@ plugin_gui::make_module_section(module_section_gui const& section) } int matched_module = -1; - auto const& topo = *_gui_state->desc().plugin; + auto const& topo = *_automation_state->desc().plugin; for (int i = 0; i < topo.modules.size(); i++) if (topo.modules[i].gui.section == section.index) matched_module = i; @@ -1213,10 +1299,10 @@ plugin_gui::init_patch() if(result == 1) { _extra_state->clear(); - _gui_state->begin_undo_region(); - _gui_state->init(state_init_type::default_, true); + _automation_state->begin_undo_region(); + _automation_state->init(state_init_type::default_, true); fire_state_loaded(); - _gui_state->end_undo_region("Init", "Patch"); + _automation_state->end_undo_region("Init", "Patch"); } }); } @@ -1231,10 +1317,10 @@ plugin_gui::clear_patch() if (result == 1) { _extra_state->clear(); - _gui_state->begin_undo_region(); - _gui_state->init(state_init_type::minimal, true); + _automation_state->begin_undo_region(); + _automation_state->init(state_init_type::minimal, true); fire_state_loaded(); - _gui_state->end_undo_region("Clear", "Patch"); + _automation_state->end_undo_region("Clear", "Patch"); } }); } @@ -1244,12 +1330,12 @@ plugin_gui::save_patch() { PB_LOG_FUNC_ENTRY_EXIT(); int save_flags = FileBrowserComponent::saveMode | FileBrowserComponent::warnAboutOverwriting; - FileChooser* chooser = new FileChooser("Save Patch", File(), String("*.") + _gui_state->desc().plugin->extension, true, false, this); + FileChooser* chooser = new FileChooser("Save Patch", File(), String("*.") + _automation_state->desc().plugin->extension, true, false, this); chooser->launchAsync(save_flags, [this](FileChooser const& chooser) { auto path = chooser.getResult().getFullPathName(); delete& chooser; if (path.length() == 0) return; - plugin_io_save_file_patch_state(path.toStdString(), *_gui_state); + plugin_io_save_file_patch_state(path.toStdString(), *_automation_state); }); } @@ -1258,7 +1344,7 @@ plugin_gui::load_patch() { PB_LOG_FUNC_ENTRY_EXIT(); int load_flags = FileBrowserComponent::openMode; - FileChooser* chooser = new FileChooser("Load Patch", File(), String("*.") + _gui_state->desc().plugin->extension, true, false, this); + FileChooser* chooser = new FileChooser("Load Patch", File(), String("*.") + _automation_state->desc().plugin->extension, true, false, this); chooser->launchAsync(load_flags, [this](FileChooser const& chooser) { auto path = chooser.getResult().getFullPathName(); delete& chooser; @@ -1271,9 +1357,9 @@ void plugin_gui::load_patch(std::string const& path, bool preset) { PB_LOG_FUNC_ENTRY_EXIT(); - _gui_state->begin_undo_region(); + _automation_state->begin_undo_region(); auto icon = MessageBoxIconType::WarningIcon; - auto result = plugin_io_load_file_patch_state(path, *_gui_state); + auto result = plugin_io_load_file_patch_state(path, *_automation_state); if (result.error.size()) { auto options = MessageBoxOptions::makeOptionsOk(icon, "Error", result.error, String(), this); @@ -1284,7 +1370,7 @@ plugin_gui::load_patch(std::string const& path, bool preset) if(preset) _extra_state->clear(); fire_state_loaded(); - _gui_state->end_undo_region("Load", preset ? "Preset" : "Patch"); + _automation_state->end_undo_region("Load", preset ? "Preset" : "Patch"); if (result.warnings.size()) { diff --git a/plugin_base/src/plugin_base/plugin_base/gui/gui.hpp b/plugin_base/src/plugin_base/plugin_base/gui/gui.hpp index 4a9bac9bf..44933e543 100644 --- a/plugin_base/src/plugin_base/plugin_base/gui/gui.hpp +++ b/plugin_base/src/plugin_base/plugin_base/gui/gui.hpp @@ -20,7 +20,11 @@ class plugin_gui; class tab_component; class grid_component; +std::vector +gui_visuals_items(); + enum class gui_hover_type { param, module, custom }; +enum gui_visuals_mode { gui_visuals_mode_off, gui_visuals_mode_params, gui_visuals_mode_full }; // globally saved (across instances) inline std::string const user_state_scale_key = "scale"; @@ -115,11 +119,13 @@ public juce::MouseListener _gui(gui), _global_index(global_index), _type(type), _component(component) { _component->addMouseListener(this, true); } }; -class mod_indicator_state_listener +class modulation_output_listener { public: + virtual void + modulation_outputs_reset() = 0; virtual void - mod_indicator_state_changed(std::vector const& states) = 0; + modulation_outputs_changed(std::vector const& outputs) = 0; }; class plugin_gui: @@ -132,8 +138,8 @@ public juce::Component PB_PREVENT_ACCIDENTAL_COPY(plugin_gui); ~plugin_gui(); plugin_gui( - plugin_state* gui_state, plugin_base::extra_state* extra_state, - std::vector* mod_indicator_states); + plugin_state* automation_state, plugin_base::extra_state* extra_state, + std::vector* modulation_outputs); void load_patch(); void save_patch(); @@ -158,7 +164,10 @@ public juce::Component void theme_changed(std::string const& theme_name); // for anyone who wants to repaint on this stuff - void mod_indicator_states_changed(); + void modulation_outputs_changed(); + + // to keep track of what the audio engine is doing + void automation_state_changed(int param_index, normalized_value normalized); void param_end_changes(int index); void param_begin_changes(int index); @@ -172,12 +181,17 @@ public juce::Component lnf const* get_lnf() const { return _lnf.get(); } void paint(juce::Graphics& g) override { g.fillAll(juce::Colours::black); } - plugin_state* gui_state() const { return _gui_state; } + std::vector const& engine_voices_active() const { return _engine_voices_active; } + std::vector const& engine_voices_activated() const { return _engine_voices_activated; } + + gui_visuals_mode get_visuals_mode() const; extra_state* extra_state_() const { return _extra_state; } - std::vector const* mod_indicator_states() const { return _mod_indicator_states; } + plugin_state* automation_state() const { return _automation_state; } + plugin_state const& global_modulation_state() const { return _global_modulation_state; } + plugin_state const& voice_modulation_state(int voice) const { return _voice_modulation_states[voice]; } - void add_mod_indicator_state_listener(mod_indicator_state_listener* listener); - void remove_mod_indicator_state_listener(mod_indicator_state_listener* listener); + void add_modulation_output_listener(modulation_output_listener* listener); + void remove_modulation_output_listener(modulation_output_listener* listener); void remove_param_listener(gui_param_listener* listener); void remove_gui_mouse_listener(gui_mouse_listener* listener); @@ -188,15 +202,27 @@ public juce::Component private: - float _system_dpi_scale = 1.0f; std::unique_ptr _lnf = {}; - plugin_state* const _gui_state; + float _system_dpi_scale = 1.0f; + double _last_mod_reset_seconds = 0.0; + gui_visuals_mode _prev_visual_mode = (gui_visuals_mode)-1; + + // this one mirrors the static values of the parameters + plugin_state* const _automation_state; + + // these ones mirrors what the audio engine is actually doing on a per-block basis + // not a pointer since this is owned by us, while _automation_state is mutated from outside + plugin_state _global_modulation_state; + std::vector _voice_modulation_states = {}; + gui_undo_listener _undo_listener; int _last_mouse_enter_param = -1; int _last_mouse_enter_module = -1; int _last_mouse_enter_custom = -1; plugin_base::extra_state* const _extra_state; - std::vector* _mod_indicator_states = {}; + std::vector _engine_voices_active = {}; // don't like vector bool + std::vector _engine_voices_activated = {}; // don't like vector bool + std::vector* _modulation_outputs = {}; std::unique_ptr _tooltip = {}; std::map> _module_lnfs = {}; std::map> _custom_lnfs = {}; @@ -204,7 +230,7 @@ public juce::Component std::vector _param_listeners = {}; std::vector _gui_mouse_listeners = {}; std::vector _tab_selection_listeners = {}; - std::vector _mod_indicator_state_listeners = {}; + std::vector _modulation_output_listeners = {}; // must be destructed first, will unregister listeners, mind order std::vector> _components = {}; std::vector> _hover_listeners = {}; diff --git a/plugin_base/src/plugin_base/plugin_base/gui/lnf.cpp b/plugin_base/src/plugin_base/plugin_base/gui/lnf.cpp index d83ae6149..8be9ae04d 100644 --- a/plugin_base/src/plugin_base/plugin_base/gui/lnf.cpp +++ b/plugin_base/src/plugin_base/plugin_base/gui/lnf.cpp @@ -77,7 +77,7 @@ override_colors(gui_colors const& base, var const& json) result.tab_header = override_color_if_present(json, "tab_header", result.tab_header); result.graph_grid = override_color_if_present(json, "graph_grid", result.graph_grid); result.graph_text = override_color_if_present(json, "graph_text", result.graph_text); - result.graph_mod_indicator = override_color_if_present(json, "graph_mod_indicator", result.graph_mod_indicator); + result.graph_modulation_bubble = override_color_if_present(json, "graph_modulation_bubble", result.graph_modulation_bubble); result.graph_background = override_color_if_present(json, "graph_background", result.graph_background); result.graph_area = override_color_if_present(json, "graph_area", result.graph_area); result.graph_line = override_color_if_present(json, "graph_line", result.graph_line); @@ -828,33 +828,33 @@ lnf::drawRotarySlider(Graphics& g, int, int, int, int, float pos, float, float, if (!ps) return; - // modulatable indicator + // can modulate indicator if(ps->param()->param->dsp.can_modulate(ps->param()->info.slot)) { g.setColour(colors().slider_can_modulate); g.fillEllipse(left + size / 3, top + size / 3, size / 3, size / 3); } - if (ps->max_mod_indicator() < 0.0f) return; + if (ps->max_modulation_output() < 0.0f) return; - // modulation indication + // actual modulation outputs Path path; auto modulation_color = colors().slider_modulation; if (!s.isEnabled()) modulation_color = color_to_grayscale(modulation_color); g.setColour(modulation_color); float half_mod_angle = start_angle + 0.5f * angle_range; - float min_mod_angle = start_angle + ps->min_mod_indicator() * angle_range; - float max_mod_angle = start_angle + ps->max_mod_indicator() * angle_range; + float min_mod_angle = start_angle + ps->min_modulation_output() * angle_range; + float max_mod_angle = start_angle + ps->max_modulation_output() * angle_range; if (!bipolar) { - if(ps->max_mod_indicator() - ps->min_mod_indicator() <= 0.05f) + if(ps->max_modulation_output() - ps->min_modulation_output() <= 0.05f) path.addArc(left - 3, top - 3, size + 6, size + 6, start_angle, max_mod_angle, true); else path.addArc(left - 3, top - 3, size + 6, size + 6, min_mod_angle, max_mod_angle, true); } - else if(ps->max_mod_indicator() - ps->min_mod_indicator() > 0.05f) + else if(ps->max_modulation_output() - ps->min_modulation_output() > 0.05f) path.addArc(left - 3, top - 3, size + 6, size + 6, min_mod_angle, max_mod_angle, true); - else if(ps->max_mod_indicator() >= 0.5f) + else if(ps->max_modulation_output() >= 0.5f) path.addArc(left - 3, top - 3, size + 6, size + 6, half_mod_angle, max_mod_angle, true); else path.addArc(left - 3, top - 3, size + 6, size + 6, max_mod_angle, half_mod_angle, true); @@ -877,8 +877,8 @@ lnf::drawLinearSlider(Graphics& g, int x, int y, int w, int h, float p, float, f float width = s.getWidth() - padh; float centerx = left + width / 2; - float min_mod_pos = ps ? ps->min_mod_indicator() : -1.0f; - float max_mod_pos = ps ? ps->max_mod_indicator() : -1.0f; + float min_mod_pos = ps ? ps->min_modulation_output() : -1.0f; + float max_mod_pos = ps ? ps->max_modulation_output() : -1.0f; assert(style == Slider::SliderStyle::LinearHorizontal); @@ -916,7 +916,7 @@ lnf::drawLinearSlider(Graphics& g, int x, int y, int w, int h, float p, float, f g.setColour(colors().slider_background); g.fillRoundedRectangle(left + 1, top + 1, width - 2, height - 2, 2); - // modulation indicator + // actual modulation outputs g.setColour(colors().slider_background); if(max_mod_pos >= 0.0f) if(!bipolar) diff --git a/plugin_base/src/plugin_base/plugin_base/topo/module.hpp b/plugin_base/src/plugin_base/plugin_base/topo/module.hpp index 084127d56..d46a07195 100644 --- a/plugin_base/src/plugin_base/plugin_base/topo/module.hpp +++ b/plugin_base/src/plugin_base/plugin_base/topo/module.hpp @@ -28,7 +28,7 @@ class state_converter; enum class module_output { none, cv, audio }; enum class module_stage { input, voice, output }; -struct mod_indicator_source +struct modulation_output_source { int module_index; int module_slot; @@ -93,9 +93,9 @@ typedef std::function - module_mod_indicator_source_selector; + modulation_output_source_selector; // module topo mapping struct module_topo_mapping final { @@ -138,6 +138,8 @@ struct module_topo_gui final { bool enable_tab_menu = true; module_tab_menu_handler_factory menu_handler_factory; + bool render_automation_state = false; // or modulation state / for graphs + PB_PREVENT_ACCIDENTAL_COPY_DEFAULT_CTOR(module_topo_gui); }; @@ -190,7 +192,7 @@ struct module_topo final { module_engine_factory engine_factory; module_graph_engine_factory graph_engine_factory; module_state_converter_factory state_converter_factory; - module_mod_indicator_source_selector mod_indicator_source_selector; + modulation_output_source_selector mod_output_source_selector; PB_PREVENT_ACCIDENTAL_COPY_DEFAULT_CTOR(module_topo); void validate(plugin_topo const& plugin, int index) const; diff --git a/plugin_base/src/plugin_base/plugin_base/topo/plugin.hpp b/plugin_base/src/plugin_base/plugin_base/topo/plugin.hpp index 81801fbad..12539aef9 100644 --- a/plugin_base/src/plugin_base/plugin_base/topo/plugin.hpp +++ b/plugin_base/src/plugin_base/plugin_base/topo/plugin.hpp @@ -142,8 +142,11 @@ struct engine_param struct engine_params { + // realtime visualization + engine_param visuals = {}; + // smooths parameter automation, midi and bpm changes, use -1 for defaults, - // must resolve to real parameter indicating nr of milliseconds to smooth engine_param bpm_smoothing = {}; + // must resolve to real parameter indicating nr of milliseconds to smooth engine_param bpm_smoothing = {}; engine_param midi_smoothing = {}; engine_param automation_smoothing = {}; diff --git a/plugin_base/src/plugin_base/plugin_base/topo/shared.hpp b/plugin_base/src/plugin_base/plugin_base/topo/shared.hpp index c5b3214a2..2f633eb40 100644 --- a/plugin_base/src/plugin_base/plugin_base/topo/shared.hpp +++ b/plugin_base/src/plugin_base/plugin_base/topo/shared.hpp @@ -188,7 +188,7 @@ struct gui_colors final { juce::Colour graph_background = juce::Colour(0xFF00FF00); juce::Colour graph_area = juce::Colour(0xFF00FF00); juce::Colour graph_line = juce::Colour(0xFF00FF00); - juce::Colour graph_mod_indicator = juce::Colour(0xFF00FF00); + juce::Colour graph_modulation_bubble = juce::Colour(0xFF00FF00); juce::Colour bubble_outline = juce::Colour(0xFF00FF00); juce::Colour slider_background = juce::Colour(0xFF00FF00); juce::Colour slider_highlight = juce::Colour(0xFF00FF00); diff --git a/scripts/build_linux.sh b/scripts/build_linux.sh index e1cd8b4bc..35828906f 100644 --- a/scripts/build_linux.sh +++ b/scripts/build_linux.sh @@ -10,9 +10,9 @@ make cd ../../../dist/"$1"/linux ./plugin_base.ref_gen firefly_synth_1.vst3/Contents/x86_64-linux/firefly_synth_1.so ../../../param_reference.html -zip -r firefly_synth_1.9.0_linux_vst3_fx.zip firefly_synth_fx_1.vst3 -zip -r firefly_synth_1.9.0_linux_vst3_instrument.zip firefly_synth_1.vst3 -zip -r firefly_synth_1.9.0_linux_clap_fx.zip firefly_synth_fx_1.clap -zip -r firefly_synth_1.9.0_linux_clap_instrument.zip firefly_synth_1.clap +zip -r firefly_synth_1.9.1_linux_vst3_fx.zip firefly_synth_fx_1.vst3 +zip -r firefly_synth_1.9.1_linux_vst3_instrument.zip firefly_synth_1.vst3 +zip -r firefly_synth_1.9.1_linux_clap_fx.zip firefly_synth_fx_1.clap +zip -r firefly_synth_1.9.1_linux_clap_instrument.zip firefly_synth_1.clap cd ../../../scripts diff --git a/scripts/build_mac.sh b/scripts/build_mac.sh index 1b53d76fe..1cb1f5f6c 100755 --- a/scripts/build_mac.sh +++ b/scripts/build_mac.sh @@ -11,9 +11,9 @@ make cd ../../../dist/"$1"/mac ./plugin_base.ref_gen firefly_synth_1.vst3/Contents/MacOS/firefly_synth_1 ../../../param_reference.html -zip -r firefly_synth_1.9.0_mac_vst3_fx.zip firefly_synth_fx_1.vst3 -zip -r firefly_synth_1.9.0_mac_vst3_instrument.zip firefly_synth_1.vst3 -zip -r firefly_synth_1.9.0_mac_clap_fx.zip firefly_synth_fx_1.clap -zip -r firefly_synth_1.9.0_mac_clap_instrument.zip firefly_synth_1.clap +zip -r firefly_synth_1.9.1_mac_vst3_fx.zip firefly_synth_fx_1.vst3 +zip -r firefly_synth_1.9.1_mac_vst3_instrument.zip firefly_synth_1.vst3 +zip -r firefly_synth_1.9.1_mac_clap_fx.zip firefly_synth_fx_1.clap +zip -r firefly_synth_1.9.1_mac_clap_instrument.zip firefly_synth_1.clap cd ../../../scripts diff --git a/scripts/build_windows.bat b/scripts/build_windows.bat index 512b686ad..114559c5c 100644 --- a/scripts/build_windows.bat +++ b/scripts/build_windows.bat @@ -13,10 +13,10 @@ cd ..\..\dist\"%1"\win plugin_base.ref_gen.exe firefly_synth_1.vst3\Contents\x86_64-win\firefly_synth_1.vst3 ..\..\..\param_reference.html if %errorlevel% neq 0 exit /b !errorlevel! -7z a -tzip firefly_synth_1.9.0_windows_vst3_fx.zip firefly_synth_fx_1.vst3\* -7z a -tzip firefly_synth_1.9.0_windows_vst3_instrument.zip firefly_synth_1.vst3\* -7z a -tzip firefly_synth_1.9.0_windows_clap_fx.zip firefly_synth_fx_1.clap\* -7z a -tzip firefly_synth_1.9.0_windows_clap_instrument.zip firefly_synth_1.clap\* +7z a -tzip firefly_synth_1.9.1_windows_vst3_fx.zip firefly_synth_fx_1.vst3\* +7z a -tzip firefly_synth_1.9.1_windows_vst3_instrument.zip firefly_synth_1.vst3\* +7z a -tzip firefly_synth_1.9.1_windows_clap_fx.zip firefly_synth_fx_1.clap\* +7z a -tzip firefly_synth_1.9.1_windows_clap_instrument.zip firefly_synth_1.clap\* if %errorlevel% neq 0 exit /b !errorlevel! cd ..\..\..\..\scripts \ No newline at end of file diff --git a/src/firefly_synth/firefly_synth/envelope.cpp b/src/firefly_synth/firefly_synth/envelope.cpp index baf93e2fa..3450e7a58 100644 --- a/src/firefly_synth/firefly_synth/envelope.cpp +++ b/src/firefly_synth/firefly_synth/envelope.cpp @@ -580,17 +580,15 @@ env_engine::process(plugin_block& block, cv_cv_matrix_mixdown const* modulation) else process_mono(block, modulation); - // only want the indicators for the actual audio engine + // only want the mod outputs for the actual audio engine if (block.graph) return; if (_stage == env_stage::end) return; float flt = block.state.own_block_automation[param_filter][0].real() / 1000.0f; - mod_indicator_state indicator_state = {}; - indicator_state.data.param_global = -1; - indicator_state.data.voice_index = block.voice->state.slot; - indicator_state.data.module_global = block.module_desc_.info.global; - indicator_state.data.value = _total_pos / (_dly + _att + _hld + _dcy + _rls + flt); - block.push_mod_indicator_state(indicator_state); + block.push_modulation_output(modulation_output::make_mod_output_cv_state( + block.voice->state.slot, + block.module_desc_.info.global, + _total_pos / (_dly + _att + _hld + _dcy + _rls + flt))); } template diff --git a/src/firefly_synth/firefly_synth/lfo.cpp b/src/firefly_synth/firefly_synth/lfo.cpp index aa14f358c..567bbf79f 100644 --- a/src/firefly_synth/firefly_synth/lfo.cpp +++ b/src/firefly_synth/firefly_synth/lfo.cpp @@ -180,7 +180,7 @@ render_graph( float freq = lfo_frequency_from_state(state, mapping.module_index, mapping.module_slot, 120); // make sure we exactly plot 1 cycle otherwise - // we cannot not where to put the mod indicators + // we cannot know where to put the mod outputs sample_rate = params.max_frame_count * freq; if (!sync) partition = float_to_string(1 / freq, 2) + " Sec"; @@ -579,16 +579,12 @@ lfo_engine::process(plugin_block& block, cv_cv_matrix_mixdown const* modulation) return; } - // only want the indicators for the actual audio engine + // only want the mod outputs for the actual audio engine if (!block.graph) - { - mod_indicator_state indicator_state = {}; - indicator_state.data.param_global = -1; - indicator_state.data.module_global = block.module_desc_.info.global; - indicator_state.data.voice_index = _global ? 0 : block.voice->state.slot; - indicator_state.data.value = type == type_repeat ? _ref_phase : _graph_phase; - block.push_mod_indicator_state(indicator_state); - } + block.push_modulation_output(modulation_output::make_mod_output_cv_state( + _global ? -1 : block.voice->state.slot, + block.module_desc_.info.global, + type == type_repeat ? _ref_phase : _graph_phase)); if(_stage == lfo_stage::end) { diff --git a/src/firefly_synth/firefly_synth/master_settings.cpp b/src/firefly_synth/firefly_synth/master_settings.cpp index 4ac6e585b..2a5a3ab41 100644 --- a/src/firefly_synth/firefly_synth/master_settings.cpp +++ b/src/firefly_synth/firefly_synth/master_settings.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -13,14 +14,15 @@ namespace firefly_synth { static int const max_auto_smoothing_ms = 50; static int const max_other_smoothing_ms = 1000; -enum { section_tuning, section_preset, section_smoothing }; -enum { param_tuning_mode, param_preset, param_midi_smooth, param_tempo_smooth, param_auto_smooth, param_count }; +enum { section_tuning, section_preset, section_smoothing, section_visuals }; +enum { param_tuning_mode, param_preset, param_midi_smooth, param_tempo_smooth, param_auto_smooth, param_visuals, param_count }; // we provide the buttons, everyone else needs to implement it +extern int const master_settings_param_visuals = param_visuals; +extern int const master_settings_param_tuning_mode = param_tuning_mode; extern int const master_settings_param_auto_smooth = param_auto_smooth; extern int const master_settings_param_midi_smooth = param_midi_smooth; extern int const master_settings_param_tempo_smooth = param_tempo_smooth; -extern int const master_settings_param_tuning_mode = param_tuning_mode; static graph_data render_graph(plugin_state const& state, graph_engine* engine, int param, param_topo_mapping const& mapping) @@ -40,7 +42,7 @@ module_topo master_settings_topo(int section, gui_position const& pos, bool is_fx, plugin_base::plugin_topo const* plugin) { std::vector row_distribution = { 1 }; - std::vector column_distribution = { -153, -225, 1 }; + std::vector column_distribution = { -153, -225, -427, 1 }; module_topo result(make_module( make_topo_info_basic("{7F400614-E996-4B02-9B78-80E22F1C44A4}", "Master", module_master_settings, 1), make_module_dsp(module_stage::input, module_output::none, 0, {}), @@ -55,7 +57,8 @@ master_settings_topo(int section, gui_position const& pos, bool is_fx, plugin_ba make_param_section_gui({ 0, 0 }, { 1, 1 }))); auto& tuning_mode = result.params.emplace_back(make_param( make_topo_info_basic("{28C619C2-C04E-4BD6-8D84-89667E1A5659}", "Tuning", param_tuning_mode, 1), - make_param_dsp_input(false, param_automate::none), make_domain_item(engine_tuning_mode_items(), engine_tuning_mode_items()[1].name), + make_param_dsp_input(false, param_automate::none), make_domain_item( + engine_tuning_mode_items(), engine_tuning_mode_items()[engine_tuning_mode_on_note_before_mod].name), make_param_gui_single(section_tuning, gui_edit_type::autofit_list, { 0, 0, 1, 1 }, make_label(gui_label_contents::name, gui_label_align::left, gui_label_justify::near)))); tuning_mode.info.is_per_instance = true; @@ -80,26 +83,39 @@ master_settings_topo(int section, gui_position const& pos, bool is_fx, plugin_ba make_param_section_gui({ 0, 2 }, { { 1 }, { gui_dimension::auto_size, 1, gui_dimension::auto_size, 1, gui_dimension::auto_size, 1 } }, gui_label_edit_cell_split::horizontal))); auto& midi_smooth = result.params.emplace_back(make_param( - make_topo_info_basic("{97BAC4E5-B7EE-43AE-AE6D-B9D4A0A3C94F}", "MIDI Smoothing", param_midi_smooth, 1), + make_topo_info_basic("{97BAC4E5-B7EE-43AE-AE6D-B9D4A0A3C94F}", "MIDI Smooth", param_midi_smooth, 1), make_param_dsp_input(false, param_automate::none), make_domain_linear(1, max_other_smoothing_ms, 50, 0, "Ms"), - make_param_gui_single(section_smoothing, gui_edit_type::hslider, { 0, 0, 1, 1 }, + make_param_gui_single(section_smoothing, gui_edit_type::knob, { 0, 0, 1, 1 }, make_label(gui_label_contents::name, gui_label_align::left, gui_label_justify::near)))); midi_smooth.info.description = "Smoothing MIDI controller changes."; midi_smooth.info.is_per_instance = true; auto& bpm_smooth = result.params.emplace_back(make_param( - make_topo_info_basic("{99B88F8C-6F3B-47E9-932F-D46136252223}", "BPM Smoothing", param_tempo_smooth, 1), + make_topo_info_basic("{99B88F8C-6F3B-47E9-932F-D46136252223}", "BPM Smooth", param_tempo_smooth, 1), make_param_dsp_input(false, param_automate::none), make_domain_linear(1, max_other_smoothing_ms, 200, 0, "Ms"), - make_param_gui_single(section_smoothing, gui_edit_type::hslider, { 0, 2, 1, 1 }, + make_param_gui_single(section_smoothing, gui_edit_type::knob, { 0, 2, 1, 1 }, make_label(gui_label_contents::name, gui_label_align::left, gui_label_justify::near)))); bpm_smooth.info.description = "Smoothing host BPM parameter changes. Affects tempo-synced delay lines."; bpm_smooth.info.is_per_instance = true; auto& auto_smooth = result.params.emplace_back(make_param( - make_topo_info_basic("{AF6D2954-3B17-4A32-895B-FB92433761D6}", "Automation Smoothing", param_auto_smooth, 1), + make_topo_info_basic("{AF6D2954-3B17-4A32-895B-FB92433761D6}", "Automation Smooth", param_auto_smooth, 1), make_param_dsp_input(false, param_automate::none), make_domain_linear(1, max_auto_smoothing_ms, 1, 0, "Ms"), - make_param_gui_single(section_smoothing, gui_edit_type::hslider, { 0, 4, 1, 1 }, + make_param_gui_single(section_smoothing, gui_edit_type::knob, { 0, 4, 1, 1 }, make_label(gui_label_contents::name, gui_label_align::left, gui_label_justify::near)))); auto_smooth.info.description = "Smoothing automation parameter changes."; auto_smooth.info.is_per_instance = true; + + result.sections.emplace_back(make_param_section(section_visuals, + make_topo_tag_basic("{1E51EDB5-E38A-4401-80F0-789D10C965FA}", "Visuals"), + make_param_section_gui({ 0, 3 }, { 1, 1 }))); + auto& visuals = result.params.emplace_back(make_param( + make_topo_info("{D945D383-3BE2-433C-8822-CB0D9E5514D4}", true, "Realtime Visuals", "Visuals", "Visuals", param_visuals, 1), + make_param_dsp_input(false, param_automate::none), make_domain_item( + gui_visuals_items(), gui_visuals_items()[gui_visuals_mode_full].name), + make_param_gui_single(section_visuals, gui_edit_type::autofit_list, { 0, 0, 1, 1 }, + make_label(gui_label_contents::name, gui_label_align::left, gui_label_justify::near)))); + visuals.info.is_per_instance = true; + visuals.info.description = "Realtime visualization mode"; + return result; } diff --git a/src/firefly_synth/firefly_synth/matrix_cv.cpp b/src/firefly_synth/firefly_synth/matrix_cv.cpp index ab4cc3a69..2a6db4370 100644 --- a/src/firefly_synth/firefly_synth/matrix_cv.cpp +++ b/src/firefly_synth/firefly_synth/matrix_cv.cpp @@ -32,8 +32,8 @@ enum { param_type, param_source, param_target, param_offset, param_scale, param_ enum { type_off, type_mul_abs, type_mul_rel, type_mul_stk, type_add_abs, type_add_rel, type_add_stk, type_ab_abs, type_ab_rel, type_ab_stk }; // for every param that gets modulated by the cv matrix, -// we output the final value per block as parameter modulation indicator -struct mod_indicator_usage +// we output the final value per block as parameter modulation output +struct mod_output_usage { bool in_use; int param_global; @@ -342,8 +342,8 @@ scale_to_longest_mod_source( } // mapping to longest mod source, prefer envelope -static mod_indicator_source -select_mod_indicator_source( +static modulation_output_source +select_modulation_output_source( plugin_state const& state, param_topo_mapping const& mapping, std::vector const& sources) { @@ -449,6 +449,7 @@ cv_matrix_topo( make_module_dsp(stage, module_output::cv, scratch_count, { make_module_dsp_output(false, make_topo_info_basic("{3AEE42C9-691E-484F-B913-55EB05CFBB02}", "Output", 0, route_count)) }), make_module_gui(section, pos, { 1, 1 }))); + result.gui.render_automation_state = true; // don't want the graph source to be modulating the graph result.graph_engine_factory = make_graph_engine; if(!cv && !is_fx) result.default_initializer = global ? init_audio_global_default : init_audio_voice_default; @@ -456,9 +457,9 @@ cv_matrix_topo( auto const& state, auto* engine, int param, auto const& mapping) { return render_graph(state, engine, param, mapping, sm, tm); }; - result.mod_indicator_source_selector = [sm = source_matrix.mappings]( + result.mod_output_source_selector = [sm = source_matrix.mappings]( auto const& state, auto const& mapping) { - return select_mod_indicator_source(state, mapping, sm); + return select_modulation_output_source(state, mapping, sm); }; result.gui.menu_handler_factory = [](plugin_state* state) { return std::make_unique( @@ -662,8 +663,8 @@ cv_matrix_engine_base::perform_mixdown(plugin_block& block, int module, int slot int modulation_index = 0; int route_count = route_count_from_matrix_type(_global, _cv); jarray* modulated_curve_ptrs[max_any_route_count] = { nullptr }; - mod_indicator_usage mod_indicator_usages[max_any_route_count]; - std::memset(&mod_indicator_usages, 0, sizeof(mod_indicator_usages)); + mod_output_usage mod_output_usages[max_any_route_count]; + std::memset(&mod_output_usages, 0, sizeof(mod_output_usages)); for (int r = 0; r < route_count; r++) { @@ -696,10 +697,10 @@ cv_matrix_engine_base::perform_mixdown(plugin_block& block, int module, int slot _modulation_indices[tm][tmi][tp][tpi] = modulation_index; // set up for the visuals - mod_indicator_usages[modulation_index].in_use = true; - mod_indicator_usages[modulation_index].modulated_curve_ptr = modulated_curve_ptr; - mod_indicator_usages[modulation_index].module_global = block.plugin_desc_.module_topo_to_index.at(tm) + tmi; - mod_indicator_usages[modulation_index].param_global = block.plugin_desc_.param_mappings.topo_to_index[tm][tmi][tp][tpi]; + mod_output_usages[modulation_index].in_use = true; + mod_output_usages[modulation_index].modulated_curve_ptr = modulated_curve_ptr; + mod_output_usages[modulation_index].module_global = block.plugin_desc_.module_topo_to_index.at(tm) + tmi; + mod_output_usages[modulation_index].param_global = block.plugin_desc_.param_mappings.topo_to_index[tm][tmi][tp][tpi]; modulation_index++; } @@ -806,18 +807,21 @@ cv_matrix_engine_base::perform_mixdown(plugin_block& block, int module, int slot if (modulated_curve_ptrs[r] != nullptr) modulated_curve_ptrs[r]->transform(block.start_frame, block.end_frame, [](float v) { return std::clamp(v, 0.0f, 1.0f); }); - // push param mod indicators - if(!block.graph) - for(int r = 0; r < route_count; r++) - if (mod_indicator_usages[r].in_use) + // push param modulation outputs + if (!block.graph) + for (int r = 0; r < route_count; r++) + if (mod_output_usages[r].in_use) { - mod_indicator_state indicator_state; - indicator_state.data.voice_index = _global ? 0 : block.voice->state.slot; - indicator_state.data.param_global = mod_indicator_usages[r].param_global; - indicator_state.data.module_global = mod_indicator_usages[r].module_global; - indicator_state.data.value = (*mod_indicator_usages[r].modulated_curve_ptr)[block.end_frame - 1]; - block.push_mod_indicator_state(indicator_state); - } + // debugging + auto const& param_desc = block.plugin_desc_.params[mod_output_usages[r].param_global]; + (void)param_desc; + + block.push_modulation_output(modulation_output::make_mod_output_param_state( + _global ? -1 : block.voice->state.slot, + mod_output_usages[r].module_global, + mod_output_usages[r].param_global, + (*mod_output_usages[r].modulated_curve_ptr)[block.end_frame - 1])); + } } } diff --git a/src/firefly_synth/firefly_synth/oscillator.cpp b/src/firefly_synth/firefly_synth/oscillator.cpp index aabf41d1c..5887db7c3 100644 --- a/src/firefly_synth/firefly_synth/oscillator.cpp +++ b/src/firefly_synth/firefly_synth/oscillator.cpp @@ -214,6 +214,8 @@ render_osc_graphs(plugin_state const& state, graph_engine* engine, int slot, boo int type = state.get_plain_at(module_osc, slot, param_type, 0).step(); float cent = state.get_plain_at(module_osc, slot, param_cent, 0).real(); float gain = state.get_plain_at(module_osc, slot, param_gain, 0).real(); + + // NOTE we do not take pitch modulators into account float freq = pitch_to_freq_no_tuning(note + cent); plugin_block const* block = nullptr; @@ -290,6 +292,7 @@ osc_topo(int section, gui_position const& pos) result.graph_engine_factory = make_osc_graph_engine; result.gui.menu_handler_factory = make_osc_routing_menu_handler; result.engine_factory = [](auto const&, int sr, int max_frame_count) { return std::make_unique(max_frame_count, sr); }; + result.mod_output_source_selector = [](auto const& state, auto const& mapping) { return modulation_output_source { module_osc_osc_matrix, 0 }; }; result.sections.emplace_back(make_param_section(section_type, make_topo_tag_basic("{A64046EE-82EB-4C02-8387-4B9EFF69E06A}", "Type"), diff --git a/src/firefly_synth/firefly_synth/plugin.hpp b/src/firefly_synth/firefly_synth/plugin.hpp index 668da9638..1270c6f83 100644 --- a/src/firefly_synth/firefly_synth/plugin.hpp +++ b/src/firefly_synth/firefly_synth/plugin.hpp @@ -13,7 +13,7 @@ #define FF_SYNTH_VERSION_MAJOR 1 #define FF_SYNTH_VERSION_MINOR 9 -#define FF_SYNTH_VERSION_PATCH 0 +#define FF_SYNTH_VERSION_PATCH 1 #define FF_SYNTH_REVERSE_URI "io.github.sjoerdvankreel.firefly_synth" #define FF_SYNTH_VERSION_TEXT PB_VERSION_TEXT(FF_SYNTH_VERSION_MAJOR, FF_SYNTH_VERSION_MINOR, FF_SYNTH_VERSION_PATCH) diff --git a/src/firefly_synth/firefly_synth/synth.cpp b/src/firefly_synth/firefly_synth/synth.cpp index 73e4eed6c..007eb8fb1 100644 --- a/src/firefly_synth/firefly_synth/synth.cpp +++ b/src/firefly_synth/firefly_synth/synth.cpp @@ -75,9 +75,9 @@ make_plugin_dimension(bool is_fx, plugin_topo_gui_theme_settings const& settings } static module_graph_params -make_module_graph_params(int module, bool render_on_module_mouse_enter, - bool render_on_param_mouse_enter, bool render_on_mod_indicator_change, - std::vector const& dependent_module_indices) +make_module_graph_params(int module, + bool render_on_module_mouse_enter, bool render_on_param_mouse_enter, + bool hover_selects_different_graph, std::vector const& dependent_module_indices) { module_graph_params result; result.fps = 10; @@ -86,7 +86,7 @@ make_module_graph_params(int module, bool render_on_module_mouse_enter, result.render_on_tab_change = true; result.dependent_module_indices = dependent_module_indices; result.render_on_module_mouse_enter = render_on_module_mouse_enter; - result.render_on_mod_indicator_change = render_on_mod_indicator_change; + result.hover_selects_different_graph = hover_selects_different_graph; if(render_on_param_mouse_enter) result.render_on_param_mouse_enter_modules = { -1 }; return result; @@ -96,7 +96,7 @@ static Component& make_module_graph_section( plugin_gui* gui, lnf* lnf, component_store store, int module, std::string const& name_in_theme, - bool render_on_module_mouse_enter, bool render_on_param_mouse_enter, bool render_on_mod_indicator_change, + bool render_on_module_mouse_enter, bool render_on_param_mouse_enter, bool hover_selects_different_graph, std::vector const& dependent_module_indices, float partition_scale = 0.15f) { graph_params params; @@ -104,7 +104,7 @@ make_module_graph_section( params.partition_scale = partition_scale; params.scale_type = graph_params::scale_w; module_graph_params module_params = make_module_graph_params(module, - render_on_module_mouse_enter, render_on_param_mouse_enter, render_on_mod_indicator_change, dependent_module_indices); + render_on_module_mouse_enter, render_on_param_mouse_enter, hover_selects_different_graph, dependent_module_indices); return store_component(store, gui, lnf, params, module_params); } @@ -121,7 +121,6 @@ make_main_graph_section(plugin_gui* gui, lnf* lnf, component_store store) module_params.render_on_tweak = true; module_params.render_on_tab_change = false; module_params.render_on_module_mouse_enter = true; - module_params.render_on_mod_indicator_change = true; module_params.render_on_param_mouse_enter_modules = { module_gcv_cv_matrix, module_global_in, module_master_settings, module_vcv_cv_matrix, module_voice_in, module_osc_osc_matrix, module_vaudio_audio_matrix, module_gaudio_audio_matrix, module_vcv_audio_matrix, module_gcv_audio_matrix }; @@ -139,19 +138,11 @@ make_matrix_graphs_section( params.name_in_theme = name_in_theme; params.scale_type = graph_params::scale_h; params.partition_scale = 0.33f; - - if (is_fx) - { - assert(module_section == module_section_global_matrices); - return std::make_unique(gui, lnf, params, make_module_graph_params(module_index, false, true, true, - { module_global_in, module_glfo, module_gfx, module_gaudio_audio_matrix, module_global_out, module_gcv_audio_matrix })); - } - switch (module_index) { - case module_vaudio_audio_matrix: return std::make_unique(gui, lnf, params, make_module_graph_params(module_index, false, true, false, { })); - case module_gaudio_audio_matrix: return std::make_unique(gui, lnf, params, make_module_graph_params(module_index, false, true, false, { })); - case module_osc_osc_matrix: return std::make_unique(gui, lnf, params, make_module_graph_params(module_index, true, false, false, + case module_vaudio_audio_matrix: return std::make_unique(gui, lnf, params, make_module_graph_params(module_index, false, true, true, { })); + case module_gaudio_audio_matrix: return std::make_unique(gui, lnf, params, make_module_graph_params(module_index, false, true, true, { })); + case module_osc_osc_matrix: return std::make_unique(gui, lnf, params, make_module_graph_params(module_index, true, false, true, { module_osc, module_voice_in })); case module_vcv_audio_matrix: return std::make_unique(gui, lnf, params, make_module_graph_params(module_index, false, true, true, { module_global_in, module_glfo, module_vlfo, module_env, module_voice_in, module_voice_out, @@ -215,7 +206,7 @@ make_edit_controls_section(plugin_gui* gui, lnf* lnf, component_store store) result.add(tweak_value_label, { 1, 0 }); auto& tweak = store_component(store, gui, lnf); result.add(tweak, { 0, 1, 1, 3 }); - result.add(store_component(store, gui->gui_state(), lnf), { 1, 1, 1, 3 }); + result.add(store_component(store, gui->automation_state(), lnf), { 1, 1, 1, 3 }); return result; } @@ -230,7 +221,7 @@ make_title_section(plugin_gui* gui, lnf* lnf, component_store store, bool is_fx) title_label.setColour(Label::ColourIds::textColourId, colors.control_text); title_label.setJustificationType(Justification::left); grid.add(title_label, { 0, 0, 1, 1 }); - std::string version_text = std::string(FF_SYNTH_VERSION_TEXT) + " " + gui->gui_state()->desc().plugin->config->format_name() + " "; + std::string version_text = std::string(FF_SYNTH_VERSION_TEXT) + " " + gui->automation_state()->desc().plugin->config->format_name() + " "; #ifdef __aarch64__ version_text += "ARM"; #else @@ -241,7 +232,7 @@ make_title_section(plugin_gui* gui, lnf* lnf, component_store store, bool is_fx) version_label.setColour(Label::ColourIds::textColourId, colors.control_text); grid.add(version_label, { 1, 0, 1, 1 }); grid.add(store_component( - store, gui->gui_state()->desc().plugin->config, + store, gui->automation_state()->desc().plugin->config, lnf->theme(), "header.png", RectanglePlacement::xRight), { 0, 1, 2, 1 }); return grid; } @@ -360,6 +351,8 @@ synth_topo(format_basic_config const* config, bool is_fx, std::string const& ful result->version.patch = FF_SYNTH_VERSION_PATCH; result->engine.voice_mode.module_index = module_voice_in; result->engine.voice_mode.param_index = voice_in_param_mode; + result->engine.visuals.module_index = module_master_settings; + result->engine.visuals.param_index = master_settings_param_visuals; result->engine.bpm_smoothing.module_index = module_master_settings; result->engine.bpm_smoothing.param_index = master_settings_param_tempo_smooth; result->engine.midi_smoothing.module_index = module_master_settings; @@ -416,7 +409,7 @@ synth_topo(format_basic_config const* config, bool is_fx, std::string const& ful -> Component& { return make_module_graph_section(gui, lnf, store, module_gfx, gfx_graph_name, false, false, false, {}); }); result->gui.custom_sections[custom_section_glfo_graph] = make_custom_section_gui( custom_section_glfo_graph, glfo_graph_name, { 4, 3, 1, 1 }, [](auto* gui, auto* lnf, auto store) - -> Component& { return make_module_graph_section(gui, lnf, store, module_glfo, glfo_graph_name, false, false, true, {}); }); + -> Component& { return make_module_graph_section(gui, lnf, store, module_glfo, glfo_graph_name, false, false, false, {}); }); if (is_fx) { result->gui.custom_sections[custom_section_global_matrix_graphs] = make_custom_section_gui( @@ -433,10 +426,10 @@ synth_topo(format_basic_config const* config, bool is_fx, std::string const& ful -> Component& { return make_module_graph_section(gui, lnf, store, module_vfx, vfx_graph_name, false, false, false, {}); }); result->gui.custom_sections[custom_section_vlfo_graph] = make_custom_section_gui( custom_section_vlfo_graph, vlfo_graph_name, { 8, 3, 1, 1 }, [](auto* gui, auto* lnf, auto store) - -> Component& { return make_module_graph_section(gui, lnf, store, module_vlfo, vlfo_graph_name, false, false, true, {}); }); + -> Component& { return make_module_graph_section(gui, lnf, store, module_vlfo, vlfo_graph_name, false, false, false, {}); }); result->gui.custom_sections[custom_section_env_graph] = make_custom_section_gui( custom_section_env_graph, env_graph_name, { 9, 3, 1, 1 }, [](auto* gui, auto* lnf, auto store) - -> Component& { return make_module_graph_section(gui, lnf, store, module_env, env_graph_name, false, false, true, {}); }); + -> Component& { return make_module_graph_section(gui, lnf, store, module_env, env_graph_name, false, false, false, {}); }); result->gui.custom_sections[custom_section_global_matrix_graphs] = make_custom_section_gui( custom_section_global_matrix_graphs, global_matrix_graphs_name, { 4, 4, 1, 2 }, [](auto* gui, auto* lnf, auto store) -> Component& { return make_matrix_graphs_section(gui, lnf, store, false, module_section_global_matrices, global_matrix_graphs_name); }); diff --git a/src/firefly_synth/firefly_synth/synth.hpp b/src/firefly_synth/firefly_synth/synth.hpp index d5b9d9b5c..c5e751e47 100644 --- a/src/firefly_synth/firefly_synth/synth.hpp +++ b/src/firefly_synth/firefly_synth/synth.hpp @@ -40,6 +40,7 @@ extern int const voice_in_param_uni_env_dtn; extern int const voice_in_param_uni_lfo_phase; extern int const voice_in_param_uni_lfo_dtn; +extern int const master_settings_param_visuals; extern int const master_settings_param_tuning_mode; extern int const master_settings_param_auto_smooth; extern int const master_settings_param_midi_smooth; diff --git a/static/screenshot_fx_cold.png b/static/screenshot_fx_cold.png index ae16b97f3..eb835ce86 100644 Binary files a/static/screenshot_fx_cold.png and b/static/screenshot_fx_cold.png differ diff --git a/static/screenshot_fx_hot.png b/static/screenshot_fx_hot.png index ef747e748..793dd7d39 100644 Binary files a/static/screenshot_fx_hot.png and b/static/screenshot_fx_hot.png differ diff --git a/static/screenshot_synth_cold.png b/static/screenshot_synth_cold.png index 164cd8326..37c1b958c 100644 Binary files a/static/screenshot_synth_cold.png and b/static/screenshot_synth_cold.png differ diff --git a/static/screenshot_synth_hot.png b/static/screenshot_synth_hot.png index 597f0dfc1..527f6c25e 100644 Binary files a/static/screenshot_synth_hot.png and b/static/screenshot_synth_hot.png differ diff --git a/studiorack/plugins.json b/studiorack/plugins.json index 666c2da34..617ffec26 100644 --- a/studiorack/plugins.json +++ b/studiorack/plugins.json @@ -6,7 +6,7 @@ "name": "Firefly Synth", "description": "Firefly Synth", "tags": [ "Instrument", "Synth" ], - "version": "1.9.0", + "version": "1.9.1", "id": "io.github.sjoerdvankreel.firefly_synth", "date": "2024-06-03T17:25:12.081Z", "files": { @@ -17,13 +17,13 @@ "name": "studiorack.jpg" }, "linux": { - "name": "firefly_synth_1.9.0_linux_vst3_instrument.zip" + "name": "firefly_synth_1.9.1_linux_vst3_instrument.zip" }, "mac": { - "name": "firefly_synth_1.9.0_mac_vst3_instrument.zip" + "name": "firefly_synth_1.9.1_mac_vst3_instrument.zip" }, "win": { - "name": "firefly_synth_1.9.0_windows_vst3_instrument.zip" + "name": "firefly_synth_1.9.1_windows_vst3_instrument.zip" } } } diff --git a/studiorack/studiorack.jpg b/studiorack/studiorack.jpg index 593881361..1005ba9c8 100644 Binary files a/studiorack/studiorack.jpg and b/studiorack/studiorack.jpg differ diff --git a/themes/Firefly Cold/theme.json b/themes/Firefly Cold/theme.json index 9b97f296a..0ba254080 100644 --- a/themes/Firefly Cold/theme.json +++ b/themes/Firefly Cold/theme.json @@ -33,7 +33,7 @@ "graph_grid": "40FFFFFF", "graph_text": "C0FFFFFF", "graph_background": "FF000000", - "graph_mod_indicator": "C0FFFFFF", + "graph_modulation_bubble": "C0FFFFFF", "bubble_outline": "FF999999", "slider_background": "FF333333", "slider_highlight": "FFFFFFFF", diff --git a/themes/Firefly Hot/theme.json b/themes/Firefly Hot/theme.json index 02d8e7771..57ee41b70 100644 --- a/themes/Firefly Hot/theme.json +++ b/themes/Firefly Hot/theme.json @@ -33,7 +33,7 @@ "graph_grid": "40FFFFFF", "graph_text": "C0FFFFFF", "graph_background": "FF000000", - "graph_mod_indicator": "C0FFFFFF", + "graph_modulation_bubble": "C0FFFFFF", "bubble_outline": "FF999999", "slider_background": "FF333333", "slider_highlight": "FFFFFFFF", diff --git a/todo.txt b/todo.txt index 1c4e55117..92c4ca654 100644 --- a/todo.txt +++ b/todo.txt @@ -24,15 +24,8 @@ autobuilds: * ask paul * integrate https://github.com/surge-synthesizer/monique-monosynth/blob/main/.github/workflows/build-pr.yml -ui-vnext: -* make the cv graphs + bolletjes work in case of automation -* global unit lfo phase offset not plot good -* less ugly audio graphs -* show clap mod indicator -* repaint on waveform change (free running or random-per-voice) -* show active modulation for the other graphs -> better to have a separate state copy including modulation -* bugfix: rerender graph on master in param hover? -* show effective modulation in the ui for clap param mod +* make a youtube video +* update the mod demo video wishlist: filter microtuning @@ -40,9 +33,13 @@ DSF falloff to the left clap polyphonic modulation full-blown renoise support fix global unison for mono mode +ditch after-mod, claim micro support ? +show effective modulation in the ui for clap param mod Feedback FM - but needs per-sample processing, not block turn on stuff when dragging (e.g. basic sin, dist skew etc) arpeggiator - hard - but easy -> play multiple octaves on-note. or maybe allow 3/s, 5/s, whatever selection +bugfix: rerender graph on master in param hover? +repaint on waveform change (free running or random-per-voice) wishlist sometime: mseg @@ -52,7 +49,9 @@ cheby shaper phaser/flanger/chorus vst3 note expressions flstudio clap support +show clap mod indicator visual routing indicators automated regression tests better studiorack integration -do async clear delay lines etc on patch select \ No newline at end of file +do async clear delay lines etc on patch select +global unit lfo phase offset not plot good \ No newline at end of file