New Beginners' Tutorials

Not sure if this is the right place to plug these, but I’ve put up some Tutorials for getting started with a JUCE Plugin. https://jucestepbystep.wordpress.com/

Please post any feedback or comments. The links in the Tutorials redirect back to this thread.

cheers,

Ollie

11 Likes

Hi Ollie.

I’m sure this tutorial took a long time to put together. It looks solid, other than the things I’ve mentioned here.

A few comments:

  1. using C-style casts to access parameter values is frowned upon.
if (int(*myBypassptr) == 0)
  1. the AudioParameterBool type has a getter function that returns true or false

AudioProcessorValueTreeState::getParameter returns the RangedAudioParameter*.
you can dynamic_cast<> this to AudioParameterBool* and have access to this get() function:

//header file:
AudioParameterBool* bypass = nullptr;
//constructor:
bypass = dynamic_cast<AudioParameterBool*>( apvts.getParameter("bypass param") );
//processBlock:
if( ! bypass->get() )
{
    auto myBlock = juce::dsp::AudioBlock<float>(buffer);
    auto myContext = juce::dsp::ProcessContextReplacing<float>(myBlock);

    myFilter.process(myContext);
}

This get() function provides much more readable code in processBlock() which is very important for new users.

  1. Your tutorial suggests manually copying the plugin from the build folder to the VST folder.

This is unnecessary if you enable the Plugin Copy Step in ProJucer.
This is the Xcode Exporter setting, but it exists in the Visual Studio exporter settings

  1. Your tutorial suggests downloading the trial of Ableton if you don’t have a plugin host installed.

Audio Plugin Host is included with JUCE. there is no need to download a DAW if you don’t already have one.

  1. Your tutorial suggests launching your DAW manually every time you want to test the plugin.

You can configure the Audio Plugin Host (included with JUCE) or any other host to launch automatically when you run the program:

  1. When configuring Visual Studio’s installer, the only things you need are the
  • MSVC (the compiler)
  • Just In Time debugger
  • C++ Profiling Tools
  • Address Sanitizer

None of that other stuff is used when developing JUCE plugins initially.

1 Like
  1. rather than advising against downloading an extra daw i’d suggest advising to provide a list of free and cheap daws to test with. not just because all daws have slightly different behaviour that needs to be tested, but also because only a real daw has transport data like ppq and bpm, so some plugin ideas can just full-on not be tested in juce’ audio plugin host
2 Likes

Well, a C-style cast would be (int) *myBypassptr == 0. And I agree, C style casts are bad since they mix the concept of static_cast and reinterpret_cast and therefore are somewhat ambigous. However the code snippet above is a C++ constructor style cast, which is just a syntactically alternative way of a static cast – actually I prefer it for better readability over writing static_cast. What‘s wrong with that?

3 Likes

Thanks for the feedback so far, everybody. Please keep it coming! I’m mulling over the best way to include your suggestions.

One of my goals has been to reduce cognitive load. That inclines me towards a direct-line, consistent, no-tricks approach. But I might overdo it, so I really appreciate guidance on what’s worth including & excluding for beginners.

Ollie

2 Likes

Constructor style casts are too similar visually to C-style casts in my opinion. Perfect example, I totally missed that it wasn’t a C-style cast. Hence, the suggestion to use the explicit cast I named.

2 Likes

Functional casts are equivalent to C-style casts, so they’re also const_cast → static_cast (allowing private base) → reinterpret_cast, whichever works first. (That said, I hate the C++ cast notation, I use functional or C-style, in that order, whenever it’s possible).

If the type is known in advance, you can use static_cast. A downcast with dynamic_cast is never simplified by the compiler, so it has a runtime overhead.

4 Likes

Thanks @kamedin for clearing that up. I was obviously wrong here and will rethink my personal cast style usage. But before this becomes a cast style discussion let‘s come back to the topic :wink: @drollie I just scanned over your tutorial quickly, but indeed it seems very detailed and the instructions are very clear. I assume that you spent quite a lot of time writing it, thank you for this investment into the audio programmer community! I get your point on not mentioning too much side information. However one central point that should be mentioned is that this is clearly a Windows centric tutorial and that other operating systems need other toolchains. As a lot of audio people that might want to get started with plugins are macOS users, it might mislead people to download Visual Studio for macOS and fail then :grimacing:

2 Likes

one thing that i noticed when going through the pages was that you immediatly go to the example of making a filter rather than first just explaining the basics of the methods in pluginProcessor. that might be a good choice for people who don’t have a lot of patience (like me, lol), but it also means skipping over information that might be useful. questions every beginner asks themselves like:

  • what does scopedDenormals mean?
  • why clearing the buffer?
  • what happens in processBlock if the daw sends an empty buffer (0 samples)?
  • can i go backwards through the sample loop or will the signal be reversed too then?
  • first channels or first samples?

i think it would be good to get that out of the way right at the beginning

2 Likes

Hi All,

Thanks for the Mac advice PluginPenguin. That goes along with other comments about DAW specificity and proves what I suspected all along - I need to make some mention of other toolchains.

