foleys_gui_magic

repository·main·Indexed 20 days ago

https://github.com/ffaudio/foleys_gui_magic

A JUCE module for creating audio plugin GUIs using a DOM-based model and CSS-like stylesheets. It features a WYSIWYG drag-and-drop editor to connect UI elements to AudioProcessor parameters, supporting components like Sliders, ComboBoxes, and Plot visualizers (Analysers/Oscilloscopes). Developers inherit from foleys::MagicProcessor to automate state management and can bake the final GUI XML directly into the plugin binary for release.

Tokens
3.8K
Snippets
10
Records
22
Agent score
68%

What's inside foleys_gui_magic

  1. Use CSS classes for property inheritance

    main

    While you can set properties directly on nodes, PluginGuiMagic supports a CSS-like class system for efficient styling. Properties are looked up by traversing the DOM.

    • Class Assignment: Every Component or View can be assigned a list of CSS classes.
    • Inheritance Control: CSS classes include a switch that determines whether the properties defined in the class should be inherited by the children or applied only to the element referencing the class.
  2. Manage Plugin State and GUI Persistence

    main

    The project provides options to control how the GUI tree is stored:

    • Store in plugin state: By default, the GUI tree can be stored in the plugin state. You can use a switch to disable this behavior (added in v1.3.1).
    • Edit mode persistence: New components respect the current edit mode (v1.3.2).
    • Note: The FOLEYS_SAVE_EDITED_GUI_IN_PLUGIN_STATE flag was removed in v1.3.3.
  3. How PluginGuiMagic works

    main

    PluginGuiMagic is a layout engine and editor for JUCE that allows you to create GUIs without writing code. The GUI is structured as a DOM tree consisting of Components and CSS instructions for visual properties.

    Key structural concepts:

    • GuiItem: Every Component is wrapped in a GuiItem, which provides decorations like margins, padding, borders, and captions. Backgrounds can be configured as images, solid colors, or color gradients.
    • Views: Panels are organized hierarchically. A View can contain other View containers or Components.
    • Display Property: Every View has a display property that determines how its children are laid out:
      • FlexBox (Default): Uses FlexBox layout logic.
      • Content: Allows manual positioning. You can drag children to specific locations. In the side panel under Node, you can use a % suffix for positions and dimensions to make them relative to the parent View instead of absolute pixels.
      • Tabbed: Creates a tab bar for the child Views or Components.
  4. Use MagicGUIBuilder for custom GUI trees

    main
    Version 1.3.3 introduced a callback to MagicProcessor that allows developers to implement bespoke generic GUI trees. Additionally, MagicGUIBuilder can be used within a JUCEApplication or any standard juce::Component (as of v1.2.0).
  5. Implement responsive design with FlexBox and CSS switches

    main

    Responsive layouts in PluginGuiMagic are achieved through two primary methods:

    1. FlexBox: Use the FlexBox display mode to manage how elements react to resizing.
    2. CSS Class Switches:
      • You can toggle CSS classes on or off (for example, by connecting them to a checkbox).
      • You can define min size or max size for a CSS class. The class will automatically switch off if the plugin size is smaller than the min size or greater than the max size.
  6. Save and restore plugin state and application settings

    main

    The foleys::MagicProcessor automatically handles saving and restoring parameters and properties. You can extend this by adding your own values to the ValueTree within magicState.

    To manage persistent settings that remain consistent across all instances of your plugin, configure a settings file path. These settings are managed as hierarchical ValueTree data within magicState.getSettings() and include built-in interprocess safety.

    // In constructor to setup application-wide settings
    magicState.setApplicationSettingsFile (juce::File::getSpecialLocation (juce::File::userApplicationDataDirectory)
                                           .getChildFile (ProjectInfo::companyName)
                                           .getChildFile (ProjectInfo::projectName + juce::String (".settings")));
    
    // Use it to manage hierarchical data
    auto presetNode = magicState.getSettings().getOrCreateChildWithName ("presets", nullptr);
    presetNode.setProperty ("foo", 100, nullptr);
  7. Setup foleys_gui_magic in a JUCE project

    main

    To use the WYSWYG plugin editor, add the foleys_gui_magic module to your JUCE project via CMake (juce_add_module()) or Projucer.

    Instead of inheriting from juce::AudioProcessor, you must inherit from foleys::MagicProcessor. When using foleys::MagicProcessor, you should remove the following methods from your class:

    • bool hasEditor()
    • juce::PluginEditor* createEditor()
    • void setStateInformation() and void getStateInformation() (a default implementation is provided).

    To enable the floating editor's auto-save feature, you must provide the source file location in your foleys::MagicProcessor constructor using the FOLEYS_SET_SOURCE_PATH macro.

    // In your foleys::MagicProcessor constructor
    FOLEYS_SET_SOURCE_PATH(__FILE__);
  8. Add an Oscilloscope visualizer

    main

    You can add an oscilloscope to your plugin by using foleys::MagicOscilloscope, which is a type of MagicPlotSource.

    1. Declare a member: Add a pointer to foleys::MagicPlotSource in your processor's private section.
    2. Register the source: In your constructor, register the oscilloscope with magicState.addPlotSource and provide a unique ID string (e.g., "oscilloscope").
    3. Prepare the state: Call magicState.prepareToPlay inside your processor's prepareToPlay method.
    4. Push samples: Call pushSamples(buffer) on the oscilloscope pointer within your audio processing loop to visualize the signal.
    // 1. Declare member
    private:
        foleys::MagicPlotSource* oscilloscope = nullptr;
    
    // 2. Register in constructor
    // oscilloscope = magicState.addPlotSource ("oscilloscope", std::make_unique<foleys::MagicOscilloscope>(0));
    
    // 3. Prepare in prepareToPlay
    void SignalGeneratorAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
    {
        magicState.prepareToPlay (sampleRate, samplesPerBlock);
    }
    
    // 4. Push samples in processBlock
    void processBlock (juce::AudioBuffer<float>& buffer, ...)
    {
        oscilloscope->pushSamples (buffer);
    }
  9. Bake the GUI XML into your plugin binary

    main

    To avoid shipping the GUI definition as a separate file, you can bake the XML exported from the PGM panel directly into your plugin's binary resources (e.g., via Projucer's BinaryData). In your constructor, tell the magicState to use this binary data.

    // In constructor
    magicState.setGuiValueTree (BinaryData::magic_xml, BinaryData::magic_xmlSize);
  10. Migrate to MagicProcessor (v1.3.0 breaking changes)

    main

    Starting from version 1.3.0, the GUI architecture changed significantly. The AudioProcessorValueTreeState is no longer required or supplied to the MagicProcessorState. Instead, the GUI ValueTree is now contained within MagicGUIState.

    To simplify implementation, use the MagicProcessor class, which handles the necessary boilerplate for managing the GUI state and connecting it to your plugin.

    // Note: MagicProcessor handles the boilerplate that previously required 
    // manual management of AudioProcessorValueTreeState and addBackgroundProcessing().
  11. Setup a foleys_gui_magic project

    main

    To use foleys_gui_magic in a JUCE project, follow these steps:

    1. Add Modules: Add the foleys_gui_magic module to your project. Note that it depends on juce::dsp for FFT and FrequencyResponse curves.
    2. Inherit from MagicProcessor: Instead of inheriting from juce::AudioProcessor, inherit from foleys::MagicProcessor.
    3. Clean up AudioProcessor methods: Remove the following methods from both your .h and .cpp files (declaration and implementation):
      • bool hasEditor() const
      • juce::AudioProcessorEditor* createEditor()
      • void setStateInformation (const char*, int)
      • void getStateInformation (juce::MemoryBlock&)
    class MyProcessor : public foleys::MagicProcessor
    {
        // Remove hasEditor, createEditor, setStateInformation, and getStateInformation
    };