gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit 58936f7b381cabfc7be1fa1152126e50616904d5
parent 7999239ca0e335d97fee109d823ad003311cf772
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Tue, 28 Dec 2021 15:55:37 +0100

merge TALs UI branch

Diffstat:
Msource/jucePlugin/CMakeLists.txt | 48+++++++++++++++++++++++++++++++++++++++++++-----
Msource/jucePlugin/PluginEditor.cpp | 290++++++++++---------------------------------------------------------------------
Msource/jucePlugin/PluginEditor.h | 34+++++++++++-----------------------
Msource/jucePlugin/PluginProcessor.cpp | 7++++---
Asource/jucePlugin/assets/bg_1377x800.png | 0
Asource/jucePlugin/assets/buttons/GLOBAL_btn_arp_settings_141x26.png | 0
Asource/jucePlugin/assets/buttons/GLOBAL_btn_effects_141x26.png | 0
Asource/jucePlugin/assets/buttons/GLOBAL_btn_lfo_matrix_141x26.png | 0
Asource/jucePlugin/assets/buttons/GLOBAL_btn_osc_filter_141x26.png | 0
Asource/jucePlugin/assets/buttons/Handle_18x47.png | 0
Asource/jucePlugin/assets/buttons/env_pol_50x34.png | 0
Asource/jucePlugin/assets/buttons/lfo_btn_23_19.png | 0
Asource/jucePlugin/assets/buttons/link_horizon_36x12.png | 0
Asource/jucePlugin/assets/buttons/link_vert_12x36.png | 0
Asource/jucePlugin/assets/buttons/presets_btn_43_15.png | 0
Asource/jucePlugin/assets/buttons/sync2_54x25.png | 0
Asource/jucePlugin/assets/knobs/GenBlue_70x70_100.png | 0
Asource/jucePlugin/assets/knobs/GenRed_70x70_100.png | 0
Asource/jucePlugin/assets/knobs/Gen_70x70_100.png | 0
Asource/jucePlugin/assets/knobs/Gen_pol_70x70_100.png | 0
Asource/jucePlugin/assets/knobs/multi_18x18_100.png | 0
Asource/jucePlugin/assets/panels/bg_arp_1018x620.png | 0
Asource/jucePlugin/assets/panels/bg_fx_1018x620.png | 0
Asource/jucePlugin/assets/panels/bg_lfo_1018x620.png | 0
Asource/jucePlugin/assets/panels/bg_osc_1018x620.png | 0
Asource/jucePlugin/assets/panels/bg_presets_1018x620.png | 0
Asource/jucePlugin/ui/Ui_Utils.h | 19+++++++++++++++++++
Asource/jucePlugin/ui/VirusEditor.cpp | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui/VirusEditor.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui/Virus_ArpEditor.cpp | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui/Virus_ArpEditor.h | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui/Virus_Buttons.cpp | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui/Virus_Buttons.h | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui/Virus_FxEditor.cpp | 185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui/Virus_FxEditor.h | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui/Virus_LfoEditor.cpp | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui/Virus_LfoEditor.h | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui/Virus_LookAndFeel.cpp | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui/Virus_LookAndFeel.h | 34++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui/Virus_OscEditor.cpp | 179+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui/Virus_OscEditor.h | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
41 files changed, 1511 insertions(+), 285 deletions(-)

