Subclass of Synthesizer. What is jassert macro?

I created a Synthesiser subclass and want to change the implementation of some of the methods. With noteOn, everything is fine. But when I copy to my class noteOff method, the error appears:

Error (active) E0265 member "juce::SynthesiserVoice::keyIsDown" (declared at line 273 of "E:\000\MusicProgram\JUCE\modules\juce_audio_basics\synthesisers\juce_Synthesiser.h") is inaccessible NewProject_SharedCode E:\000\MusicProgram\JUCE Projects\NewProject\Source\MySynthesiser.cpp 39

in line:

jassert(!voice->keyIsDown || voice->isSustainPedalDown() == sustainPedalsDown[midiChannel]);

Method code:

void MySynthesiser::noteOff(const int midiChannel, const int midiNoteNumber, const float velocity, const bool allowTailOff)
{
    const ScopedLock sl(lock);

    for (auto* voice : voices)
    {
        if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel(midiChannel))
        {
            if (auto sound = voice->getCurrentlyPlayingSound())
            {
                if (sound->appliesToNote(midiNoteNumber) && sound->appliesToChannel(midiChannel))
                {
                    jassert(!voice->keyIsDown || voice->isSustainPedalDown() == sustainPedalsDown[midiChannel]);

                    voice->setKeyDown(false);

                    if (!(voice->isSustainPedalDown() || voice->isSostenutoPedalDown()))
                        stopVoice(voice, velocity, allowTailOff);
                }
            }
        }
    }
}

What is this jassert? From the description of the macro, I did not understand what it is for and what function it performs.

  1. If it becomes unexecutable in an assembly with debugging disabled, does that mean that I can simply remove this line from my implementation of MySynthesiser and the functionality of the final plugin will not be affected in any way?
  2. Why is he needed then?
  3. Why does he set a condition, but does not have a statement?
  4. Why does the error appear?
  5. Is it possible to fix the error without deleting this line?

I would be very grateful for clarifications.

Full code:

#pragma once

#include <JuceHeader.h>
using namespace juce;

class MySynthesiser : public Synthesiser
{
    void noteOn(const int midiChannel, const int midiNoteNumber, const float velocity) override;
    void noteOff(const int midiChannel, const int midiNoteNumber, const float velocity, const bool allowTailOff) override;
};


#include "MySynthesiser.h"

void MySynthesiser::noteOn(const int midiChannel, const int midiNoteNumber, const float velocity)
{
    const ScopedLock sl(lock);

    for (auto* sound : sounds)
    {
        if (sound->appliesToNote(midiNoteNumber) && sound->appliesToChannel(midiChannel))
        {
            // If hitting a note that's still ringing, stop it first (it could be still playing because of the sustain or sostenuto pedal).
            for (auto* voice : voices)
            {
                DBG("for (auto* voice : voices)");
                if ((voice->getCurrentlyPlayingNote() == midiNoteNumber) && voice->isPlayingChannel(midiChannel) && (voice->getCurrentlyPlayingSound() == sound))
                {
                    stopVoice(voice, 1.0f, true);
                    DBG("stopVoice");
                }
            }

            startVoice(findFreeVoice(sound, midiChannel, midiNoteNumber, isNoteStealingEnabled()), sound, midiChannel, midiNoteNumber, velocity);
        }
    }
}

void MySynthesiser::noteOff(const int midiChannel, const int midiNoteNumber, const float velocity, const bool allowTailOff)
{
    const ScopedLock sl(lock);

    for (auto* voice : voices)
    {
        if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel(midiChannel))
        {
            if (auto sound = voice->getCurrentlyPlayingSound())
            {
                if (sound->appliesToNote(midiNoteNumber) && sound->appliesToChannel(midiChannel))
                {
                    jassert(!voice->keyIsDown || voice->isSustainPedalDown() == sustainPedalsDown[midiChannel]);

                    voice->setKeyDown(false);

                    if (!(voice->isSustainPedalDown() || voice->isSostenutoPedalDown()))
                        stopVoice(voice, velocity, allowTailOff);
                }
            }
        }
    }
}

Yes, pretty much.

jassert will, in debug builds, warn you at runtime if some unexpected condition becomes true. This can be helpful to track down rare bugs. In this case, the assertion is checking that the voice is in the expected state.

If the condition is false, then the program will pause, allowing you to inspect the current state of the program and attempt to debug the issue.

keyIsDown is a private member of SynthesiserVoice. Synthesiser is able to access this private member because it is a friend of SynthesiserVoice. Your custom class is not a friend of SynthesiserVoice, so it cannot access its private data members.

You could change the line to call voice->isKeyDown() instead of voice->keyIsDown.

1 Like

An overwhelmingly detailed answer. Thanks to!

jassert is VERY helpful, and you should use it everywhere you can. When you write a new function, you should always think about assumptions you’re making about the arguments (and what will happen if those assumptions are not met). For example, if a function takes a pointer, can it cope with null pointers? Or if a function takes a numeric value, what happens if the value is outside of the range you expect?

You can use jassert to enforce these assumptions. This can save you a lot of time later on, by reminding you of the promises that you made to yourself. (The alternative is waiting until the program fails and then trying to work out why).

Here are a few examples of the kinds of ways you might want to use jassert:



void accessSomethingInAContainer(int index)
{
    jassert(index < container.size());  // index must be in bounds!

    container.at(index)...
}


void doSomethingWithAPointer(std::shared_ptr<SomeClass> p)
{
    if (p != nullptr)
    {
        ...
    }
    else jassertfalse; // why did you send me a nullptr?
}


void doSomethingToTwoValueTrees(juce::ValueTree a, juce::ValueTree b)
{
    jassert(a.getParent() == b.getParent());    // only call this function on two sibling nodes!

    ...
}

void setValue(float f)
{
    if (f >= 0.0f)
    {
        ...
    }
    else jassertfalse;     // value must be greater than 0!
}

void switchStatement(SomeEnum e)
{
    case (e)
    {
    case SomeEnum::ThisCase: doThis(); break;
    case SomeEnum::ThatCase: doThat(); break;

    default: jassertfalse;  // looks like you added a new enum but forgot to update this function!
    }
}
1 Like