How do I get the last midi note pressed?

Hello all, im having a little trouble finding the answer to this so I thought I’d just ask it for a more general use case:

within the process audio loop, how do I get the last midi note pressed and how can I tell when its been lifted?

Im trying to make my own synthesizer basically and I want to do it all mostly by myself (using JUCE only for GUI and the process audio block method). I already have the oscillators done so the next step is obviously triggering them with an envelope

Im having trouble reading through the documentation and looking for forum answers though because a lot of the questions seem geared towards very specific uses, so is there a really basic way of determining when a note is pressed?

Ideally it would be awesome if there were some bindable events like OnNotePressed that I could utilize

xx

In general, you would need to loop through your sample buffer, and check whether a MIDI event exists at each sample position (i.e. loop index). If the MIDI event corresponds to a Note On, that’s the point at which you’d want to trigger envelope.

1 Like

If you really want to go the route of dealing with the MIDI messages yourself and not for example use the Juce Synthesiser class, here’s a very minimal starting point :

void MyAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiBuffer)
{
    for (const MidiMessageMetadata metadata : midiBuffer)
    {
        const auto msg = metadata.getMessage();
        if (msg.isNoteOn())
        {
            DBG("Note on " << msg.getNoteNumber() << " " << msg.getVelocity());
        }
        else if (msg.isNoteOff())
        {
            DBG("Note off " << msg.getNoteNumber() << " " << msg.getVelocity());
        }
    }

(That just prints into the debug output when note on/offs are found in the incoming MIDI buffer.)

I can tell you it’s going to be quite an ordeal to get a synthesiser working from scratch. The code needed to manage the MIDI messages and synth voices can get very involved.

Is there a really good reason you wouldn’t want to use the existing Juce Synthesiser stuff? It’s not perfect, but not that bad either.

1 Like

Thank you!

I mostly just want to do it as practice, learn the absolute fundamentals if you will because ive been in gamedev for 12ish years and have only recently started using pure graphics pipelines, which not only have the concepts broadened my horizons significantly - I also kinda enjoy it more then using premade stuff. The control and learning experience is really awesome

I’d use the raw steinberg sdk but I still want to use JUCE for the time being because they setup a nice little “audio loop” I can work off of and the GUI setup is pretty decent as well

but that leads me to my next question, the synth im making is a VA mono synth, is there a msg.getNoteOrder() or am i going to have to get creative and do something like lowest note priority?

1 Like

OK, doing a proper mono synth is actually a use case the Juce Synthesiser doesn’t cover! (The voice management logic has been designed to only work for a poly synth.)

You have to track/manage the messages and what they do yourself.

Aw sweet my stubbornness actually has a purpose too!

Thank you for the help though. Ive looked up questions on this forum a bunch of times and I have never seen a post you have yet to respond to :rofl:

keep up the good work friend, people 8 years from now will hail you as king

I actually want to implement mono mode in my synth stuff also, but I am waiting for a 3rd party library to get into implementing that instead of doing it myself…It feels to me the mono mode is one of those things that seem simple at first but end up complicated to do in the end. :slight_smile: But I suppose I could look into implementing it myself anyway. Can’t be too difficult to get at least a basic implementation running…

After some experimentation i finally got something working if you’re interested for your own project:

Basically I use a method I learned from scripting in an online game called “roblox” called “debouncing”

within that loop, in the isNoteOn you simply add the note number to a vector

and within the noteoff portion you remove that note number from the vector

then in my project - temporarily - I do a check to see if that vector is empty, if not I set my oscillator volume to 1 else 0 (maybe ill keep this in to mimic the gate feature of the arturia microbrute)

then I do an if(there was a recent change in notes && !noteDebounces.empty())

and from there you can do lowest note priority by looping through the vector and grabbing the lowest note OR last note priority simply grab noteDebounces.back()

from there to convert the note number to frequency I did float frequency = 440.f * pow(2.f, (noteDebounces.back() - 69) / 12.f);

I am awful and this is probably a terrible way of doing it but it works! :yum:

one of the special things to keep in mind when writing audio/midi code is to try to reduce allocations to a minimum, because they can cause audio dropouts from skipping blocks. so in this case it would mean using array instead of vector with some logic that keeps track of how many entries of the array are used rn

while you are at it i’d like to point you to the awesomeness of this term. you can actually put other numbers than 12 there to change the tonal intervals by having a different number of pitches per octave. this is also one of the things you can explore better when you write everything from scratch :slight_smile:

You can pre-reserve space in a vector.

std::vector<int> v;
// in audio code, do this in the constructor or prepareToPlay
v.reserve(128); 
// all these will then work without allocations or deallocations as
// long as the vector doesn't need to resize itself 
// to over 128 elements
for (int i=0;i<100;++i)
  v.push_back(i);
v.clear();
for (int i=0;i<100;++i)
  v.push_back(i);
v.resize(98);
v.erase(v.begin()+64);
v.erase(v.begin()+2);
// oops, now we would get an allocation/deallocation
for (int i=0;i<50;++i)
  v.push_back(i);
1 Like

Since midi can only be on or off, the juce::BigInteger is quite useful here (the juce synthesiser and the juce MidiKeyboardComponent use that as well).
You can check isZero() which gives the same result like empty() on your vector.

yeah, but the moment you decide there’s some sort of max capacity anyway there’s no good reason left for doing it with vector, except if you just want more stuff on the heap to avoid stack overflows

std::array doesn’t have the push_back, insert, erase etc methods and you if you need those operations, you have to write additional code yourself.

let’s just say it’s a style question. when i personally hear vector i think ‘ah yes, situation that needs dynamic allocation’ and when i hear array i think ‘ah yes, situation where i may or may not reach some predefined limit’. but you are right. vector can be used like you said, too