diff --git a/source/jucePlugin/CMakeLists.txt b/source/jucePlugin/CMakeLists.txt @@ -31,15 +31,53 @@ target_sources(jucePlugin PRIVATE PluginEditor.cpp PluginProcessor.cpp - VirusController.cpp - VirusParameter.cpp + ui/Virus_Buttons.cpp + ui/Virus_LookAndFeel.cpp + ui/VirusEditor.cpp + ui/Virus_ArpEditor.cpp + ui/Virus_FxEditor.cpp + ui/Virus_LfoEditor.cpp + ui/Virus_OscEditor.cpp PluginEditor.h PluginProcessor.h - VirusController.h - VirusParameter.h + ui/Virus_Buttons.h + ui/Virus_LookAndFeel.h + ui/VirusEditor.h + ui/Virus_ArpEditor.h + ui/Virus_FxEditor.h + ui/Virus_LfoEditor.h + ui/Virus_OscEditor.h + ui/Ui_Utils.h version.h ) +# https://forum.juce.com/t/help-needed-using-binarydata-with-cmake-juce-6/40486 +# "This might be because the BinaryData files are generated during the build, so the IDE may not be able to find them until the build has been run once (and even then, some IDEs might need a bit of a nudge to re-index the binary directory…)" +juce_add_binary_data(jucePlugin_BinaryData + SOURCES + "assets/bg_1377x800.png" + "assets/panels/bg_arp_1018x620.png" + "assets/panels/bg_fx_1018x620.png" + "assets/panels/bg_lfo_1018x620.png" + "assets/panels/bg_osc_1018x620.png" + "assets/buttons/GLOBAL_btn_arp_settings_141x26.png" + "assets/buttons/GLOBAL_btn_effects_141x26.png" + "assets/buttons/GLOBAL_btn_lfo_matrix_141x26.png" + "assets/buttons/GLOBAL_btn_osc_filter_141x26.png" + "assets/buttons/env_pol_50x34.png" + "assets/buttons/lfo_btn_23_19.png" + "assets/buttons/link_vert_12x36.png" + "assets/buttons/link_horizon_36x12.png" + "assets/buttons/presets_btn_43_15.png" + "assets/buttons/Handle_18x47.png" + "assets/buttons/sync2_54x25.png" + "assets/knobs/Gen_70x70_100.png" + "assets/knobs/Gen_pol_70x70_100.png" + "assets/knobs/GenBlue_70x70_100.png" + "assets/knobs/GenRed_70x70_100.png" + "assets/knobs/multi_18x18_100.png" +) + target_compile_definitions(jucePlugin PUBLIC # JUCE_WEB_BROWSER and JUCE_USE_CURL would be on by default, but you might not need them. @@ -50,7 +88,7 @@ PUBLIC target_link_libraries(jucePlugin PRIVATE - # AudioPluginData # If we'd created a binary data target, we'd link to it here + jucePlugin_BinaryData juce::juce_audio_utils PUBLIC virusLib diff --git a/source/jucePlugin/PluginEditor.cpp b/source/jucePlugin/PluginEditor.cpp @@ -1,189 +1,50 @@ #include "PluginProcessor.h" #include "PluginEditor.h" +#include "ui/VirusEditor.h" #include "version.h" //============================================================================== -AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor(AudioPluginAudioProcessor &p) : - AudioProcessorEditor(&p), processorRef(p), m_btSingleMode("Single Mode"), m_btMultiMode("Multi Mode"), - m_btLoadFile("Load bank"), m_cmbMidiInput("Midi Input"), m_cmbMidiOutput("Midi Output"), - deviceManager(), m_tempEditor(p) +AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor (AudioPluginAudioProcessor& p) + : AudioProcessorEditor (&p), processorRef (p) + , m_btSingleMode("Single Mode") + , m_btMultiMode("Multi Mode") { ignoreUnused (processorRef); - juce::PropertiesFile::Options opts; - opts.applicationName = "DSP56300 Emulator"; - opts.filenameSuffix = ".settings"; - opts.folderName = "DSP56300 Emulator"; - opts.osxLibrarySubFolder = "Application Support/DSP56300 Emulator"; - m_properties = new juce::PropertiesFile(opts); - // Make sure that before the constructor has finished, you've set the // editor's size to whatever you need it to be. - setSize(800, 800); - - // Resizable UI - setResizable(true, true); - setResizeLimits(800,400,800,1600); + setSize (400, 300); - m_btSingleMode.setRadioGroupId(0x3cf); - m_btMultiMode.setRadioGroupId(0x3cf); addAndMakeVisible(m_btSingleMode); addAndMakeVisible(m_btMultiMode); + m_btSingleMode.setTopLeftPosition(0,0); m_btSingleMode.setSize(120,30); - m_btMultiMode.getToggleStateValue().referTo(*processorRef.getController().getParam(0, 2, 0x7a)); - const auto isMulti = processorRef.getController().isMultiMode(); - m_btSingleMode.setToggleState(!isMulti, juce::dontSendNotification); - m_btMultiMode.setToggleState(isMulti, juce::dontSendNotification); - m_btSingleMode.setClickingTogglesState(true); - m_btMultiMode.setClickingTogglesState(true); - m_btMultiMode.setTopLeftPosition(m_btSingleMode.getPosition().x + m_btSingleMode.getWidth() + 10, m_btSingleMode.getY()); - m_btMultiMode.setSize(120,30); - - addAndMakeVisible(m_btLoadFile); - m_btLoadFile.setTopLeftPosition(m_btSingleMode.getPosition().x + m_btSingleMode.getWidth() + m_btMultiMode.getWidth() + 10, m_btSingleMode.getY()); - m_btLoadFile.setSize(120, 30); - m_btLoadFile.onClick = [this]() { - loadFile(); - }; - - for (auto pt = 0; pt < 16; pt++) - { - m_partSelectors[pt].onClick = [this, pt]() { - juce::PopupMenu selector; - - for(auto b=0; b<processorRef.getController().getBankCount(); ++b) - { - auto bank = processorRef.getController().getSinglePresetNames(b); - juce::PopupMenu p; - for (auto i = 0; i < 128; i++) - { - p.addItem(bank[i], [this, b, i, pt] { processorRef.getController().setCurrentPartPreset(pt, b, i); }); - } - std::stringstream bankName; - bankName << "Bank " << static_cast<char>('A' + b); - selector.addSubMenu(std::string(bankName.str()), p); - } - selector.showMenu(juce::PopupMenu::Options()); - }; - addAndMakeVisible(m_partSelectors[pt]); - } - - auto midiIn = m_properties->getValue("midi_input", ""); - auto midiOut = m_properties->getValue("midi_output", ""); - if (midiIn != "") - { - processorRef.setMidiInput(midiIn); - } - if (midiOut != "") - { - processorRef.setMidiOutput(midiOut); - } - - m_cmbMidiInput.setSize(160, 30); - m_cmbMidiInput.setTopLeftPosition(0, 400); - m_cmbMidiOutput.setSize(160, 30); - m_cmbMidiOutput.setTopLeftPosition(164, 400); - addAndMakeVisible(m_cmbMidiInput); - addAndMakeVisible(m_cmbMidiOutput); - m_cmbMidiInput.setTextWhenNoChoicesAvailable("No MIDI Inputs Enabled"); - auto midiInputs = juce::MidiInput::getAvailableDevices(); - juce::StringArray midiInputNames; - midiInputNames.add(" - Midi In - "); - auto inIndex = 0; - for (int i = 0; i < midiInputs.size(); i++) - { - const auto input = midiInputs[i]; - if (processorRef.getMidiInput() != nullptr && input.identifier == processorRef.getMidiInput()->getIdentifier()) - { - inIndex = i + 1; - } - midiInputNames.add(input.name); - } - m_cmbMidiInput.addItemList(midiInputNames, 1); - m_cmbMidiInput.setSelectedItemIndex(inIndex, juce::dontSendNotification); - m_cmbMidiOutput.setTextWhenNoChoicesAvailable("No MIDI Outputs Enabled"); - auto midiOutputs = juce::MidiOutput::getAvailableDevices(); - juce::StringArray midiOutputNames; - midiOutputNames.add(" - Midi Out - "); - auto outIndex = 0; - for (int i = 0; i < midiOutputs.size(); i++) - { - const auto output = midiOutputs[i]; - if (processorRef.getMidiOutput() != nullptr && output.identifier == processorRef.getMidiOutput()->getIdentifier()) - { - outIndex = i+1; - } - midiOutputNames.add(output.name); - } - m_cmbMidiOutput.addItemList(midiOutputNames, 1); - m_cmbMidiOutput.setSelectedItemIndex(outIndex, juce::dontSendNotification); - m_cmbMidiInput.onChange = [this]() { updateMidiInput(m_cmbMidiInput.getSelectedItemIndex()); }; - m_cmbMidiOutput.onChange = [this]() { updateMidiOutput(m_cmbMidiOutput.getSelectedItemIndex()); }; - - addAndMakeVisible(m_tempEditor); - - startTimerHz(5); -} -void AudioPluginAudioProcessorEditor::updateMidiInput(int index) -{ - auto list = juce::MidiInput::getAvailableDevices(); - - if (index == 0) - { - m_properties->setValue("midi_input", ""); - m_properties->save(); - m_lastInputIndex = index; - m_cmbMidiInput.setSelectedItemIndex(index, juce::dontSendNotification); - return; - } - index--; - auto newInput = list[index]; - - if (!deviceManager.isMidiInputDeviceEnabled(newInput.identifier)) - deviceManager.setMidiInputDeviceEnabled(newInput.identifier, true); + m_btMultiMode.setTopLeftPosition(m_btSingleMode.getPosition().x + m_btSingleMode.getWidth() + 10, 0); + m_btMultiMode.setSize(120,30); - if (!processorRef.setMidiInput(newInput.identifier)) + m_btSingleMode.onClick = [this]() { - m_cmbMidiInput.setSelectedItemIndex(0, juce::dontSendNotification); - m_lastInputIndex = 0; - return; - } - - m_properties->setValue("midi_input", newInput.identifier); - m_properties->save(); - - m_cmbMidiInput.setSelectedItemIndex(index+1, juce::dontSendNotification); - m_lastInputIndex = index; -} -void AudioPluginAudioProcessorEditor::updateMidiOutput(int index) -{ - auto list = juce::MidiOutput::getAvailableDevices(); + switchPlayMode(0); + }; - if (index == 0) + m_btMultiMode.onClick = [this]() { - m_properties->setValue("midi_output", ""); - m_properties->save(); - m_cmbMidiOutput.setSelectedItemIndex(index, juce::dontSendNotification); - m_lastOutputIndex = index; - processorRef.setMidiOutput(""); - return; - } - index--; - auto newOutput = list[index]; - if(!processorRef.setMidiOutput(newOutput.identifier)) - { - m_cmbMidiOutput.setSelectedItemIndex(0, juce::dontSendNotification); - m_lastOutputIndex = 0; - return; - } - m_properties->setValue("midi_output", newOutput.identifier); - m_properties->save(); + switchPlayMode(2); + }; - m_cmbMidiOutput.setSelectedItemIndex(index+1, juce::dontSendNotification); - m_lastOutputIndex = index; + m_openEditor.setButtonText ("Show Editor"); + m_openEditor.onClick = [this]() { + m_virusEditor.reset (new juce::ResizableWindow ("VirusEditor", true)); + m_virusEditor->setTopLeftPosition (0, 0); + m_virusEditor->setUsingNativeTitleBar (true); + m_virusEditor->setVisible (true); + m_virusEditor->setResizable(true, false); + m_virusEditor->setContentOwned (new VirusEditor(), true); + }; + addAndMakeVisible (m_openEditor); } AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor() @@ -204,100 +65,22 @@ void AudioPluginAudioProcessorEditor::paint (juce::Graphics& g) if(!processorRef.isPluginValid()) message += "\n\nNo ROM, no sound!\nCopy ROM next to plugin, must end with .bin"; - g.drawFittedText(message, getLocalBounds().removeFromLeft(400).removeFromBottom(45), juce::Justification::centred, - 2); - g.drawFittedText("To donate: paypal.me/dsp56300", getLocalBounds().removeFromRight(400).removeFromTop(35), - juce::Justification::centred, 2); -} - -void AudioPluginAudioProcessorEditor::timerCallback() -{ - // ugly (polling!) way for refreshing presets names as this is temporary ui - const auto multiMode = processorRef.getController().isMultiMode(); - for (auto pt = 0; pt < 16; pt++) - { - bool singlePartOrInMulti = pt == 0 || multiMode; - m_partSelectors[pt].setVisible(singlePartOrInMulti); - if (singlePartOrInMulti) - m_partSelectors[pt].setButtonText(processorRef.getController().getCurrentPartPresetName(pt)); - } + g.drawFittedText(message, getLocalBounds(), juce::Justification::centred, 2); + g.drawFittedText("To donate: paypal.me/dsp56300", getLocalBounds(), juce::Justification::centredBottom, 2); } void AudioPluginAudioProcessorEditor::resized() { // This is generally where you'll want to lay out the positions of any // subcomponents in your editor.. - auto area = getLocalBounds(); - area.removeFromTop(35); - m_tempEditor.setBounds(area.removeFromRight(400)); - for (auto pt = 0; pt < 16; pt++) - { - m_partSelectors[pt].setBounds(area.removeFromTop(20)); - } + m_openEditor.setBounds (0,0, 80, 20); } -void AudioPluginAudioProcessorEditor::loadFile() { - juce::FileChooser chooser("Choose syx/midi banks to import", - m_previousPath.isEmpty() ? juce::File::getSpecialLocation(juce::File::currentApplicationFile).getParentDirectory() : m_previousPath, - "*.syx,*.mid,*.midi", - true); - const bool result = chooser.browseForFileToOpen(); - if (result) - { - const auto result = chooser.getResult(); - m_previousPath = result.getParentDirectory().getFullPathName(); - const auto ext = result.getFileExtension().toLowerCase(); - if (ext == ".syx") - { - juce::MemoryBlock data; - result.loadFileAsData(data); - for (auto it = data.begin(); it != data.end(); it += 267) - { - if ((it + 267) < data.end()) - { - processorRef.getController().parseMessage(Virus::SysEx(it, it + 267)); - } - } - m_btLoadFile.setButtonText("Loaded"); - } - else if (ext == ".mid" || ext == ".midi") - { - juce::MemoryBlock data; - if (!result.loadFileAsData(data)) - { - return; - } - const uint8_t *ptr = (uint8_t *)data.getData(); - const auto end = ptr + data.getSize(); - - for (auto it = ptr; it < end; it += 1) - { - if ((uint8_t)*it == (uint8_t)0xf0 && (it+267) < end) - { - if ((uint8_t) *(it + 1) == (uint8_t)0x00) - { - auto syx = Virus::SysEx(it, it + 267); - syx[7] = 0x01; // force to bank a - syx[266] = 0xf7; - processorRef.getController().parseMessage(syx); - - it += 266; - } - else // some midi files have two bytes after the 0xf0 - { - auto syx = Virus::SysEx(); - syx.push_back(0xf0); - for (auto i = it + 3; i < it + 3 + 266; i++) - { - syx.push_back((uint8_t)*i); - } - syx[7] = 0x01; // force to bank a - syx[266] = 0xf7; - processorRef.getController().parseMessage(syx); - it += 266; - } - } - } - } - } -} -\ No newline at end of file +void AudioPluginAudioProcessorEditor::switchPlayMode(uint8_t _playMode) const +{ + synthLib::SMidiEvent ev; + ev.sysex = { 0xf0, 0x00, 0x20, 0x33, 0x01, 0x00, 0x72, 0x0, 0x7a, _playMode, 0xf7}; + processorRef.addMidiEvent(ev); + // This is generally where you'll want to lay out the positions of any + // subcomponents in your editor.. +} diff --git a/source/jucePlugin/PluginEditor.h b/source/jucePlugin/PluginEditor.h @@ -1,42 +1,30 @@ #pragma once #include "PluginProcessor.h" -#include <juce_audio_devices/juce_audio_devices.h> + //============================================================================== -class AudioPluginAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::Timer +class AudioPluginAudioProcessorEditor : public juce::AudioProcessorEditor { public: explicit AudioPluginAudioProcessorEditor (AudioPluginAudioProcessor&); ~AudioPluginAudioProcessorEditor() override; //============================================================================== - //void handleIncomingMidiMessage(juce::MidiInput *source, const juce::MidiMessage &message) override; void paint (juce::Graphics&) override; void resized() override; private: - void updateMidiInput(int index); - void updateMidiOutput(int index); - void timerCallback() override; - void loadFile(); - - // This reference is provided as a quick way for your editor to - // access the processor object that created it. - AudioPluginAudioProcessor& processorRef; - - juce::GenericAudioProcessorEditor m_tempEditor; - juce::TextButton m_partSelectors[16]; + void switchPlayMode(uint8_t _playMode) const; + + // This reference is provided as a quick way for your editor to + // access the processor object that created it. + AudioPluginAudioProcessor& processorRef; juce::TextButton m_btSingleMode; juce::TextButton m_btMultiMode; - juce::TextButton m_btLoadFile; - juce::String m_previousPath; - juce::ComboBox m_cmbMidiInput; - juce::ComboBox m_cmbMidiOutput; - juce::AudioDeviceManager deviceManager; - juce::PropertiesFile *m_properties; - int m_lastInputIndex = 0; - int m_lastOutputIndex = 0; + + juce::TextButton m_openEditor; // temporary until integrated - will be rebased! + std::unique_ptr<juce::ResizableWindow> m_virusEditor; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginAudioProcessorEditor) - }; diff --git a/source/jucePlugin/PluginProcessor.cpp b/source/jucePlugin/PluginProcessor.cpp @@ -194,10 +194,11 @@ void AudioPluginAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, if(playHead) playHead->getCurrentPosition(pos); - m_plugin.process(inputs, outputs, buffer.getNumSamples(), static_cast<float>(pos.bpm), static_cast<float>(pos.ppqPosition), pos.isPlaying); + m_plugin.process(inputs, outputs, buffer.getNumSamples(), static_cast<float>(pos.bpm), + static_cast<float>(pos.ppqPosition), pos.isPlaying); - m_midiOut.clear(); - m_plugin.getMidiOut(m_midiOut); + m_midiOut.clear(); + m_plugin.getMidiOut(m_midiOut); if (!m_midiOut.empty()) { diff --git a/source/jucePlugin/assets/bg_1377x800.png b/source/jucePlugin/assets/bg_1377x800.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/GLOBAL_btn_arp_settings_141x26.png b/source/jucePlugin/assets/buttons/GLOBAL_btn_arp_settings_141x26.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/GLOBAL_btn_effects_141x26.png b/source/jucePlugin/assets/buttons/GLOBAL_btn_effects_141x26.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/GLOBAL_btn_lfo_matrix_141x26.png b/source/jucePlugin/assets/buttons/GLOBAL_btn_lfo_matrix_141x26.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/GLOBAL_btn_osc_filter_141x26.png b/source/jucePlugin/assets/buttons/GLOBAL_btn_osc_filter_141x26.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/Handle_18x47.png b/source/jucePlugin/assets/buttons/Handle_18x47.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/env_pol_50x34.png b/source/jucePlugin/assets/buttons/env_pol_50x34.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/lfo_btn_23_19.png b/source/jucePlugin/assets/buttons/lfo_btn_23_19.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/link_horizon_36x12.png b/source/jucePlugin/assets/buttons/link_horizon_36x12.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/link_vert_12x36.png b/source/jucePlugin/assets/buttons/link_vert_12x36.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/presets_btn_43_15.png b/source/jucePlugin/assets/buttons/presets_btn_43_15.png Binary files differ. diff --git a/source/jucePlugin/assets/buttons/sync2_54x25.png b/source/jucePlugin/assets/buttons/sync2_54x25.png Binary files differ. diff --git a/source/jucePlugin/assets/knobs/GenBlue_70x70_100.png b/source/jucePlugin/assets/knobs/GenBlue_70x70_100.png Binary files differ. diff --git a/source/jucePlugin/assets/knobs/GenRed_70x70_100.png b/source/jucePlugin/assets/knobs/GenRed_70x70_100.png Binary files differ. diff --git a/source/jucePlugin/assets/knobs/Gen_70x70_100.png b/source/jucePlugin/assets/knobs/Gen_70x70_100.png Binary files differ. diff --git a/source/jucePlugin/assets/knobs/Gen_pol_70x70_100.png b/source/jucePlugin/assets/knobs/Gen_pol_70x70_100.png Binary files differ. diff --git a/source/jucePlugin/assets/knobs/multi_18x18_100.png b/source/jucePlugin/assets/knobs/multi_18x18_100.png Binary files differ. diff --git a/source/jucePlugin/assets/panels/bg_arp_1018x620.png b/source/jucePlugin/assets/panels/bg_arp_1018x620.png Binary files differ. diff --git a/source/jucePlugin/assets/panels/bg_fx_1018x620.png b/source/jucePlugin/assets/panels/bg_fx_1018x620.png Binary files differ. diff --git a/source/jucePlugin/assets/panels/bg_lfo_1018x620.png b/source/jucePlugin/assets/panels/bg_lfo_1018x620.png Binary files differ. diff --git a/source/jucePlugin/assets/panels/bg_osc_1018x620.png b/source/jucePlugin/assets/panels/bg_osc_1018x620.png Binary files differ. diff --git a/source/jucePlugin/assets/panels/bg_presets_1018x620.png b/source/jucePlugin/assets/panels/bg_presets_1018x620.png Binary files differ. diff --git a/source/jucePlugin/ui/Ui_Utils.h b/source/jucePlugin/ui/Ui_Utils.h @@ -0,0 +1,19 @@ +#include "Virus_LookAndFeel.h" + +constexpr auto knobSize = Virus::LookAndFeel::kKnobSize; +constexpr auto comboBoxHeight = 17; + +static void setupBackground(juce::Component &parent, std::unique_ptr<juce::Drawable> &bg, const void *data, + const size_t numBytes) +{ + bg = juce::Drawable::createFromImageData(data, numBytes); + parent.addAndMakeVisible(bg.get()); + bg->setBounds(bg->getDrawableBounds().toNearestIntEdges()); +} + +static void setupRotary(juce::Component &parent, juce::Slider &slider) +{ + slider.setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag); + slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0); + parent.addAndMakeVisible(slider); +} diff --git a/source/jucePlugin/ui/VirusEditor.cpp b/source/jucePlugin/ui/VirusEditor.cpp @@ -0,0 +1,101 @@ +#include "VirusEditor.h" +#include "BinaryData.h" + +#include "Virus_ArpEditor.h" +#include "Virus_FxEditor.h" +#include "Virus_LfoEditor.h" +#include "Virus_OscEditor.h" + +using namespace juce; + +constexpr auto kPanelWidth = 1377; +constexpr auto kPanelHeight = 800; + +VirusEditor::VirusEditor() +{ + setLookAndFeel(&m_lookAndFeel); + + m_background = Drawable::createFromImageData (BinaryData::bg_1377x800_png, BinaryData::bg_1377x800_pngSize); + + m_background->setBufferedToImage (true); + addAndMakeVisible (*m_background); + addAndMakeVisible (m_mainButtons); + + m_arpEditor = std::make_unique<ArpEditor>(); + m_fxEditor = std::make_unique<FxEditor>(); + m_lfoEditor = std::make_unique<LfoEditor>(); + m_oscEditor = std::make_unique<OscEditor>(); + + applyToSections([this](Component *s) { addChildComponent(s); }); + + // show/hide section from buttons.. + m_mainButtons.updateSection = [this]() { + m_arpEditor->setVisible(m_mainButtons.m_arpSettings.getToggleState()); + m_fxEditor->setVisible(m_mainButtons.m_effects.getToggleState()); + m_lfoEditor->setVisible(m_mainButtons.m_lfoMatrix.getToggleState()); + m_oscEditor->setVisible(m_mainButtons.m_oscFilter.getToggleState()); + }; + + addAndMakeVisible(m_presetButtons); + + setSize (kPanelWidth, kPanelHeight); +} + +VirusEditor::~VirusEditor() { setLookAndFeel(nullptr); } + +void VirusEditor::applyToSections(std::function<void(Component *)> action) +{ + for (auto *section : {static_cast<Component *>(m_arpEditor.get()), static_cast<Component *>(m_fxEditor.get()), + static_cast<Component *>(m_lfoEditor.get()), static_cast<Component *>(m_oscEditor.get())}) + { + action(section); + } +} + +void VirusEditor::resized() +{ + m_background->setBounds (getLocalBounds()); + m_mainButtons.setBounds (394, 106, m_mainButtons.getWidth(), m_mainButtons.getHeight()); + auto statusArea = Rectangle<int>(395, 36, 578, 60); + m_presetButtons.setBounds(statusArea.removeFromRight(188)); + applyToSections([this](Component *s) { s->setTopLeftPosition(338, 133); }); +} + +VirusEditor::MainButtons::MainButtons() +: m_oscFilter ("OSC|FILTER", DrawableButton::ImageRaw) +, m_lfoMatrix ("LFO|MATRIX", DrawableButton::ImageRaw) +, m_effects ("EFFECTS", DrawableButton::ImageRaw) +, m_arpSettings ("ARP|SETTINGS", DrawableButton::ImageRaw) +{ + constexpr auto numOfMainButtons = 4; + setupButton (0, Drawable::createFromImageData (BinaryData::GLOBAL_btn_osc_filter_141x26_png, BinaryData::GLOBAL_btn_osc_filter_141x26_pngSize), m_oscFilter); + setupButton (1, Drawable::createFromImageData (BinaryData::GLOBAL_btn_lfo_matrix_141x26_png, BinaryData::GLOBAL_btn_lfo_matrix_141x26_pngSize), m_lfoMatrix); + setupButton (2, Drawable::createFromImageData (BinaryData::GLOBAL_btn_effects_141x26_png, BinaryData::GLOBAL_btn_effects_141x26_pngSize), m_effects); + setupButton (3, Drawable::createFromImageData (BinaryData::GLOBAL_btn_arp_settings_141x26_png, BinaryData::GLOBAL_btn_arp_settings_141x26_pngSize), m_arpSettings); + setSize ((kButtonWidth + kMargin) * numOfMainButtons, kButtonHeight); +} + +void VirusEditor::MainButtons::valueChanged(juce::Value &) { updateSection(); } + +void VirusEditor::MainButtons::setupButton (int i, std::unique_ptr<Drawable>&& btnImage, juce::DrawableButton& btn) +{ + auto onImage = btnImage->createCopy(); + onImage->setOriginWithOriginalSize({0, -kButtonHeight}); + btn.setClickingTogglesState (true); + btn.setRadioGroupId (kGroupId); + btn.setImages (btnImage.get(), nullptr, nullptr, nullptr, onImage.get()); + btn.setBounds ((i > 1 ? -1 : 0) + i * (kButtonWidth + kMargin), 0, kButtonWidth, kButtonHeight); + btn.getToggleStateValue().addListener(this); + addAndMakeVisible (btn); +} + +VirusEditor::PresetButtons::PresetButtons() +{ + for (auto *btn : {&m_save, &m_load, &m_presets}) + addAndMakeVisible(btn); + constexpr auto y = 8; + constexpr auto w = Buttons::PresetButton::kWidth; + m_save.setBounds(28, y, w, Buttons::PresetButton::kHeight); + m_load.setBounds(36 + w, y, w, Buttons::PresetButton::kHeight); + m_presets.setBounds(43 + w * 2, y, w, Buttons::PresetButton::kHeight); +} diff --git a/source/jucePlugin/ui/VirusEditor.h b/source/jucePlugin/ui/VirusEditor.h @@ -0,0 +1,50 @@ +#pragma once + +#include <juce_gui_extra/juce_gui_extra.h> +#include "Virus_Buttons.h" +#include "Virus_LookAndFeel.h" + +class OscEditor; +class LfoEditor; +class FxEditor; +class ArpEditor; + +class VirusEditor : public juce::Component +{ +public: + VirusEditor(); + ~VirusEditor(); + void resized() override; + +private: + struct MainButtons : juce::Component, juce::Value::Listener + { + MainButtons(); + void setupButton (int i, std::unique_ptr<juce::Drawable>&& btn, juce::DrawableButton&); + void valueChanged(juce::Value &) override; + + std::function<void()> updateSection; + juce::DrawableButton m_oscFilter, m_lfoMatrix, m_effects, m_arpSettings; + static constexpr auto kMargin = 5; + static constexpr auto kButtonWidth = 141; + static constexpr auto kButtonHeight = 26; + static constexpr auto kGroupId = 0x3FBBA; + } m_mainButtons; + + struct PresetButtons : juce::Component + { + PresetButtons(); + Buttons::PresetButton m_save, m_load, m_presets; + } m_presetButtons; + + void applyToSections(std::function<void(juce::Component *)>); + + std::unique_ptr<OscEditor> m_oscEditor; + std::unique_ptr<LfoEditor> m_lfoEditor; + std::unique_ptr<FxEditor> m_fxEditor; + std::unique_ptr<ArpEditor> m_arpEditor; + + std::unique_ptr<juce::Drawable> m_background; + + Virus::LookAndFeel m_lookAndFeel; +}; diff --git a/source/jucePlugin/ui/Virus_ArpEditor.cpp b/source/jucePlugin/ui/Virus_ArpEditor.cpp @@ -0,0 +1,119 @@ +#include "Virus_ArpEditor.h" +#include "BinaryData.h" +#include "Ui_Utils.h" + +constexpr auto comboBoxWidth = 84; + +using namespace juce; + +ArpEditor::ArpEditor() +{ + setupBackground(*this, m_background, BinaryData::bg_arp_1018x620_png, BinaryData::bg_arp_1018x620_pngSize); + setBounds(m_background->getDrawableBounds().toNearestIntEdges()); + + m_velocityAmount.setBounds(23, 28, 439, 238); + addAndMakeVisible(m_velocityAmount); + m_inputs.setBounds(23, m_velocityAmount.getBottom() + 2, 439, 118); + addAndMakeVisible(m_inputs); + m_arp.setBounds(m_velocityAmount.getRight() + 2, 28, 552, 120); + addAndMakeVisible(m_arp); + m_softKnobs.setBounds(m_arp.getX(), m_arp.getBottom() + 2, 552, 116); + addAndMakeVisible(m_softKnobs); + m_patchSettings.setBounds(m_softKnobs.getX(), m_softKnobs.getBottom() + 2, 552, 194); + addAndMakeVisible(m_patchSettings); +} + +ArpEditor::VelocityAmount::VelocityAmount() +{ + constexpr auto y = 19; + for (auto *s : {&m_osc1Shape, &m_filter1Freq, &m_filter1Res, &m_pulseWidth, &m_volume, &m_panorama, &m_osc2Shape, + &m_filter2Freq, &m_filter2Res, &m_fmAmount}) + setupRotary(*this, *s); + m_osc1Shape.setBounds(31, y, knobSize, knobSize); + m_filter1Freq.setBounds(m_osc1Shape.getRight() - 7, y, knobSize, knobSize); + m_filter1Res.setBounds(m_filter1Freq.getRight() - 8, y, knobSize, knobSize); + m_pulseWidth.setBounds(m_filter1Res.getRight() - 7, y, knobSize, knobSize); + m_volume.setBounds(m_pulseWidth.getRight() - 3, y, knobSize, knobSize); + m_panorama.setBounds(m_volume.getRight() - 9, y, knobSize, knobSize); + + const auto y2 = m_osc1Shape.getBottom() + y; + m_osc2Shape.setBounds(31, y2, knobSize, knobSize); + m_filter2Freq.setBounds(m_osc1Shape.getRight() - 7, y2, knobSize, knobSize); + m_filter2Res.setBounds(m_filter1Freq.getRight() - 8, y2, knobSize, knobSize); + m_fmAmount.setBounds(m_filter1Res.getRight() - 7, y2, knobSize, knobSize); +} + +ArpEditor::Inputs::Inputs() +{ + addAndMakeVisible(m_inputMode); + m_inputMode.setBounds(43, 38, comboBoxWidth, comboBoxHeight); + addAndMakeVisible(m_inputSelect); + m_inputSelect.setBounds(145, 38, comboBoxWidth, comboBoxHeight); +} + +ArpEditor::Arpeggiator::Arpeggiator() +{ + constexpr auto y = 18; + for (auto *s : {&m_globalTempo, &m_noteLength, &m_noteSwing}) + setupRotary(*this, *s); + m_globalTempo.setBounds(341, y, knobSize, knobSize); + m_noteLength.setBounds(m_globalTempo.getRight() - 8, y, knobSize, knobSize); + m_noteSwing.setBounds(m_noteLength.getRight() - 7, y, knobSize, knobSize); + + for (auto *c : {&m_mode, &m_pattern, &m_octaveRange, &m_resolution}) + addAndMakeVisible(c); + + constexpr auto comboBoxWidth = 90; + constexpr auto comboBoxHeight = 15; + constexpr auto comboTopY = 35; + + m_mode.setBounds(39, 40, 52, 38); + m_pattern.setBounds(114, comboTopY, comboBoxWidth, comboBoxHeight); + m_resolution.setBounds(220, comboTopY, comboBoxWidth, comboBoxHeight); + m_octaveRange.setBounds(m_pattern.getBounds().translated(0, comboBoxHeight + 18)); + + m_arpHold.setButtonText("On"); + addAndMakeVisible(m_arpHold); + m_arpHold.setBounds(220, m_octaveRange.getY(), 32, comboBoxHeight); +} + +ArpEditor::SoftKnobs::SoftKnobs() +{ + auto distance = 105; + for (auto i = 0; i < 2; i++) + { + addAndMakeVisible(m_funcAs[i]); + m_funcAs[i].setBounds(i == 0 ? 18 : 338, 42, comboBoxWidth, comboBoxHeight); + addAndMakeVisible(m_name[i]); + m_name[i].setBounds(m_funcAs[i].getX() + distance, 42, comboBoxWidth, comboBoxHeight); + } +} + +ArpEditor::PatchSettings::PatchSettings() +{ + constexpr auto y = 18; + for (auto *s : {&m_patchVolume, &m_panning, &m_outputBalance, &m_transpose}) + setupRotary(*this, *s); + m_patchVolume.setBounds(101, y, knobSize, knobSize); + m_panning.setBounds(m_patchVolume.getRight() - 9, y, knobSize, knobSize); + const auto y2 = m_patchVolume.getBottom() + 9; + m_outputBalance.setBounds(m_patchVolume.getX(), y2, knobSize, knobSize); + m_transpose.setBounds(m_panning.getX(), y2, knobSize, knobSize); + + for (auto *cb : + {&m_keyMode, &m_secondaryOutput, &m_bendUp, &m_bendDown, &m_bendScale, &m_smoothMode, &m_cat1, &m_cat2}) + addAndMakeVisible(cb); + + + constexpr auto yDist = 50; + m_keyMode.setBounds(18, 42, comboBoxWidth, comboBoxHeight); + m_secondaryOutput.setBounds(18, 122, comboBoxWidth, comboBoxHeight); + constexpr auto x1 = 338; + constexpr auto x2 = 444; + m_bendUp.setBounds(x1, 42, comboBoxWidth, comboBoxHeight); + m_bendScale.setBounds(x1, 42 + yDist, comboBoxWidth, comboBoxHeight); + m_cat1.setBounds(x1, m_bendScale.getY() + yDist - 1, comboBoxWidth, comboBoxHeight); + m_bendDown.setBounds(x2, 42, comboBoxWidth, comboBoxHeight); + m_smoothMode.setBounds(x2, m_bendScale.getY(), comboBoxWidth, comboBoxHeight); + m_cat2.setBounds(x2, m_cat1.getY(), comboBoxWidth, comboBoxHeight); +} diff --git a/source/jucePlugin/ui/Virus_ArpEditor.h b/source/jucePlugin/ui/Virus_ArpEditor.h @@ -0,0 +1,60 @@ +#pragma once + +#include "../PluginProcessor.h" + +class ArpEditor : public juce::Component +{ +public: + ArpEditor(); + +private: + struct VelocityAmount : juce::Component + { + VelocityAmount(); + juce::Slider m_osc1Shape; + juce::Slider m_filter1Freq; + juce::Slider m_filter1Res; + juce::Slider m_pulseWidth; + juce::Slider m_volume; + juce::Slider m_panorama; + juce::Slider m_osc2Shape; + juce::Slider m_filter2Freq; + juce::Slider m_filter2Res; + juce::Slider m_fmAmount; + } m_velocityAmount; + + struct Inputs : juce::Component + { + Inputs(); + juce::ComboBox m_inputMode, m_inputSelect; + } m_inputs; + + struct Arpeggiator : juce::Component + { + Arpeggiator(); + juce::Slider m_globalTempo; + juce::Slider m_noteLength; + juce::Slider m_noteSwing; + juce::ComboBox m_mode, m_pattern, m_octaveRange, m_resolution; + juce::TextButton m_arpHold; + } m_arp; + + struct SoftKnobs : juce::Component + { + SoftKnobs(); + juce::ComboBox m_funcAs[2], m_name[2]; + } m_softKnobs; + + struct PatchSettings : juce::Component + { + PatchSettings(); + juce::Slider m_patchVolume; + juce::Slider m_panning; + juce::Slider m_outputBalance; + juce::Slider m_transpose; + juce::ComboBox m_keyMode, m_secondaryOutput; + juce::ComboBox m_bendUp, m_bendDown, m_bendScale, m_smoothMode, m_cat1, m_cat2; + } m_patchSettings; + + std::unique_ptr<juce::Drawable> m_background; +}; diff --git a/source/jucePlugin/ui/Virus_Buttons.cpp b/source/jucePlugin/ui/Virus_Buttons.cpp @@ -0,0 +1,95 @@ +#include "Virus_Buttons.h" +#include "BinaryData.h" + +using namespace juce; + +namespace Buttons +{ + Buttons::HandleButton::HandleButton() : DrawableButton("HandleButton", DrawableButton::ImageRaw) + { + auto up = Drawable::createFromImageData(BinaryData::Handle_18x47_png, BinaryData::Handle_18x47_pngSize); + auto down = up->createCopy(); + setColour(DrawableButton::ColourIds::backgroundColourId, Colours::transparentBlack); + setColour(DrawableButton::ColourIds::backgroundOnColourId, Colours::transparentBlack); + setClickingTogglesState(true); + down->setOriginWithOriginalSize({-18, 0}); + setImages(down.get(), nullptr, up.get(), nullptr, up.get(), nullptr, down.get()); + } + + Buttons::LfoButton::LfoButton() : DrawableButton("LFOButton", DrawableButton::ImageRaw) + { + auto off = Drawable::createFromImageData(BinaryData::lfo_btn_23_19_png, BinaryData::lfo_btn_23_19_pngSize); + auto on = off->createCopy(); + setColour(DrawableButton::ColourIds::backgroundColourId, Colours::transparentBlack); + setColour(DrawableButton::ColourIds::backgroundOnColourId, Colours::transparentBlack); + setClickingTogglesState(true); + on->setOriginWithOriginalSize({0, -19}); + setImages(off.get(), nullptr, on.get(), nullptr, on.get()); + } + + Buttons::EnvPol::EnvPol() : m_pos("Positive", DrawableButton::ImageRaw), m_neg("Negative", DrawableButton::ImageRaw) + { + static int radioGroup = 0x4bc3f; + radioGroup++; // group counter is static to generate group per each button. + // 27x15 + for (auto *b : {&m_pos, &m_neg}) + { + b->setRadioGroupId(radioGroup); + b->setColour(DrawableButton::ColourIds::backgroundColourId, Colours::transparentBlack); + b->setColour(DrawableButton::ColourIds::backgroundOnColourId, Colours::transparentBlack); + b->setClickingTogglesState(true); + } + auto pos_off = Drawable::createFromImageData(BinaryData::env_pol_50x34_png, BinaryData::env_pol_50x34_pngSize); + auto pos_on = pos_off->createCopy(); + pos_on->setOriginWithOriginalSize({-25, 0}); + m_pos.setImages(pos_on.get(), nullptr, pos_off.get(), nullptr, pos_off.get(), nullptr, pos_on.get()); + m_pos.setBounds(1, 1, 25, 13); + addAndMakeVisible(m_pos); + + addAndMakeVisible(m_neg); + auto neg_off = Drawable::createFromImageData(BinaryData::env_pol_50x34_png, BinaryData::env_pol_50x34_pngSize); + neg_off->setOriginWithOriginalSize({0, -17}); + auto neg_on = neg_off->createCopy(); + neg_on->setOriginWithOriginalSize({-25, -17}); + m_neg.setImages(neg_off.get(), nullptr, neg_on.get(), nullptr, neg_on.get(), nullptr, nullptr, neg_off.get()); + m_neg.setBounds(1, 18, 25, 13); + } + + Buttons::LinkButton::LinkButton(bool isVert) : DrawableButton("LinkButton", DrawableButton::ImageRaw) + { + auto off = Drawable::createFromImageData( + isVert ? BinaryData::link_vert_12x36_png : BinaryData::link_horizon_36x12_png, + isVert ? BinaryData::link_vert_12x36_pngSize : BinaryData::link_horizon_36x12_pngSize); + auto on = off->createCopy(); + setColour(DrawableButton::ColourIds::backgroundColourId, Colours::transparentBlack); + setColour(DrawableButton::ColourIds::backgroundOnColourId, Colours::transparentBlack); + setClickingTogglesState(true); + if (isVert) + on->setOriginWithOriginalSize({-12, 0}); + else + on->setOriginWithOriginalSize({0, -12}); + setImages(off.get(), nullptr, on.get(), nullptr, on.get()); + } + + Buttons::SyncButton::SyncButton() : DrawableButton("SyncButton", DrawableButton::ImageRaw) + { + auto off = Drawable::createFromImageData(BinaryData::sync2_54x25_png, BinaryData::sync2_54x25_pngSize); + auto on = off->createCopy(); + setColour(DrawableButton::ColourIds::backgroundColourId, Colours::transparentBlack); + setColour(DrawableButton::ColourIds::backgroundOnColourId, Colours::transparentBlack); + setClickingTogglesState(true); + on->setOriginWithOriginalSize({0, -25}); + setImages(off.get(), nullptr, on.get(), nullptr, on.get()); + } + + Buttons::PresetButton::PresetButton() : DrawableButton("PresetButton", DrawableButton::ImageRaw) + { + auto normal = + Drawable::createFromImageData(BinaryData::presets_btn_43_15_png, BinaryData::presets_btn_43_15_pngSize); + auto pressed = normal->createCopy(); + pressed->setOriginWithOriginalSize({0, -15}); + setColour(DrawableButton::ColourIds::backgroundColourId, Colours::transparentBlack); + setColour(DrawableButton::ColourIds::backgroundOnColourId, Colours::transparentBlack); + setImages(normal.get(), nullptr, pressed.get(), nullptr, pressed.get(), nullptr, normal.get()); + } +}; // namespace Buttons diff --git a/source/jucePlugin/ui/Virus_Buttons.h b/source/jucePlugin/ui/Virus_Buttons.h @@ -0,0 +1,54 @@ +#pragma once + +#include <juce_gui_extra/juce_gui_extra.h> + +namespace Buttons +{ + class HandleButton : public juce::DrawableButton + { + public: + HandleButton(); + static constexpr auto kWidth = 18; + static constexpr auto kHeight = 47; + }; + + class LfoButton : public juce::DrawableButton + { + public: + LfoButton(); + static constexpr auto kWidth = 23; + static constexpr auto kHeight = 19; + }; + + class EnvPol : public juce::Component + { + public: + EnvPol(); + + private: + juce::DrawableButton m_pos; + juce::DrawableButton m_neg; + }; + + class LinkButton : public juce::DrawableButton + { + public: + LinkButton(bool isVert); + }; + + class SyncButton : public juce::DrawableButton + { + public: + static constexpr auto kWidth = 54; + static constexpr auto kHeight = 25; + SyncButton(); + }; + + class PresetButton : public juce::DrawableButton + { + public: + static constexpr auto kWidth = 43; + static constexpr auto kHeight = 15; + PresetButton(); + }; +} // namespace Buttons diff --git a/source/jucePlugin/ui/Virus_FxEditor.cpp b/source/jucePlugin/ui/Virus_FxEditor.cpp @@ -0,0 +1,185 @@ +#include "Virus_FxEditor.h" +#include "BinaryData.h" +#include "Ui_Utils.h" + +using namespace juce; + +constexpr auto comboBoxWidth = 84; + +FxEditor::FxEditor() +{ + setupBackground(*this, m_background, BinaryData::bg_fx_1018x620_png, BinaryData::bg_fx_1018x620_pngSize); + setBounds(m_background->getDrawableBounds().toNearestIntEdges()); + + m_dist.setBounds(23, 28, 273, 116); + addAndMakeVisible(m_dist); + m_analogBoost.setBounds(m_dist.getRight() + 2, 28, 235, 116); + addAndMakeVisible(m_analogBoost); + m_phaser.setBounds(m_dist.getX(), m_dist.getBottom() + 2, 510, 116); + addAndMakeVisible(m_phaser); + m_chorus.setBounds(m_phaser.getBounds().withY(m_phaser.getBottom() + 2)); + addAndMakeVisible(m_chorus); + m_eq.setBounds(23, m_chorus.getBottom() + 2, 510, 109); + addAndMakeVisible(m_eq); + m_envFollow.setBounds(23, m_eq.getBottom() + 2, 510 - 174 - 2, 103); + addAndMakeVisible(m_envFollow); + m_punch.setBounds(m_envFollow.getRight() + 2, m_envFollow.getY(), 174, 103); + addAndMakeVisible(m_punch); + m_delayReverb.setBounds(m_phaser.getRight() + 2, m_dist.getY(), 481, m_phaser.getHeight() * 2); + addAndMakeVisible(m_delayReverb); + m_vocoder.setBounds(m_delayReverb.getBounds().withY(m_delayReverb.getBottom() + 2).withHeight(304)); + addAndMakeVisible(m_vocoder); +} + +FxEditor::Distortion::Distortion() +{ + setupRotary(*this, m_intensity); + m_intensity.setBounds(101, 18, knobSize, knobSize); + addAndMakeVisible(m_curve); + m_curve.setBounds(17, 42, comboBoxWidth, comboBoxHeight); +} + +FxEditor::AnalogBoost::AnalogBoost() +{ + for (auto *s : {&m_boost, &m_tune}) + setupRotary(*this, *s); + m_boost.setBounds(16, 18, knobSize, knobSize); + m_tune.setBounds(m_boost.getBounds().withX(m_boost.getRight() - 4)); +} + +FxEditor::Phaser::Phaser() +{ + constexpr auto y = 16; + for (auto *s : {&m_rate, &m_freq, &m_depth, &m_feedback, &m_spread, &m_mix}) + setupRotary(*this, *s); + m_rate.setBounds(100, 16, knobSize, knobSize); + m_freq.setBounds(m_rate.getRight() - 8, y, knobSize, knobSize); + m_depth.setBounds(m_freq.getRight() - 7, y, knobSize, knobSize); + m_feedback.setBounds(m_depth.getRight() - 5, y, knobSize, knobSize); + m_spread.setBounds(m_feedback.getRight() - 5, y, knobSize, knobSize); + m_mix.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); + m_mix.setBounds(m_spread.getRight() - 7, y, knobSize, knobSize); + addAndMakeVisible(m_stages); + m_stages.setBounds(17, 41, comboBoxWidth, comboBoxHeight); +} + +FxEditor::Chorus::Chorus() +{ + constexpr auto y = 18; + for (auto *s : {&m_rate, &m_depth, &m_feedback, &m_delay, &m_mix}) + setupRotary(*this, *s); + m_rate.setBounds(101, y, knobSize, knobSize); + m_depth.setBounds(m_rate.getRight() - 8, y, knobSize, knobSize); + m_feedback.setBounds(m_depth.getRight() - 8, y, knobSize, knobSize); + m_delay.setBounds(m_feedback.getRight() - 6, y, knobSize, knobSize); + m_mix.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); + m_mix.setBounds(m_delay.getRight() - 4, y, knobSize, knobSize); + addAndMakeVisible(m_lfoShape); + m_lfoShape.setBounds(17, 42, comboBoxWidth, comboBoxHeight); +} + +FxEditor::Equalizer::Equalizer() +{ + constexpr auto y = 18; + for (auto *s : {&m_low_gain, &m_low_freq, &m_mid_gain, &m_mid_freq, &m_mid_q, &m_high_gain, &m_high_freq}) + setupRotary(*this, *s); + m_low_gain.setBounds(37, y, knobSize, knobSize); + m_low_freq.setBounds(m_low_gain.getRight() - 6, y, knobSize, knobSize); + m_mid_gain.setBounds(m_low_freq.getRight() - 8, y, knobSize, knobSize); + m_mid_freq.setBounds(m_mid_gain.getRight() - 7, y, knobSize, knobSize); + m_mid_q.setBounds(m_mid_freq.getRight() - 6, y, knobSize, knobSize); + m_high_gain.setBounds(m_mid_q.getRight() - 6, y, knobSize, knobSize); + m_high_freq.setBounds(m_high_gain.getRight() - 6, y, knobSize, knobSize); +} + +FxEditor::EnvelopeFollower::EnvelopeFollower() +{ + constexpr auto y = 12; + for (auto *s : {&m_gain, &m_attack, &m_release}) + setupRotary(*this, *s); + m_gain.setBounds(101, y, knobSize, knobSize); + m_attack.setBounds(m_gain.getRight() - 8, y, knobSize, knobSize); + m_release.setBounds(m_attack.getRight() - 7, y, knobSize, knobSize); + addAndMakeVisible(m_input); + m_input.setBounds(17, 37, comboBoxWidth, comboBoxHeight); +} + +FxEditor::Punch::Punch() +{ + setupRotary(*this, m_amount); + m_amount.setBounds(19, 12, knobSize, knobSize); +} + +FxEditor::DelayAndReverb::DelayAndReverb() +{ + constexpr auto y = 18; + for (auto *s : {&m_time, &m_rate, &m_depth, &m_color, &m_feedback}) + setupRotary(*this, *s); + m_time.setBounds(118, 18, knobSize, knobSize); + m_rate.setBounds(m_time.getRight() - 8, y, knobSize, knobSize); + m_depth.setBounds(m_rate.getRight() - 8, y, knobSize, knobSize); + m_color.setBounds(m_depth.getRight() - 3, y, knobSize, knobSize); + m_feedback.setBounds(m_color.getRight() - 3, y, knobSize, knobSize); + + addAndMakeVisible(m_fxMode); + m_fxMode.setBounds(18, 42, comboBoxWidth, comboBoxHeight); + + m_sync.setBounds(0, 116 + 2, 481, 116); + addAndMakeVisible(m_sync); +} + +FxEditor::DelayAndReverb::Sync::Sync() +{ + setupRotary(*this, m_mix); + m_mix.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); + m_mix.setBounds(376, -2, knobSize, knobSize); + + addAndMakeVisible(m_clock); + m_clock.setBounds(18, 22, comboBoxWidth, comboBoxHeight); + addAndMakeVisible(m_lfoShape); + m_lfoShape.setBounds(m_clock.getBounds().getRight() + 26, 22, comboBoxWidth, comboBoxHeight); +} + +FxEditor::Vocoder::Vocoder() : m_link(true) +{ + constexpr auto y = 17; + for (auto *s : {&m_sourceBalance, &m_spectralBalance, &m_bands, &m_attack, &m_release}) + setupRotary(*this, *s); + m_sourceBalance.setBounds(117, 17, knobSize, knobSize); + m_spectralBalance.setBounds(m_sourceBalance.getRight() - 8, y, knobSize, knobSize); + m_bands.setBounds(m_spectralBalance.getRight() - 7, y, knobSize, knobSize); + m_attack.setBounds(m_bands.getRight() - 2, y, knobSize, knobSize); + m_release.setBounds(m_attack.getRight() - 3, y, knobSize, knobSize); + + m_carrier.setBounds(105, 125, 328, 80); + addAndMakeVisible(m_carrier); + m_modulator.setBounds(10, 218, 423, 81); + addAndMakeVisible(m_modulator); + + addAndMakeVisible(m_link); + m_link.setBounds(445, 195, 12, 36); + addAndMakeVisible(m_mode); + m_mode.setBounds(16, 43, comboBoxWidth, comboBoxHeight); +} + +FxEditor::Vocoder::Carrier::Carrier() +{ + constexpr auto y = -4; + for (auto *s : {&m_center_freq, &m_q_factor, &m_spread}) + setupRotary(*this, *s); + m_center_freq.setBounds(12, -4, knobSize, knobSize); + m_q_factor.setBounds(m_center_freq.getRight() - 8, y, knobSize, knobSize); + m_spread.setBounds(m_q_factor.getRight() - 7, y, knobSize, knobSize); +} + +FxEditor::Vocoder::Modulator::Modulator() +{ + constexpr auto y = -4; + for (auto *s : {&m_freq_offset, &m_q_factor, &m_spread}) + setupRotary(*this, *s); + m_freq_offset.setBounds(107, y, knobSize, knobSize); + m_q_factor.setBounds(m_freq_offset.getRight() - 8, y, knobSize, knobSize); + m_spread.setBounds(m_q_factor.getRight() - 7, y, knobSize, knobSize); + addAndMakeVisible(m_modInput); + m_modInput.setBounds(8, 23, comboBoxWidth, comboBoxHeight); +} diff --git a/source/jucePlugin/ui/Virus_FxEditor.h b/source/jucePlugin/ui/Virus_FxEditor.h @@ -0,0 +1,124 @@ +#pragma once + +#include "../PluginProcessor.h" +#include "Virus_Buttons.h" + +class FxEditor : public juce::Component +{ +public: + FxEditor(); + +private: + struct Distortion : juce::Component + { + Distortion(); + juce::Slider m_intensity; + juce::ComboBox m_curve; + } m_dist; + + struct AnalogBoost : juce::Component + { + AnalogBoost(); + juce::Slider m_boost; + juce::Slider m_tune; + } m_analogBoost; + + struct Phaser : juce::Component + { + Phaser(); + juce::Slider m_rate; + juce::Slider m_freq; + juce::Slider m_depth; + juce::Slider m_feedback; + juce::Slider m_spread; + juce::Slider m_mix; + juce::ComboBox m_stages; + } m_phaser; + + struct Chorus : juce::Component + { + Chorus(); + juce::Slider m_rate; + juce::Slider m_depth; + juce::Slider m_feedback; + juce::Slider m_delay; + juce::Slider m_mix; + juce::ComboBox m_lfoShape; + } m_chorus; + + struct Equalizer : juce::Component + { + Equalizer(); + juce::Slider m_low_gain; + juce::Slider m_low_freq; + juce::Slider m_mid_gain; + juce::Slider m_mid_freq; + juce::Slider m_mid_q; + juce::Slider m_high_gain; + juce::Slider m_high_freq; + } m_eq; + + struct EnvelopeFollower : juce::Component + { + EnvelopeFollower(); + juce::Slider m_gain; + juce::Slider m_attack; + juce::Slider m_release; + juce::ComboBox m_input; + } m_envFollow; + + struct Punch : juce::Component + { + Punch(); + juce::Slider m_amount; + } m_punch; + + struct DelayAndReverb : juce::Component + { + DelayAndReverb(); + juce::Slider m_time; + juce::Slider m_rate; + juce::Slider m_depth; + juce::Slider m_color; + juce::Slider m_feedback; + juce::ComboBox m_fxMode; + + struct Sync : juce::Component + { + Sync(); + juce::Slider m_mix; + juce::ComboBox m_clock, m_lfoShape; + } m_sync; + } m_delayReverb; + + struct Vocoder : juce::Component + { + Vocoder(); + juce::Slider m_sourceBalance; + juce::Slider m_spectralBalance; + juce::Slider m_bands; + juce::Slider m_attack; + juce::Slider m_release; + Buttons::LinkButton m_link; + juce::ComboBox m_mode; + + struct Carrier : juce::Component + { + Carrier(); + juce::Slider m_center_freq; + juce::Slider m_q_factor; + juce::Slider m_spread; + } m_carrier; + + struct Modulator : juce::Component + { + Modulator(); + juce::Slider m_freq_offset; + juce::Slider m_q_factor; + juce::Slider m_spread; + juce::ComboBox m_modInput; + } m_modulator; + } m_vocoder; + + std::unique_ptr<juce::Drawable> m_background; +}; diff --git a/source/jucePlugin/ui/Virus_LfoEditor.cpp b/source/jucePlugin/ui/Virus_LfoEditor.cpp @@ -0,0 +1,133 @@ +#include "Virus_LfoEditor.h" +#include "BinaryData.h" +#include "Ui_Utils.h" + +using namespace juce; +constexpr auto comboBoxWidth = 98; + +LfoEditor::LfoEditor() +{ + setupBackground(*this, m_background, BinaryData::bg_lfo_1018x620_png, BinaryData::bg_lfo_1018x620_pngSize); + setBounds(m_background->getDrawableBounds().toNearestIntEdges()); + + m_lfoOne.setBounds(23, 28, 522, 188); + addAndMakeVisible(m_lfoOne); + m_lfoTwo.setBounds(m_lfoOne.getBounds().withY(m_lfoOne.getBottom() + 2)); + addAndMakeVisible(m_lfoTwo); + m_lfoThree.setBounds(m_lfoTwo.getBounds().withY(m_lfoTwo.getBottom() + 2)); + addAndMakeVisible(m_lfoThree); + constexpr auto kMatrixWidth = 450; + constexpr auto kMatrixHeight = 568; + m_modMatrix.setBounds(getWidth() - kMatrixWidth - 12, 28, kMatrixWidth, kMatrixHeight); + addAndMakeVisible(m_modMatrix); +} + +LfoEditor::LfoBase::LfoBase() +{ + for (auto *s : {&m_rate, &m_keytrack, &m_amount}) + setupRotary(*this, *s); + addAndMakeVisible(m_subWaveform); + m_subWaveform.setBounds(8, 123, Buttons::HandleButton::kWidth, Buttons::HandleButton::kHeight); + + addAndMakeVisible(m_shape); + m_shape.setBounds(10, 37, 84, comboBoxHeight); + addAndMakeVisible(m_clock); + m_clock.setBounds(10, 80, 84, comboBoxHeight); + addAndMakeVisible(m_assignDest); +} + +LfoEditor::LfoTwoOneShared::LfoTwoOneShared() : m_link(false) +{ + for (auto *s : {&m_contour, &m_phase}) + setupRotary(*this, *s); + m_rate.setBounds(106, 15, knobSize, knobSize); + m_keytrack.setBounds(m_rate.getBounds().translated(65, 0)); + m_contour.setBounds(m_rate.getBounds().translated(0, knobSize + 14)); + m_phase.setBounds(m_keytrack.getBounds().translated(0, knobSize + 14)); + m_amount.setBounds(307, knobSize + 28, knobSize, knobSize); + addAndMakeVisible(m_envMode); + m_envMode.setBounds(66, 122, Buttons::LfoButton::kWidth, Buttons::LfoButton::kHeight); + + m_link.setBounds(293, 8, 36, 12); + m_assignDest.setBounds(393, 122, comboBoxWidth, comboBoxHeight); +} + +LfoEditor::LfoOne::LfoOne() +{ + for (auto *s : {&m_osc1Pitch, &m_osc2Pitch, &m_filterGain, &m_pw12, &m_reso12}) + setupRotary(*this, *s); + m_osc1Pitch.setBounds(245, m_keytrack.getY(), knobSize, knobSize); + m_osc2Pitch.setBounds(m_osc1Pitch.getBounds().translated(64, 0)); + m_filterGain.setBounds(m_osc1Pitch.getBounds().withY(m_amount.getY())); + m_pw12.setBounds(374, 14, knobSize, knobSize); + m_reso12.setBounds(m_pw12.getBounds().translated(knobSize - 4, 0)); + addAndMakeVisible(m_link); // add last to allow clicking over knobs area... +} + +LfoEditor::LfoTwo::LfoTwo() +{ + for (auto *s : {&m_f1cutoff, &m_f2cutoff, &m_panning, &m_shape12, &m_fmAmount}) + setupRotary(*this, *s); + m_f1cutoff.setBounds(245, m_keytrack.getY(), knobSize, knobSize); + m_f2cutoff.setBounds(m_f1cutoff.getBounds().translated(64, 0)); + m_panning.setBounds(m_f1cutoff.getBounds().withY(m_amount.getY())); + m_shape12.setBounds(374, 14, knobSize, knobSize); + m_fmAmount.setBounds(m_shape12.getBounds().translated(knobSize - 4, 0)); + addAndMakeVisible(m_link); // add last to allow clicking over knobs area... +} + +LfoEditor::LfoThree::LfoThree() +{ + setupRotary(*this, m_fadeIn); + m_rate.setBounds(107, 22, knobSize, knobSize); + m_fadeIn.setBounds(m_rate.getBounds().translated(knobSize - 6, 0)); + m_keytrack.setBounds(m_rate.getBounds().translated(0, knobSize + 6)); + m_amount.setBounds(307, 22, knobSize, knobSize); + m_assignDest.setBounds(393, 45, comboBoxWidth, comboBoxHeight); +} + +LfoEditor::ModMatrix::ModMatrix() +{ + constexpr auto kNumOfSlots = 6; + for (auto s = 0; s < kNumOfSlots; s++) + m_modMatrix.push_back(std::make_unique<MatrixSlot>(s == 0 ? 3 : s == 1 ? 2 : 1)); + setupSlot(0, {{20, 89}, {20, 165}, {20, 241}}, {86, 65}); + setupSlot(1, {{20, 386}, {20, 462}}, {86, 363}); + setupSlot(2, {{255, 89}}, {320, 65}); + setupSlot(3, {{255, 214}}, {320, 190}); + setupSlot(4, {{255, 338}}, {320, 314}); + setupSlot(5, {{255, 462}}, {320, 439}); +} + +void LfoEditor::ModMatrix::setupSlot(int slotNum, std::initializer_list<juce::Point<int>> destsPos, + juce::Point<int> sourcePos) +{ + constexpr auto width = MatrixSlot::Dest::kWidth; + constexpr auto height = MatrixSlot::Dest::kHeight; + auto &slot = *m_modMatrix[slotNum]; + int i = 0; + for (auto pos : destsPos) + { + auto &dest = *slot.m_destinations[i]; + addAndMakeVisible(dest); + dest.setBounds(pos.x, pos.y, width, height); + i++; + } + addAndMakeVisible(slot.m_source); + slot.m_source.setBounds(sourcePos.x, sourcePos.y, comboBoxWidth, comboBoxHeight); +} + +LfoEditor::ModMatrix::MatrixSlot::Dest::Dest() +{ + setupRotary(*this, m_amount); + m_amount.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_BLUE); + m_amount.setBounds(-6, -4, knobSize, knobSize); + addAndMakeVisible(m_dest); + m_dest.setBounds(66, 35, comboBoxWidth, comboBoxHeight); +} + +LfoEditor::ModMatrix::MatrixSlot::MatrixSlot(int numOfDests) +{ + for (auto d = 0; d < numOfDests; d++) + m_destinations.push_back(std::make_unique<Dest>()); +} diff --git a/source/jucePlugin/ui/Virus_LfoEditor.h b/source/jucePlugin/ui/Virus_LfoEditor.h @@ -0,0 +1,81 @@ +#pragma once + +#include "../PluginProcessor.h" +#include "Virus_Buttons.h" + +class LfoEditor : public juce::Component +{ +public: + LfoEditor(); + +private: + struct LfoBase : juce::Component + { + LfoBase(); + juce::Slider m_rate; + juce::Slider m_keytrack; + juce::Slider m_amount; + Buttons::HandleButton m_subWaveform; + juce::ComboBox m_shape, m_clock; + juce::ComboBox m_assignDest; + }; + + struct LfoTwoOneShared : LfoBase + { + LfoTwoOneShared(); + juce::Slider m_contour; + juce::Slider m_phase; + Buttons::LfoButton m_envMode; + Buttons::LinkButton m_link; + }; + + struct LfoOne : LfoTwoOneShared + { + LfoOne(); + juce::Slider m_osc1Pitch; + juce::Slider m_osc2Pitch; + juce::Slider m_filterGain; + juce::Slider m_pw12; + juce::Slider m_reso12; + } m_lfoOne; + + struct LfoTwo : LfoTwoOneShared + { + LfoTwo(); + juce::Slider m_f1cutoff; + juce::Slider m_f2cutoff; + juce::Slider m_panning; + juce::Slider m_shape12; + juce::Slider m_fmAmount; + } m_lfoTwo; + + struct LfoThree : LfoBase + { + LfoThree(); + juce::Slider m_fadeIn; + } m_lfoThree; + + struct ModMatrix : juce::Component + { + ModMatrix(); + void setupSlot(int slot, std::initializer_list<juce::Point<int>> destsPos, juce::Point<int> sourcePos); + struct MatrixSlot : juce::Component + { + MatrixSlot(int numOfDests); + struct Dest : juce::Component + { + static constexpr auto kWidth = 173; + static constexpr auto kHeight = 62; + Dest(); + juce::Slider m_amount; + juce::ComboBox m_dest; + }; + juce::ComboBox m_source; + std::vector<std::unique_ptr<Dest>> m_destinations; + }; + + std::vector<std::unique_ptr<MatrixSlot>> m_modMatrix; + } m_modMatrix; + + std::unique_ptr<juce::Drawable> m_background; +}; diff --git a/source/jucePlugin/ui/Virus_LookAndFeel.cpp b/source/jucePlugin/ui/Virus_LookAndFeel.cpp @@ -0,0 +1,65 @@ +#include "Virus_LookAndFeel.h" +#include "BinaryData.h" + +using namespace juce; + +namespace Virus +{ + + const char *LookAndFeel::KnobStyleProp = "knob_style"; + + LookAndFeel::LookAndFeel() + { + // setup knobs... + m_genKnob = Drawable::createFromImageData(BinaryData::Gen_70x70_100_png, BinaryData::Gen_70x70_100_pngSize); + m_genPol = + Drawable::createFromImageData(BinaryData::Gen_pol_70x70_100_png, BinaryData::Gen_pol_70x70_100_pngSize); + m_genBlue = + Drawable::createFromImageData(BinaryData::GenBlue_70x70_100_png, BinaryData::GenBlue_70x70_100_pngSize); + m_genRed = + Drawable::createFromImageData(BinaryData::GenRed_70x70_100_png, BinaryData::GenRed_70x70_100_pngSize); + m_multi = Drawable::createFromImageData(BinaryData::multi_18x18_100_png, BinaryData::multi_18x18_100_pngSize); + + m_knobImageSteps.start = 0; + m_knobImageSteps.end = 99; + } + + void LookAndFeel::drawRotarySlider(Graphics &g, int x, int y, int width, int height, float sliderPosProportional, + float rotaryStartAngle, float rotaryEndAngle, Slider &s) + { + Drawable *knob; + switch (static_cast<int>(s.getProperties().getWithDefault(KnobStyleProp, KnobStyle::GENERIC))) + { + case KnobStyle::GENERIC_POL: + knob = m_genPol.get(); + break; + case KnobStyle::GENERIC_RED: + knob = m_genRed.get(); + break; + case KnobStyle::GENERIC_BLUE: + knob = m_genBlue.get(); + break; + case KnobStyle::GENERIC: + default: + knob = m_genKnob.get(); + } + + { + // debug + // g.fillAll (Colours::pink.withAlpha (0.4f)); + const auto step = roundToInt(m_knobImageSteps.convertFrom0to1(sliderPosProportional)); + // take relevant pos + knob->setOriginWithOriginalSize({0.0f, -70.0f * step}); + } + // this won't support scaling! + knob->drawAt(g, x, y, 1.0f); + } + +void LookAndFeel::drawComboBox (Graphics& g, int width, int height, bool isButtonDown, + int buttonX, int buttonY, int buttonW, int buttonH, + ComboBox&) +{ + // panels draws combo box... so it's invisible :) +} + +} // namespace Virus diff --git a/source/jucePlugin/ui/Virus_LookAndFeel.h b/source/jucePlugin/ui/Virus_LookAndFeel.h @@ -0,0 +1,34 @@ +#pragma once + +#include <juce_gui_extra/juce_gui_extra.h> + +namespace Virus +{ + class LookAndFeel : public juce::LookAndFeel_V4 + { + public: + LookAndFeel(); + + static constexpr auto kKnobSize = 70; + + enum KnobStyle + { + GENERIC, + GENERIC_POL, + GENERIC_BLUE, + GENERIC_RED + }; + + static const char *KnobStyleProp; + + void drawRotarySlider(juce::Graphics &, int x, int y, int width, int height, float sliderPosProportional, + float rotaryStartAngle, float rotaryEndAngle, juce::Slider &) override; + + void drawComboBox (juce::Graphics&, int width, int height, bool isButtonDown, + int buttonX, int buttonY, int buttonW, int buttonH, + juce::ComboBox&) override; + private: + std::unique_ptr<juce::Drawable> m_genKnob, m_genPol, m_genBlue, m_genRed, m_multi; + juce::NormalisableRange<float> m_knobImageSteps; + }; +} // namespace Virus diff --git a/source/jucePlugin/ui/Virus_OscEditor.cpp b/source/jucePlugin/ui/Virus_OscEditor.cpp @@ -0,0 +1,179 @@ +#include "Virus_OscEditor.h" +#include "BinaryData.h" +#include "Ui_Utils.h" + +using namespace juce; + +constexpr auto comboBoxWidth = 84; + +OscEditor::OscEditor() +{ + setupBackground(*this, m_background, BinaryData::bg_osc_1018x620_png, BinaryData::bg_osc_1018x620_pngSize); + setBounds(m_background->getDrawableBounds().toNearestIntEdges()); + + addAndMakeVisible(m_oscOne); + addAndMakeVisible(m_oscTwo); + addAndMakeVisible(m_oscThree); + addAndMakeVisible(m_unison); + addAndMakeVisible(m_mixer); + addAndMakeVisible(m_ringMod); + addAndMakeVisible(m_sub); + addAndMakeVisible(m_portamento); + addAndMakeVisible(m_filters); + addAndMakeVisible(m_filterEnv); + addAndMakeVisible(m_ampEnv); + addAndMakeVisible(m_oscSync); +} + +void OscEditor::resized() +{ + m_oscOne.setBounds(23, 28, 379, 116); + m_oscTwo.setBounds(m_oscOne.getBounds().translated(0, 119).withHeight(216)); + m_oscThree.setBounds(m_oscTwo.getX(), m_oscTwo.getBottom(), m_oscTwo.getWidth(), 116); + m_unison.setBounds(m_oscThree.getBounds().translated(0, 119)); + m_mixer.setBounds(m_oscOne.getBounds().translated(381, 0).withWidth(165)); + m_ringMod.setBounds(m_mixer.getBounds().translated(0, m_mixer.getHeight() + 2).withHeight(216)); + m_sub.setBounds(m_ringMod.getBounds().withY(m_ringMod.getBottom() + 2).withHeight(m_mixer.getHeight())); + m_portamento.setBounds(m_sub.getX(), m_sub.getBottom() + 2, m_sub.getWidth(), 114); + m_filters.setBounds(m_mixer.getRight() + 2, m_mixer.getY(), 445, 334); + m_filterEnv.setBounds(m_filters.getX(), m_filters.getBottom() + 2, m_filters.getWidth(), m_oscOne.getHeight()); + m_ampEnv.setBounds(m_filterEnv.getBounds().withY(m_filterEnv.getBounds().getBottom() + 2)); + m_oscSync.setBounds(319, roundToInt(4 + m_oscOne.getBottom() - Buttons::SyncButton::kHeight * 0.5), + Buttons::SyncButton::kWidth, Buttons::SyncButton::kHeight); +} + +OscEditor::OscOne::OscOne() +{ + for (auto *s : {&m_shape, &m_pulseWidth, &m_semitone, &m_keyFollow}) + setupRotary(*this, *s); + m_shape.setBounds(101, 17, knobSize, knobSize); + m_pulseWidth.setBounds(m_shape.getBounds().translated(62, 0)); + m_semitone.setBounds(m_pulseWidth.getBounds().translated(63, 0)); + m_keyFollow.setBounds(m_semitone.getBounds().translated(66, 0)); + + addAndMakeVisible(m_waveSelect); + m_waveSelect.setBounds (18, 42, comboBoxWidth, comboBoxHeight); +} + +OscEditor::OscTwo::OscTwo() +{ + for (auto *s : {&m_fmAmount, &m_detune, &m_envFm, &m_envOsc2}) + setupRotary(*this, *s); + m_fmAmount.setBounds(m_shape.getBounds().translated(0, 95)); + m_detune.setBounds(m_fmAmount.getBounds().translated(62, 0)); + m_envFm.setBounds(m_detune.getBounds().translated(63, 0)); + m_envOsc2.setBounds(m_envFm.getBounds().translated(66, 0)); + addAndMakeVisible (m_fmMode); + m_fmMode.setBounds (18, 140, comboBoxWidth, comboBoxHeight); +} + +OscEditor::OscThree::OscThree() +{ + for (auto *s : {&m_semitone, &m_detune, &m_level}) + setupRotary(*this, *s); + m_semitone.setBounds(101, 17, knobSize, knobSize); + m_detune.setBounds(m_semitone.getBounds().translated(62, 0)); + m_level.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); + m_level.setBounds(m_detune.getBounds().translated(63, 0)); + addAndMakeVisible (m_oscThreeMode); + m_oscThreeMode.setBounds (18, 43, comboBoxWidth, comboBoxHeight); +} + +OscEditor::Unison::Unison() +{ + for (auto *s : {&m_detune, &m_panSpread, &m_lfoPhase, &m_phaseInit}) + setupRotary(*this, *s); + m_detune.setBounds(101, 17, knobSize, knobSize); + m_panSpread.setBounds(m_detune.getBounds().translated(62, 0)); + m_lfoPhase.setBounds(m_panSpread.getBounds().translated(63, 0)); + m_phaseInit.setBounds(m_lfoPhase.getBounds().translated(66, 0)); + addAndMakeVisible (m_unisonVoices); + m_unisonVoices.setBounds (18, 42, comboBoxWidth, comboBoxHeight); +} + +OscEditor::Mixer::Mixer() +{ + for (auto *s : {&m_oscBalance, &m_oscLevel}) + setupRotary(*this, *s); + m_oscBalance.setBounds(14, 18, knobSize, knobSize); + m_oscLevel.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); + m_oscLevel.setBounds(m_oscBalance.getBounds().translated(knobSize, 0)); +} + +OscEditor::RingMod::RingMod() +{ + for (auto *s : {&m_noiseLevel, &m_ringModLevel, &m_noiseColor}) + setupRotary(*this, *s); + m_noiseLevel.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); + m_noiseLevel.setBounds(14, 20, knobSize, knobSize); + m_ringModLevel.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); + m_ringModLevel.setBounds(m_noiseLevel.getBounds().translated(knobSize, 0)); + m_noiseColor.setBounds(m_noiseLevel.getBounds().translated(0, 95)); +} + +OscEditor::Sub::Sub() +{ + setupRotary(*this, m_level); + m_level.getProperties().set(Virus::LookAndFeel::KnobStyleProp, Virus::LookAndFeel::KnobStyle::GENERIC_RED); + m_level.setBounds(14 + knobSize, 20, knobSize, knobSize); + m_subWaveform.setBounds(30, 34, Buttons::HandleButton::kWidth, Buttons::HandleButton::kHeight); + addAndMakeVisible(m_subWaveform); +} + +OscEditor::Portamento::Portamento() +{ + setupRotary(*this, m_portamento); + m_portamento.setBounds(12, 18, knobSize, knobSize); +} + +OscEditor::Filters::Filters() : m_link1(true), m_link2(true) +{ + addAndMakeVisible(m_filter[0]); + addAndMakeVisible(m_filter[1]); + m_filter[0].setBounds(28, 17, 390, knobSize); + m_filter[1].setBounds(m_filter[0].getBounds().translated(0, 215)); + setupRotary(*this, m_filterBalance); + m_filterBalance.setBounds(132, 137, knobSize, knobSize); + addAndMakeVisible(m_filterBalance); + + m_envPol[0].setBounds(317, 179, 27, 33); + m_envPol[1].setBounds(m_envPol[0].getBounds().translated(48, 0)); + addAndMakeVisible(m_envPol[0]); + addAndMakeVisible(m_envPol[1]); + + addAndMakeVisible(m_link1); + m_link1.setBounds(7, 143, 12, 36); + addAndMakeVisible(m_link2); + m_link2.setBounds(m_link1.getBounds().withX(426)); + + for (auto* combo : {&m_filterMode[0], &m_filterMode[1], &m_filterRouting, &m_saturationCurve, &m_keyFollowBase}) + addAndMakeVisible (combo); + m_filterMode[0].setBounds (32, 128, comboBoxWidth, comboBoxHeight); + m_filterMode[1].setBounds (32, 178, comboBoxWidth, comboBoxHeight); + m_filterRouting.setBounds (m_filterMode[0].getBounds().translated (184, 0)); + m_saturationCurve.setBounds (m_filterMode[1].getBounds().translated (184, 0)); + m_keyFollowBase.setBounds (m_filterMode[0].getBounds().translated (286, 0)); +} + +OscEditor::Filters::Filter::Filter() +{ + for (auto *s : {&m_cutoff, &m_res, &m_envAmount, &m_keyTrack, &m_resVel, &m_envVel}) + setupRotary(*this, *s); + m_cutoff.setBounds(0, 0, knobSize, knobSize); + m_res.setBounds(m_cutoff.getBounds().translated(knobSize - 9, 0)); + m_envAmount.setBounds(m_res.getBounds().translated(knobSize - 6, 0)); + m_keyTrack.setBounds(m_envAmount.getBounds().translated(knobSize - 5, 0)); + m_resVel.setBounds(m_keyTrack.getBounds().translated(knobSize - 5, 0)); + m_envVel.setBounds(m_resVel.getBounds().translated(knobSize - 5, 0)); +} + +OscEditor::Envelope::Envelope() +{ + for (auto *s : {&m_attack, &m_decay, &m_sustain, &m_time, &m_release}) + setupRotary(*this, *s); + m_attack.setBounds(28, 17, knobSize, knobSize); + m_decay.setBounds(m_attack.getBounds().translated(knobSize + 10, 0)); + m_sustain.setBounds(m_decay.getBounds().translated(knobSize + 9, 0)); + m_time.setBounds(m_sustain.getBounds().translated(knobSize + 9, 0)); + m_release.setBounds(m_time.getBounds().translated(knobSize + 12, 0)); +} diff --git a/source/jucePlugin/ui/Virus_OscEditor.h b/source/jucePlugin/ui/Virus_OscEditor.h @@ -0,0 +1,118 @@ +#pragma once + +#include "../PluginProcessor.h" +#include "Virus_Buttons.h" + +class OscEditor : public juce::Component +{ +public: + OscEditor(); + void resized() override; + +private: + struct OscOne : juce::Component + { + OscOne(); + juce::Slider m_shape; + juce::Slider m_pulseWidth; + juce::Slider m_semitone; + juce::Slider m_keyFollow; + juce::ComboBox m_waveSelect; + } m_oscOne; + + struct OscTwo : OscOne + { + OscTwo(); + juce::Slider m_fmAmount, m_detune, m_envFm, m_envOsc2; + juce::ComboBox m_fmMode; + } m_oscTwo; + + struct OscThree : juce::Component + { + OscThree(); + juce::Slider m_semitone; + juce::Slider m_detune; + juce::Slider m_level; + juce::ComboBox m_oscThreeMode; + } m_oscThree; + + struct Unison : juce::Component + { + Unison(); + juce::Slider m_detune; + juce::Slider m_panSpread; + juce::Slider m_lfoPhase; + juce::Slider m_phaseInit; + juce::ComboBox m_unisonVoices; + } m_unison; + + struct Mixer : juce::Component + { + Mixer(); + juce::Slider m_oscBalance; + juce::Slider m_oscLevel; + } m_mixer; + + struct RingMod : juce::Component + { + RingMod(); + juce::Slider m_noiseLevel; + juce::Slider m_ringModLevel; + juce::Slider m_noiseColor; + + } m_ringMod; + + struct Sub : juce::Component + { + Sub(); + juce::Slider m_level; + Buttons::HandleButton m_subWaveform; + } m_sub; + + struct Portamento : juce::Component + { + Portamento(); + juce::Slider m_portamento; + } m_portamento; + + struct Filters : juce::Component + { + Filters(); + struct Filter : juce::Component + { + Filter(); + juce::Slider m_cutoff; + juce::Slider m_res; + juce::Slider m_envAmount; + juce::Slider m_keyTrack; + juce::Slider m_resVel; + juce::Slider m_envVel; + } m_filter[2]; + juce::Slider m_filterBalance; + Buttons::EnvPol m_envPol[2]; + Buttons::LinkButton m_link1, m_link2; + juce::ComboBox m_filterMode[2]; + juce::ComboBox m_filterRouting, m_saturationCurve, m_keyFollowBase; + } m_filters; + + struct Envelope : juce::Component + { + Envelope(); + juce::Slider m_attack; + juce::Slider m_decay; + juce::Slider m_sustain; + juce::Slider m_time; + juce::Slider m_release; + }; + + struct FilterEnv : Envelope + { + } m_filterEnv; + + struct AmpEnv : Envelope + { + } m_ampEnv; + + Buttons::SyncButton m_oscSync; + std::unique_ptr<juce::Drawable> m_background; +};