FR: floating-point Midi to Frequency & vice-versa

hi everyone,

This is a minor feature request, but Juce’s built-in MidiMessage::getMidiNoteInHertz() function only takes an int as an argument, so I had to create a custom function to be able to convert midiFloat values to hertz:

static float mtof(const float midiNote) // converts midiPitch to frequency in Hz
	{
		return CONCERT_PITCH_HZ * std::pow(2.0f, ((midiNote - 69.0f) / 12.0f)); // where CONCERT_PITCH_HZ is the global pitch in hz of midiNote 69, usually 440.0
	};

it would also be nice to be able to change the default concert pitch, for alternate tuning systems, etc… maybe a second argument to this function, or a helper function like setConcertPitch()…

it would also be nice to have a built-in JUCE function to do the opposite conversion:

static float ftom(const float inputFreq) // converts frequency in Hz to midiPitch (as a float)
	{
		return 12.0f * log2(inputFreq / CONCERT_PITCH_HZ) + 69.0f;
	};

Apologies if these functions already exist in the JUCE codebase and I just haven’t found them… ¯_(ツ)_/¯

for anyone interested, here’s the full class I created to do this:

class MidiConverter
{
public:
	
	MidiConverter(int initialConcertPitch): concertPitchHz(initialConcertPitch)
	{ };
	
	float mtof(const float midiNote) // converts midiPitch to frequency in Hz
	{
		return concertPitchHz * std::pow(2.0f, ((midiNote - 69.0f) / 12.0f));
	};
	
	float ftom(const float inputFreq) // converts frequency in Hz to midiPitch (as a float)
	{
		return 12.0f * log2(inputFreq / concertPitchHz) + 69.0f;
	};
	
	void setConcertPitchHz(const int newConcertPitch)
	{
		concertPitchHz = newConcertPitch;
	}
	
	int getCurrentConcertPitchHz()
	{
		return concertPitchHz;
	}
	
	
private:
	
	int concertPitchHz;
	
};
3 Likes

Hello friends,

I’ve expanded this little helper class to be able to alter the number of notes per octave, as well as the reference MIDI note being referred to by concertPitchHz.

Here’s the new class:

class PitchConverter
{
public:

	PitchConverter(const int initialConcertPitch, const int initialRootNote, const int initialNotesPerOctave):
	concertPitchHz(initialConcertPitch), rootNote(initialRootNote), notesPerOctave(initialNotesPerOctave)
	{ };

	float mtof(const float midiNote) const // converts midiPitch to frequency in Hz
	{
		jassert(midiNote >= 0.0f && midiNote <= 127.0f);
		return concertPitchHz * std::pow(2.0f, ((midiNote - rootNote) / notesPerOctave));
	};

	int mtof(const int midiNote) const // midiPitch to frequency with integers instead of floats
	{
		jassert(isPositiveAndBelow(midiNote, 128));
		return round(concertPitchHz * std::pow(2.0f, ((midiNote - rootNote) / notesPerOctave)));
	};


	float ftom(const float inputFreq) const // converts frequency in Hz to midiPitch (as a float)
	{
		jassert(inputFreq >= 0);
		return notesPerOctave * log2(inputFreq / concertPitchHz) + rootNote;
	};

	int ftom(const int inputFreq) const // frequency to midiPitch with integers
	{
		jassert(inputFreq >= 0);
		return round(notesPerOctave * log2(inputFreq / concertPitchHz) + rootNote);
	};

	void setConcertPitchHz(const int newConcertPitch) noexcept
	{
		jassert(newConcertPitch >= 0);
		concertPitchHz = newConcertPitch;
	};

	int getCurrentConcertPitchHz() const noexcept { return concertPitchHz; };

	void setNotesPerOctave(const int newNPO) noexcept
	{
		jassert(newNPO > 0);
		notesPerOctave = newNPO;
	};

	int getCurrentNotesPerOctave() const noexcept { return notesPerOctave; };

	void setRootNote(const int newRoot) noexcept
	{
		jassert(newRoot >= 0);
		rootNote = newRoot;
	};

	int getCurrentRootNote() const noexcept { return rootNote; };


private:

	int concertPitchHz; // the frequency in Hz of the root note. Usually 440 in standard Western tuning.

	int rootNote; // the midiPitch that corresponds to concertPitchHz. Usually 69 (A4) in Western standard tuning.

	int notesPerOctave; // the number of notes per octave. Usually 12 in standard Western tuning.

	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PitchConverter)

};

I think I could make this better using type templates… that’s my next C++ topic to learn :grinning:

1 Like

Nice work. Just my 2 pennies:
in times of auto complete names like mtof are really old school and hard to read :slight_smile:

Thanks!

You’re right, I probably should rename these functions… I guess this was my personal hold over from Max MSP’s mtof object :tipping_hand_woman:

I see where it’s coming from, reminds also of C atoi and similar stuff…

1 Like

Was pulling my hair out the other day inverting the midi to frequency mapping lol – thanks for sharing this. It should be in the midi class!

1 Like

of course, I’m glad this is helpful!

I agree, it would be super convenient to have this built-in to Juce, it’s kind of surprising that it’s not…

Another suggestion: I would create a static method taking the concert pitch as second (maybe even default) argument. Reasons:
a) the caller might have stored the concert pitch somewhere anyway, so it can be called without having to access this conversion object or creating it on the fly
b) it can be easier inlined

    float mtof (float midiNote) const // converts midiPitch to frequency in Hz
    {
        return PitchConverter::mtof (midiNote, concertPitchHz);
    }

    static float mtof (float midiNote, float concertPitch) // converts midiPitch to frequency in Hz
	{
		jassert (midiNote >= 0.0f && midiNote <= 127.0f);
		return concertPitchHz * std::pow(2.0f, ((midiNote - rootNote) / notesPerOctave));
	};

If you use 440.0f as default value, you need to pick different names, otherwise it’s ambiguous to the compiler.

And as you might have noticed, I would use the same type for the concert pitch, maybe somebody wants to use 440.5 Hz, and it’s one conversion less in the formulas.

And usually people don’t write const to by-copy values, reasons can be found in Jules’ JUCE Coding Standards

1 Like

This is a good suggestion! I actually started with all of these being only static functions, but for the plugin I’m working on right now it ended up being easier to make calls like

if(pitchConverter.getConcertPitchHz() != newConcertPitch)
{
   //do some stuff
}

so that’s why I had this class store the concert pitch & notes-per-octave internally.

I’ll read into the const thing, this is something I’ve been curious about!

This blog post is a good read:

1 Like

Very interesting, thanks for sending this my way!