Can I go back to the casting of Audio Parameters. I initially used getRawParameterValue() becuase that appears in a couple of existing tutorials. Then I was surprised that it always returns a float; and there weren’t corresponding methods for the other parameter types. So now I’ve tried kamedin’s static cast with all parameter types anad it works (even without get() - just a defereneced pointer).

So to get to the point: is there a single “approved” way to extract parameter values from the ValueTreeState; or can I at least adopt a single “suitable” approach to keep things simple for beginners?

Ollie

1 Like

I think the best way is to keep a pointer to the actual parameter of the actual type using a down cast:

// member
juce::AudioParameterBoolean* switch = nullptr;

// in constructor
switch = dynamic_cast<ToneGeneratorAudioSource*>(apvts.getParameter ("mySwitch"));
jassert (switch); // if this is hit you don't have a parameter with that ID of that type

// can be used like
if (switch.get())

This goes along with @matkatmusic s second point.

The pros:

  • only one dynamic cast per life time
  • returns the value in the correct type/value range
  • the jassert will catch mistakes when refactoring
  • will work with or without AudioProcessorValueTreeState

[edited: added missing asterisk in example]

2 Likes

adding that jassert is clever!!!

1 Like

Thanks daniel & matkatmusic :slight_smile: Is jassert something to teach beginners?

Ollie

1 Like

Yes, they are incredibly handy.
I mention them in my tutorial videos often as they will save your ass more often than you think.

2 Likes

jassertfalse is also incredibly handy, I use them whenever I’ve got into an “error” situation and would show something to the user, pop in one of those so that the debugger will stop execution and I can inspect the state of the application/plugin to understand exactly why the error is being shown (because you don’t necessarily want to show any more details to the user than “there was a problem”).

2 Likes

There are two reasons to teach jassert:

  1. The bigger part of software development is riddling over why things behave differently than expected. To verify assumptions jasserts are extremely handy: "I assume the number of channels is the same:
    jassert (b1.getNumChannels() == b2.getNumChannels());
    If my assumption was wrong I’ll see it asap
  2. Sooner or later the user will hit an assert in the juce code. If they used it themselves they can better understand why it is there and how to read the jassert condition. This condition often tells why it’s there, if there is no comment or the comment is not understandable
6 Likes

This tutorial is really excellent, and one of the best I have come accross. Thank you so much for taking the time to develop it. As a beginner, it really makes sense and is paced just right, as it is such a complicated subject overall (well I think so). Anyways, well done!

1 Like

Hi,

Thanks for the feedback, I really appreciate it :slight_smile: I’m pleased to know that tutorials are good for beginners.

cheers,

Ollie

Just finished this. I’ve actually used Juce for a while but am still a beginner and this was a great way to rehearse. :slightly_smiling_face:

So all in all this is great work but here are a few remarks:

When it comes to the internet pages I found the Explanation links confusing since they kick me out of the context and I’ll have to scroll back remembering where I was. I would prefer a “box” that can be opened/closed with a triangle.

Also the Aims and Topics boxes (which I had very little use of) takes up most of my screen making all pages look the same when jumping between pages, making it harder to find my way. I would say these boxes are not very important and can be made much smaller to get focus back to the actual content.

Also some indication on which chapter I am at in the row of chapters is useful.

And here are some remarks regarding the code and such:

Chapter 2c:

auto myBlock = juce::dsp::AudioBlock<float>(buffer);
auto myContext = juce::dsp::ProcessContextReplacing<float>(myBlock);
myFilter.process(myContext);

I must say I’m a bit puzzled by object instansiations and function calls wrapping the buffer in a Audiblock, wrapped in context and finally handed to the filter. From earlier reading I’ve got the impression functional calls should be kept to a minimum in the processor functions, but I guess this is the way to do this, just curious to hear if there are any other options. Couldn’t all the context stuff go into some initial settings and then only hand the buffer to the filter?

Also, this explanation in the tutorial is a bit strange,
quote:

The Audio Graph is coded with a couple of variables that we declare here. We use the auto variable type which lets the computer decide what data type we need. But we have to declare them here in the .cpp file because we can’t use auto in the header.

This is not only a declaration but actual implementation so it could never be in a pure header-file.

Chapter 3b:
I believe it’s worth mentioning that the use of the (deprecated?) GenericEditor renders PluginEditor.cpp/.h unused. They can be removed from the project. So someone doesn’t spend time editing stuff there wondering why nothing happens.

This is also an excellent place for your second tutorial :grinning: - that would be about building a GUI right?

Chapter4:
I had to add the juce-qualifier here, like:

    myCutoffptr = dynamic_cast<juce::AudioParameterFloat*>(myValueTreeState.getParameter("cutoff"));

Chapter5:
Typo here, should be dynamic_cast

myTypeptr = dynamic_casy<juce::AudioParameterChoice*>

I noticed there’s a discussion regarding cast above, I’ll read it. My initial reaction when seeing this section was wondering if there’s not a better way to do this than casting though. I’m definitely no C++ expert but I though casting in general is something to avoid.