How to set polyphonic aftertouch note number


#1

For a MidiMessage object where isAftertouch( ) is true, there doesn't appear to be a way to change the note number. The setNoteNumber() method only works if isNoteOnOrOff(). It looks like the only way is to instantiate a new MidiMessage with the aftertouchChange() factory method. Is there something I'm missing? What's the reason for disallowing setNoteNumber() on polyphonic aftertouch messages?


#2

Good point - I never thought of aftertouch when I wrote the setNoteNumber method - I'll tweak it to also work on aftertouch events.

(The MidiMessage class is generally designed to be used in more of an immutable style though - create the messages in their final form rather than modifying existing ones)


#3

I have this midi keyboard that doesn't send an absolut controller value (between 0 and 127) but merely the change from previous; if i turn the knob slightly clockwise it will send 65, ditto counter clockwise will yield 63. The difference from the centervalue (64) gives the increment from previous value, +1 or -1 in this example. I translate this today with an ugly const_cast, but it would be nice to be able todo do this via a setControllerValue().

 


#4

If this MIDI keyboard is an Arturia Mini Lab, you can change this in the their editor AFAIK.


#5

You don't have to use a const_cast for this kind of thing anyway - you can always just create your new MidiMessage and assign it to the old one. The compiler should do a good job of optimising that, as for small messages, there's no heap allocation involved.


#6

Yes. if i remember to do it every time I switch it on. And if I do, I won't benefit from the joy of relative controllers?


#7

And how would I go about doing that?​ I intercept incoming midi messags in the first processblock via the MidiBuffer param. There's an Midibuffer::addEvent function but no removeEvent AFAIK.


#8

The way MidiBuffers are designed to work is that you iterate them and create new buffers of whatever filtered version you need to. They're specifically not designed to be modifiable in-place.


#9

Yes I've realized they're not supposed to be modifiable in-place. But creating a new MidiBuffer, iterate through the old one, adding every non-controller message to the new buffer and create new Controller messages with the updated controller values seems a bit less optimal compared to just modifying the controllervalues in-place!  


#10

True, but normally there's not much need to do things like that.

If you're going to use the data in this buffer, i.e. if you're doing more than just modifying the controllers, then why modify them at all? You could just handle their values differently later on in whatever code you have that actually uses the controller messages.


#11

I automatically detect if the midi keyboard is in relative or absolute mode in the first processblock of the audioprocessorgraph aka the midiinput. If in relative mode I convert the values to absolute mode once & for all so all midi plugins/equipment further down the stream including any external midi equipment (which I definitively have no internal access to) only have to handle normal/absolute controller values.


#12

With AudioUnits you can make a "midi fx" plug-in where it's just midi in and midi out. So you just process the midi signal and pass it on to be used by another plug-in. It can be done with VST2 too, it's just more cumbersome because you have to treat it like a virtual instrument. But you can still hook up the midi output of a VST to another plug-in.


#13

Check out the Arpeggiator sample code in JUCE/examples/PlugInSamples/Arpeggiator.  This will build as an AudioUnit midi fx plug-in.


#14

Hmm, looks like this thread have diverged from the suggestion of introducing setControllerValue() into something like "How to make a plugin". Although I appreciate the suggestions I think they're missing the point:

Task: To adjust controller values of incoming midi event controller messages in the processblock from relative to absolute mode.

Reason: To let all aftercoming midi effects/plugins/equipment who expects the values being normal controller values from 0..127 benefit from this conversion.

What's missing: a MidiMessage::setControllerValue() to just... update the values w/o much further ado.

Jules suggestion to make a copy of all incoming mesages and put them in a new MidiBuffer will for some messages invoke a memory allocation which is generally considered as bad practice in a time critical audio thread. True, one can certainly skip some of those messages, but then you'd have to introduce some filtering logic to filter out "big" messages, which won't be an easy task if you want the whole affair to be as transparent as possible for all other (non controller) messages. Plus you would have to have a thorough understanding of all possble midi messages. Wouldn't it be a lot easier to just adjust the relevant controller values in situo?

And this have afaik nothing to do if you're implementing it as a plugin or in the daw.

PS @Fabian: Shouldn't acceptsMidi() as well as producesMid() both return true in the Arpeggiator sample?

 


#15

Jules suggestion to make a copy of all incoming mesages and put them in a new MidiBuffer will for some messages invoke a memory allocation which is generally considered as bad practice in a time critical audio thread

No, that's not really true.. The MidiBuffer has a single buffer which contains all its message data, it doesn't heap-allocate individual messages. Of course it does have to heap-allocate to grow its buffer if you push too much into it, but you can usually avoid that by making sure you re-use a MidiBuffer object that has been pre-allocated with a decent amount of storage.


#16

Thanks for the clarification. But considering the extra coding involved for copying the messages and the need to asses the biggest reasonable midi event size(s) for a process block, I'll think I stick to the const_cast method.

Something slightly differen: what about some default values for MidiMessageSequence::addSequence like

void addSequence (const MidiMessageSequence& other,
                      double timeAdjustmentDelta = 0.0,
                      double firstAllowableDestTime = 0.0,
                      double endOfAllowableDestTimes = std::numeric_limits<double>::max());


#17

const_cast is fine in this trivial case, but creating a fitered copy is probably better any time you're adding/removing messages or changing their size or type.

We always try to avoid default parameters where possible.

Besides, that method with default params would be doing a naive merge of two midi sequences, which is generally a bad idea if you want to avoid stuck notes. A more typical use for that method is to append or move a sequence, for which you would need the extra arguments.


#18

Naive? Browsing through my code I'd say half of the addSequence calls look like below and could well use default arguments to give a cleaner appearance:

//merge all tracks to one single slice
midiSequence = *midiFile.getTrack(0);
for (int i = 1; i < midiFile.getNumTracks(); i++)
   midiSequence.addSequence(*midiFile.getTrack(i), 0.0, 0.0, std::numeric_limits<double>::max());

//add the global tempo and time signature events to the midi track
timeAndTempoSigEvents.addSequence(timeSigEvents, 0.0, 0.0, std::numeric_limits<double>::max());

//add all channel-less messages to every new lane
sequences[n].addSequence(sequences[0], 0.0, 0.0, std::numeric_limits<double>::max());

Is there perhaps a less naive way to use addSequence to merge, say, single-channel midi sequences into a multichannel track than using 0.0, 0.0, and std::numeric_limits<double>::max() as the last arguments?

And if I were to add a midi sequence into another I would not use an upper limit becuase then I would surely run the risk of loosing a trailing note-off.

 


#19

Oh, sorry, I misunderstood and thought you were talking about MidiBuffer, not MidiMessageSequence.. Sure, that's different, and yes, that's a totally valid way to use it!

If you promise to never spell "losing" wrong on our forum again, I'll promise to add a version of the method with fewer parameters!


#20

Sorry, not even loosing my religion gets caught in your spell check!