Why aren't there more breaking changes?

I’m working on some stuff related to the MidiMessage and MidiKeyboardState classes, and the decision to use Midi Channel 1 (out of 16) = 1 is a strange one, instead of = 0. I’ve spoken to some other devs about it, and apparently this is a decision that @jules has revealed to have regretted making several times on the forum. So, why hasn’t a change to fix this been made? Where is the general fear of pushing breaking changes to the JUCE repository coming from?

There are other places in the repo where improvements or fixes haven’t been made because it would break too many people’s existing code. but, who cares? Shouldn’t those users be required to fix their code if the improvement results in a better, more stable codebase? What’s the real motivation for not pushing out these “breaking changes” and just leaving old, booby-trapped code as is? They end up going thru the same code-base tune-up whenever they upgrade juce versions, so how is this any different?

1 Like

Maybe the thing you’re not understanding is the difference between a breaking change and a silently breaking change?

We’re actually pretty bullish about pushing breaking changes - i.e. changes where everybody’s code stops compiling, and they have to go and change it to start working again. We probably do more of those than a lot of people would like!

But something like changing our code to treat those MIDI channel integers differently wouldn’t stop anyone’s code compiling, so a change like that would silently mess up the behaviour of actual products, and cause problems with end-users. That’s absolutely unacceptable for a library to do, obviously.

…and I guess there are also “too noisy” changes, where we could rewrite something in a way that would break such a huge amount of code that the annoyance it’d cause to so many people would outweigh the mistake it’s trying to fix. So for example there are a bunch of methods in Component that have always niggled me because they’re not quite right, but can’t justify changing because they’re so widely used.

8 Likes

Some years ago I got somewhat fed up with this midi message base thing, I got tired of endless error prone midiChannel + 1 and midiChannel - 1 arithmetics so I took the bull by its horns and defined a macro in appconfig.h like

#define MIDI_CHANNEL_START_AT_ZERO 1

#ifdef MIDI_CHANNEL_START_AT_ZERO
static const int midiOffset = 0;
#define ASSERT_CHANNEL_RANGE(channel) jassert (channel >= 0 && channel < 16)
#else
static const int midiOffset = 1;
#define ASSERT_CHANNEL_RANGE(channel) jassert (channel > 0 && channel <= 16)
#endif

and updated relevant functions in a few files (midimessage, midikeyboardState midikeyboardComponent and midimessageSequence, didn’t care about any mpe stuff then).

After this update the functions would then typically look like

void MidiKeyboardComponent::setMidiChannel (int midiChannelNumber)
{
    ASSERT_CHANNEL_RANGE(midiChannelNumber);

    if (midiChannel != midiChannelNumber)
    {
        resetAnyKeysInUse();
		  midiChannel = jlimit(midiOffset, 15 + midiOffset, midiChannelNumber);
    }
}

Now I can compile others juce code with MIDI_CHANNEL_START_AT_ZERO undefined and mine with it defined and my perceived quality of life has increased ever since :grinning:

You don’t need to stoop as low as using macros! In the tracktion engine, I added a class called MidiChannel to help avoid confusion. We should probably use the same trick in juce at some point…

I’m currently doing this to deal with it:

struct MidiChannel
{
    explicit MidiChannel(int JUCEChannel) : channel(JUCEChannel - 1) { }
    MidiChannel(const MidiChannel& o) : channel(o.channel) { }
    MidiChannel& operator=(const MidiChannel& o) { channel = o.channel; return *this; }
    int getRawChannel() const { return channel; }
    int getJUCEChannel() const { return channel + 1; }
private:
    int channel;
};

FYI

1 Like

Well, this problem have certainly unleashed a lot of creativity…

2 Likes