What is a PIP?


#1

We’ve just released JUCE 5.3.0, where the main focus is improving the documentation, code examples and tutorials to make it easier for people to get started and learn how to use JUCE. Around 30 new tutorials can be found on the JUCE website here, covering a wide range of topics from DSP to in-app purchases and over 70 updated, JUCE team code-reviewed code examples have been added to the examples directory.

In order to keep the JUCE repository and download size reasonable, we came up with a way of generating a full JUCE project from a single header file containing some JUCE code and a chunk of metadata that is read by the Projucer - called a Projucer Instant Project, or PIP. The JUCE code examples have been categorised and converted to this format and can be opened simply by dragging and dropping the file into the Projucer, or can be opened directly via the Projucer’s File->Open Example menu item. Additionally a new “DemoRunner” application has been added to JUCE that allows you to preview all of the JUCE code examples in one place, and see the code side-by-side. This can be run via the Projucer’s File->Open Example->Launch Demo Runner menu item.

As well as being useful for keeping the JUCE download size down we hope that PIPs can also be utilised for sharing code and ideas between developers. For example, try copying this code to your clipboard and then opening it in the Projucer using the File->New Project From Clipboard... option:

/*******************************************************************************
 The block below describes the properties of this PIP. A PIP is a short snippet
 of code that can be read by the Projucer and used to generate a JUCE project.

 BEGIN_JUCE_PIP_METADATA

 name:             HelloWorldDemo
 version:          1.0.0
 vendor:           juce
 website:          http://juce.com
 description:      Simple HelloWorld application.

 dependencies:     juce_core, juce_data_structures, juce_events, juce_graphics,
                   juce_gui_basics
 exporters:        xcode_mac, vs2017, linux_make, xcode_iphone

 type:             Component
 mainClass:        HelloWorldDemo

 useLocalCopy:     1

 END_JUCE_PIP_METADATA

*******************************************************************************/

#pragma once


//==============================================================================
class HelloWorldDemo  : public Component
{
public:
    //==============================================================================
    HelloWorldDemo()
    {
        addAndMakeVisible (helloWorldLabel);

        helloWorldLabel.setFont (Font (40.00f, Font::bold));
        helloWorldLabel.setJustificationType (Justification::centred);
        helloWorldLabel.setEditable (false, false, false);
        helloWorldLabel.setColour (Label::textColourId, Colours::black);
        helloWorldLabel.setColour (TextEditor::textColourId, Colours::black);
        helloWorldLabel.setColour (TextEditor::backgroundColourId, Colour (0x00000000));

        addAndMakeVisible (quitButton);
        quitButton.onClick = [this] { JUCEApplication::quit(); };

        setSize (600, 300);
    }

    //==============================================================================
    void paint (Graphics& g) override
    {
        g.fillAll (Colour (0xffc1d0ff));

        g.setColour (Colours::white);
        g.fillPath (internalPath);

        g.setColour (Colour (0xff6f6f6f));
        g.strokePath (internalPath, PathStrokeType (5.200f));
    }

    void resized() override
    {
        helloWorldLabel.setBounds (152, 80, 296, 48);
        quitButton.setBounds (getWidth() - 176, getHeight() - 60, 120, 32);

        internalPath.clear();
        internalPath.startNewSubPath (136.0f, 80.0f);
        internalPath.quadraticTo (176.0f, 24.0f, 328.0f, 32.0f);
        internalPath.quadraticTo (472.0f, 40.0f, 472.0f, 104.0f);
        internalPath.quadraticTo (472.0f, 192.0f, 232.0f, 176.0f);
        internalPath.lineTo (184.0f, 216.0f);
        internalPath.lineTo (200.0f, 168.0f);
        internalPath.quadraticTo (96.0f, 136.0f, 136.0f, 80.0f);
        internalPath.closeSubPath();
    }

private:
    //==============================================================================
    Label helloWorldLabel { {}, TRANS("Hello World!") };
    TextButton quitButton { TRANS("Quit") };
    Path internalPath;

    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HelloWorldDemo)
};

As you can see it contains a simple class deriving from Component, and the metadata at the top is read by the Projucer to generate all the boilerplate startup/shutdown code for you - you simply need to fill out a few fields and tell it the type of application it is (a Component, AudioProcessor, or Console).

Check out the JUCE examples for some more examples and please post any questions about PIPs here!


ButtonAttachment doesn't call AudioProcessorValueTreeState::Listener::parameterChanged
.Jucer files missing for plugin demos
#2

Great!
a small request to the web team:
I don’t know much about “discourse”, but that would be handy if we could have a “copy to clipboard” button for the code blocks


#3

Yes, that’s a good idea. I’ll look into it


#4

And while you are at it, is it possible to mark a post as an “accepted solution”?
In most cases, when the first post of a topic is a question, it is then required to scan the whole topic to get to what is the solution / correct answer.

IIRC, there is even a badge for that, so it must be supported somehow


#5

