Keyboard hold ... latch .. midi notes


#1

Hi all,
I am working on a midi plugin which stores chord sequences and various playback methods can be triggered with midi notes.
I have implemented :
midi note 60 : reset sequence and play
midi note 61 : play previous chord
midi note 62 : play next chord
midi note 64: play random chord …

I am trying to make 63 work as a “keyboard hold” or “keyboard latch” function.

I set up a boolean which a 63 note on switches to true .

In pseudo code :

I tried

/// if the latch boolean is engaged : 
/// send all notes off manually 
                    for (int i = 0; i < 128; ++i){
                        m = MidiMessage::noteOff(m.getChannel(), i, 0.0f);
                        processedMidi.addEvent(m, time);
                    }

/// then send chord 
                // now play chord at current pointer position
                for ( int i = 0; i < chordSize; i++) {
                    int NewNote = chords[chordsPosition][i];
                    m = MidiMessage::noteOn(m.getChannel(), NewNote, m.getVelocity());
                    processedMidi.addEvent(m, time);
                }

//at the end of all the conditional situations for the notes there is
// swap the buffer
midiMessages.swapWith (processedMidi);


I suspect that the note-offs and note-ons are sitting at the same time position in the buffer . 

I think I need a way to send the note-offs first . 

I tried this 
processedMidi.addEvent(m, time + 10 );
but that didn't help .. 

Has anyone solved this before ? 
I thought of keeping an array of "notes played" 
but I don't think that would help what is a timing problem
I can't think of another solution. 
According to my interpretation of the class documentation it should work : 
https://docs.juce.com/master/classMidiBuffer.html
"If an event is added whose sample position is the same as one or more events already in the buffer, the new event will be placed after the existing ones."
Can anyone shed any light ? 
Thanks Sean Wayland

#2

Probably a sign of madness talking to myself here …
I tried various things like wacky sephamores inside the buffer and played around with sustain pedal messages like this …


void WaylochorderAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
    ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();
    
    
    MidiMessage m;
    
    
    {
        buffer.clear();
        
        
        MidiBuffer processedMidi;
        int time;
        
        for (MidiBuffer::Iterator i (midiMessages); i.getNextEvent (m, time);)
        {

            
            if (m.isNoteOn()){

                m = MidiMessage::controllerEvent(m.getChannel(), 64, 0); // turn off sustain pedal
                for (int i = 0; i < 128; ++i){
                    m = MidiMessage::noteOff(m.getChannel(), i, 0.0f);
                    processedMidi.addEvent(m, time);
                }
                
                
            }
            
            else if (m.isNoteOff()){
                m = MidiMessage::controllerEvent(m.getChannel(), 64, 127); // turn on sustain pedal
                processedMidi.addEvent(m, time);
                m = MidiMessage::noteOn(m.getChannel(), 60, m.getVelocity());
                processedMidi.addEvent(m, time);

            }

             midiMessages.swapWith (processedMidi);

Can’t find a way out of this …

I also simplified things and tried this …
I was hoping with this to make a sort of “monosynth” where notes sustain until the next note is pressed …

It makes no sound …


void WaylochorderAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
    ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();
    
    
    MidiMessage m;
    MidiMessage n;
    
    
    {
        buffer.clear();
        
        
        MidiBuffer processedMidi;
        int time;
        
        for (MidiBuffer::Iterator i (midiMessages); i.getNextEvent (m, time);)
        {

            
            if (m.isNoteOn()){

                
                m = MidiMessage::noteOff(m.getChannel(), lastNote, 0.0f);
                processedMidi.addEvent(m, time);
                
                n = MidiMessage::noteOn(m.getChannel(), m.getNoteNumber(), m.getVelocity());
                processedMidi.addEvent(n, time);
                
                lastNote = m.getNoteNumber();
                
            }
            
            else if (m.isNoteOff()){
                
            }
            
            else {}

             midiMessages.swapWith (processedMidi);

I don’t understand why this doesn’t work either …


#3

I think you are overcomplicating things. When processblock starts (and it will start every couple of milliseconds) it wil first silence all notes. they you creatd your chord - but the next call of processblock will immediately silence them.

You need something like this (pseudocode)

// this goes in the private part of the processor code
bool amIOutputtingAChordYet = false;

// this in your loop over all midi events
if (m.isNoteOff() 
{
     silenceEverything();
     amIOutputtingAChordYet = false;
}
else if (m.isNoteOn()) {
   if (!amIOutputtingAChordYet)
   {
       outputTheChord();
       amIOutputtingAChordYet = true;
   }
}

The boolean needs to be defined globally (not in processBlock() but in the processor itself). You need it because otherwise, if you hit a 3-note chord you would be outputting your chord 3 times.

Hope that helps.


#4

Thanks Tomto,
I thought some sort of sephamore like this was the solution.
I don’t want the note-offs to send all note offs that’s kind of the point of it. I want a note-on to silence all notes and then send the next chord …
I will keep thinking. Something like what you have suggested will probably do the trick .
Thanks Sean


#5

Still thinking about this …
I think the solution might be to send the note offs inside the midibuffer::iterator and then use the boolean outside the iterator to send the noteOn messages the next time the processBlock starts.

like this :

bool notesToSend = false; // declared globally

processblock()

if (notesToSend) { 
at NoteOns to MidiBuffer processedMidi;
midiMessages.swapWith (processedMidi);
}

MidiBuffer::Iterator()
{
if m is noteOn {
allNotesOff(); // add noteOffs to processedMidi
notesToSend = True }
midiMessages.swapWith (processedMidi);
}

Tricky one for me to solve.
I still don’t get why just sending a note on straight after the note off doesn’t work. Is it possible the DAW has the midi buffer on more than one thread and the note on might arrive first ?? That seems impossible

I tried this too …
The result was a midi note being sent and then cut off immediately. It did make a sound. It’s as if the DAW is receiving them at the same time. I tried reversing the order but it didn’t help.

        for (MidiBuffer::Iterator i (midiMessages); i.getNextEvent (m, time);)
        {

            
            if (m.isNoteOn()){

                for (int i = 0; i < 128; ++i){
                    m = MidiMessage::noteOff(m.getChannel(), i, 0.0f);
                    midiMessages.addEvent(m, time);
                }
                
                m = MidiMessage::noteOn(m.getChannel(), m.getNoteNumber(), m.getVelocity());
                midiMessages.addEvent(m, time);
            
            }
            else if (m.isNoteOff()){
            }
            else {}
            

Sean


#6

Hi all,
I try different solutions every day with no luck.
I tried to create a version of @tomto66 sephamore which doesn’t work. It makes no sound at all …

void WaylochorderAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
    ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();

    MidiMessage m;
    {
        buffer.clear();
        MidiBuffer processedMidi;
        int time;
        for (MidiBuffer::Iterator i (midiMessages); i.getNextEvent (m, time);)
        {
           
            
            if (m.isNoteOn())
            {
                for (int i = 0; i < 128; ++i){
                    m = MidiMessage::noteOff(m.getChannel(), i, 0.0f);
                    processedMidi.addEvent(m, time);
                }
                amIOutputtingAChordYet = false;
            }
                else if (m.isNoteOff()) {
                    if (!amIOutputtingAChordYet)
                    {
                        m = MidiMessage::noteOn(m.getChannel(), m.getNoteNumber(), 0.0f);
                        processedMidi.addEvent(m, time);
                        amIOutputtingAChordYet = true;
                    }
                }
            
            else if (m.isAftertouch())
            {
            }
            else if (m.isPitchWheel())
            {
            }
            
            // processedMidi.addEvent (m, time);
        }
        midiMessages.swapWith (processedMidi);
    }

I wish I could figure out something that worked !
Sean


#7

You never initialize amIOutputtingYet to false so it’s logical it does’t generate output (your if (!amIOutputtingAChordYet will always fail and your output generating code never runs). Also, are you sure about your logic? You now silence all notes when you get a noteOn - should it not be the other way around?

Really, these are very basic coding mistakes. IMHO you’d be better off learning to program simple things in C++ before trying to build a plugin. Just my 2cts; no offense intended.


#8

Thanks Tomto its declared false globally. I have written a lot of C++ and C . Do you know what keyboard latch or keyboard hold does ? Thats what I am trying to achieve. A sustain pedal affect which ignores note offs and note ons kill remaining notes . I suspect you dont understand what I want here . This thread has a description of various versions of it. https://www.kvraudio.com/forum/viewtopic.php?t=169942
I have build over 10 midi plugins in C++
It’s not a programming issue I can’t figure out how the juce midibuffer is really working.
https://github.com/seanwayland?tab=repositories

Best Sean