How do you create a PIP?
simply by hand, or there is an utility app/projucer functionality that can help creating it?


#6

cool stuff


#7

By hand at the moment, but there will be a simple tool in the Projucer to generate the metadata soon.

The PIPs in the examples directory should cover all of the metadata options that you can set and there are examples of both Components and AudioProcessors, but I’ll summarise here:

There are a total of 12 fields, the required fields are marked with an asterisk (*):

  • name* - the JUCE project name.
  • version - used for the “Project Version” field in the Projucer
  • vendor - used for the “Company Name” field in the Projucer
  • website - used for the “Company Website” field in the Projucer
  • description - a short description of the PIP
  • dependencies* - the JUCE modules that should be added to the project
  • exporters* - the exporters that should be added to the project
  • moduleFlags - sets one, or many, of the JUCE module flags
  • defines - sets some global preprocessor definitions for the project. Used to populate the “Preprocessor Definitions” field in the Projucer.
  • type* - the type of project. This can either be Console, Component or AudioProcessor
  • mainClass* - the name of the main class that should be instantiated. There can only be one main class and it must have a default constructor. Depending on the type, this may need to inherit from a specific JUCE class
  • useLocalCopy - set this to specify that the PIP file should be copied to the generated project directory instead of just referred to. This is used for the JUCE examples because we don’t want people modifying the original example code when creating a project from one of the example PIPs

When generating the project, the Projucer creates a Main.cpp file that includes the PIP file and the contents of this file depend on the type that was specified.

A PIP can either be a Console application, a Component or an AudioProcessor, set via the type field.

  • A console application must contain a main function and the generated Main.cpp just includes the PIP file.
  • A Component PIP must have its main class (specified using the mainClass field) inherit from Component and must have a default constructor. The Main.cpp that is generated will instantiate this main class and put it in a simple desktop window.
  • An AudioProcessor PIP must have its main class inherit from AudioProcessor. The Main.cpp that is generated just implements the createPluginFilter() method and returns a new instance of this class.

#8

Hi @ed95

Are PIPs primarily intended for sharing small examples, demos POCs etc? I’m guessing they’re not planned as a replacement for .jucer files but if so it’s probably worth being clear about that.


#9

No, we’re certainly not replacing .jucer files. PIPs are simply a way of generating a .jucer file from a single header file and handling some boilerplate startup/shutdown code for you. We used them to reduce the clutter and file size of the JUCE examples directory, but hopefully they will also be a really useful tool for people who want to share code on the forum, between devs etc.


#10

Currently generating the IDE project files from a PIP requires to manually choose the folder where the project files will be generated. Could a global folder option be added into Projucer?


#11

The project is initially generated into a temp folder so the Projucer prompts you for a save location when you close or save the project. I suppose there could be a default for this location, but personally I have found that I often want to save different projects in different locations so I’m not sure how useful it would be.


#12

What I’d like to see is a kind of temporary space that can be used to generate the PJ and IDE files from a PIP. One of the pains of C++ (although I know you can directly compile files on the command line) is the necessity to create a project and files etc. just to run some code.

If this was handled transparently but the PJ when loading a PIP, you could simply forget about the project files and they could be cleaned up when the PJ closes.
This means you’d be able to simply keep a folder with PIPs in essentially for code snippets which is much cleaner.

I’ve not fully explored PIPs yet so this might not be possible. From what I gather, a PIP is essentially used to generate a project so any changes to the PIP or generated files afterwards would be disconnected. This might not fit with the above workflow so well.
(Would be cool if changing the PIP updated the code in the project though…)


#13

It might be better to default to the folder containing the PIP file? For example I’ve just been using the audioplugindemo PIP and the logical place for that is in that examples folder…


#14

The JUCE project files are generated in a temporary directory that is cleaned up when the Projucer exits. If you don’t add the useLocalCopy option to the metadata then the PIP file won’t be copied to that directory and the project will just refer to it in the original location, meaning you can decouple the PIPs and the generated JUCE project. When saving the project and generating the IDE files however, you’ll be prompted to choose a location to where it will be moved.


#15

TLDR:
A PIP is a human-readable replacement for a .jucer file that exists within a .cpp file as a comment. The concept is similar to Unix’s “Shebang” (#!/bin/sh) which explains in-place in a file how to run it.

Regarding the name it’s unfortunate that it shares its name with Python’s well known pip. As Python is the fastest-growing major programming language and at least in this corner of the world Python it is the most popular programming language, it will certainly cause some confusion.


#16

Yeah, but a JUCE PIP? The pun was too good to pass up


#17

I’ll admit that as a non-native British nor English speaker - I didn’t get the pun…


#18

Had to Google it, too. You know that there is a world beyond the UK out there, right? :joy_cat:


#19

Did I get it right?


#20

With this commit there is now a “PIP Creator” tool in the Projucer (accessed via File->Create PIP... or cmd/ctrl + P) that you can use to generate the metadata required for a PIP:

You just need to fill out a few fields and then hit the “Create PIP” button to choose where to save the file.