Hi Tim,

Wow, this subject becomes more and more interesting!

Honestly, unlike you, I have no such strong background in midi 
specifications theory, I just started to learn it.
But from your explanation I can make some useful conclusions:
1. For *all* virtual instruments: *always* convert all zero note-ons to 
note-offs before sending it to the instrument. Traffic is not a problem 
here. I read some forums about vst and midi and found that nearly all 
plugins support note-offs, but there is some doubt about how to treat 
zero note-ons. I think this is because developers don't know that this 
is required by midi specs (as me too :) before your explanation).
2. For all external alsa/jack ports we should always optimize note-offs 
to zero note-ons but leave an ability to change this behavior for any 
external port. Thanks to persistent routes support (BTW, it's 
excellent!) we can remember this settings even for absent ports.

What do you think about this, Tim?

P.S. At time of writing this message don't know about official vst and 
dssi policies yet, but (1) should be correct for all cases I hope.

24.12.2015 11:10, Tim E. Real пишет:
> On December 23, 2015 09:30:41 PM Tim E. Real wrote:
>> On December 23, 2015 11:23:20 PM Andrew Deryabin wrote:
>>> Hi All,
>>>
>>> Just pushed new commit, fixing stuck notes. To prove that this patch
>>> should work right, I'll try to explain my steps. May be these results
>>> are completely wrong, but here is the explanation:
>>>
>>> (IMPORTANT: this results are not related to physical midi inputs - when
>>> playing on external midi devices, ME_NOTEON and ME_NOTEOFF are sent
>>> right).
>>>
>>> 1. stuckNotes. I checked the code and decided that this mechanism is
>>> used for shutting down notes that were not explicitly shut off by note
>>> offs messages or by some other means. So the only message that should be
>>> recorded for future post is ME_NOTEOFF (0x80 in midi specs).
>>> 2. The code from midi.cpp made something strange for adding events to
>>> stuck notes array. According to midi specs, not only ME_NOTEON, but also
>>> ME_NOTEOFF can have velocity value. For ME_NOTEOFF velocity describes
>>> how quickly the note should be released (127 - fast release, 0 - long
>>> release). For reference: in MusE all noteoffs from virtual instruments
>>> are sent with velocity value = 0.
>>> 3. From dssihost.cpp I knew that ME_NOTEON message is used in rather
>>> interesting way: if supplied velocity value is zero, ME_NOTEOFF message
>>> is sent instead of ME_NOTEON. lv2host.cpp has the same issue until I
>>> rewrote midi handling code and removed this check. That's why some
>>> instruments were stuck on some notes after this change (ME_NOTEOFF was
>>> not sent at all).
>>> 4. Again about stuck notes. It seems, that velocity for ME_NOTEOFF
>>> message is supported by the code. When ME_NOTEOFF is received by midi
>>> engine, but before it's send to actual midi device, velocity value is
>>> remembered in event's _veloOff variable. When stuck notes array is
>>> filled, this value is used as midi event velocity. But (and this is  a
>>> mystery for me) this velocity value was also used for deciding if
>>> ME_NOTEON (if _veloOff = 0) or ME_NOTEOFF (if _veloOff > 0) was inserted
>>> to stuck notes. The problem was that stuckNotes was filled with
>>> ME_NOTEON and velocity = 0. It was ok for this code (from dssihost.cpp):
>>>
>>> if(b)
>>>
>>>           snd_seq_ev_set_noteon(event, chn, a, b);
>>>         
>>>         else
>>>         
>>>           snd_seq_ev_set_noteoff(event, chn, a, 0);
>>>
>>> where b is the velocity.
>>>
>>> But not ok when this check was not used. As for me, the above code seems
>>> like a hack.
>>>
>>> After forcing ME_NOTEOFF for stuckNotes all things went in a normal way.
>>> To be sure I added code for dumping midi messages sent to lv2 synths to
>>> check if it works right. And it did. Every ME_NOTEON message was closed
>>> with corresponding ME_NOTEOFF. I also added velocity to noteoff message
>>> in lv2 host code to satisfy midi specs.
>>>
>>> P.S. midi_engine_fixes branch also have this problem, but because of
>>> older version of lv2host.cpp (which handles ME_NOTEON with velocity=0 as
>>> ME_NOTEOFF) everything worked right.
>>>
>>> Regards,
>>> Andrew
>> Howdy.
>>
>> Here is the reasoning for this long-time code, Robert and others
>>   might chime in here:
>>
>> First, in MIDI a note on with zero velocity is essentially same as note off.
>>
>> Next, to conserve both MIDI interface bandwidth and MIDI file space,
>>   there is something called 'running status':
>> If the status byte of the next desired message to be sent happens to be
>>   the same as the /last/ status byte that was sent, then this next status
>>   byte can be /omitted/.
>> Note on and note off are two /different/ status bytes, thus it would be
>>   required to include both with /every/ note on - note off message pair.
>>
>> So, for a note off, they just use a note on with zero velocity, so that the
>>   status byte does not change and can be omitted from the steam or file,
>>   even for a whole run of many notes (that's where the savings come in).
>> Many MIDI manufacturers and software developers do this.
>> Refer to MusE Settings -> Midi import/Export and see the export option
>>   "Save space by replacing note offs with zero velocity note ons"
>>
>> Concerning the MusE synthesizers:
>> Perhaps you can now see why that DSSI code was not really a hack
>>   but a final 'decoding' of these 'zero velocity note ons' into 'real'
>>   MIDI note off messages before they are sent to the DSSI synths.
>>
>> That is what happens inside your midi keyboard when it receives zero
>>   velocity note on messages from MusE.
>>
>> And, that is what you should try to do /inside/ MusE's LV2 code.
>>
>> (Although not intimately familiar with the LV2 code, it struck me that
>>   only your LV2 code was having problems and the other synths were OK.)
>>
>> Could we /leave/ them as 0V note ons when they're sent to the synths?
>> I dunno... likely some are OK with it some not. But I think the reasoning
>>   for decoding before being sent might be:
>> "Why not (decode it now)? We've plenty of bandwidth at this level,
>>   and why risk sending 0V note ons to a synth that doesn't like it...",
> I read this at http://sourceforge.net/p/qmidiarp/bugs/11/
>
> "I just found (only now!) that LV2 MIDI specification apparently overrides
>   the NOTE-OFF / NOTE-ON-VELOCITY-ZERO equivalence of the standard
>   MIDI specification, and it states that a NOTE OFF is required..."
>
> So yes, you need to convert zero-velocity note ons to zero-velocity
>   note offs in your LV2 code.
>
>> Could we add an extra Setting (like the export option mentioned above),
>>   which applies to the realtime messages being sent /out/ from MusE,
>>   for users who are not happy with the 0V note ons?
>> Sure, I suppose it might help in some cases.
>> Better than no option for that, eh?
>>
>>
>> Sincerely hope I got that all correct ;-)
>> Tim.
> Awright, grab a coffee :-) see if the following makes sense:
>
> Checking, I do see some inconsistencies in the device code.
>
> I pass events rather verbosely to Jack including note off velocity,
>   but for VST native and DSSI I don't even pass the off velocity !
> Not sure why I did that.
> The DSSI logic looks correct, just not the off value.
> I believe we should apply the same DSSI logic to the other
>   drivers. That is, the if(b) part. Zero velocity note ons should be
>   converted to note offs.
> And in the interest of devices utilizing a non-zero off value,
>   I think that we should probably pass it instead of zero, below.
>
> Jack midi:
> ---------------
>              case ME_NOTEON:
>              case ME_NOTEOFF:
>              case ME_POLYAFTER:
>              case ME_CONTROLLER:
>              case ME_PITCHBEND:
>                    {
>                    unsigned char* p = jack_midi_event_reserve(pb, ft, 3);
>                    if (p == 0)
>                          return false;
>                    p[0] = e.type() | e.channel();
>                    p[1] = e.dataA();
>                    p[2] = e.dataB();
>
> VST Native:
> ------------------
>      case ME_NOTEON:
>        setVstEvent(event, (type | chn) & 0xff, a & 0x7f, b & 0x7f);
>      break;
>      case ME_NOTEOFF:
>        setVstEvent(event, (type | chn) & 0xff, a & 0x7f, 0);
>      break;
>
> DSSI:
> --------------
>      case ME_NOTEON:
>        snd_seq_ev_clear(event);
>        event->queue = SND_SEQ_QUEUE_DIRECT;
>        if(b)
>          snd_seq_ev_set_noteon(event, chn, a, b);
>        else
>          snd_seq_ev_set_noteoff(event, chn, a, 0);
>      break;
>      case ME_NOTEOFF:
>        snd_seq_ev_clear(event);
>        event->queue = SND_SEQ_QUEUE_DIRECT;
>        snd_seq_ev_set_noteoff(event, chn, a, 0);
>      break;
>
>
> Notice in the midi exporter in exportmidi.cpp I did this:
>
> // Save space by replacing note offs with note on velocity 0
> if(MusEGlobal::config.expOptimNoteOffs)
>     mpevlist->add(MusECore::MidiPlayEvent(tick+len, port, channel,
>      MusECore::ME_NOTEON, pitch, 0));
> else
>      mpevlist->add(MusECore::MidiPlayEvent(tick+len, port, channel,
>      MusECore::ME_NOTEOFF, pitch, velo));
>
> That I /know/ is correct. When enabled, there is no off velocity stored,
>   just zero on velocity.
> But we have this in midi.cpp as you've seen:
>
> md->addStuckNote(MusECore::MidiPlayEvent(tick + len, port, channel,
>    veloOff ? MusECore::ME_NOTEOFF : MusECore::ME_NOTEON, pitch, veloOff));
>
> * I think now that may have been too liberal. If we're going to use
>   running status then /all/ note offs must be converted to 0V note ons,
>   regardless of off velocity which is discarded.
>
> I think the scheme is supposed to use no note offs at all, sacrificing
>   off velocities by discarding them.
> Some devices do not even generate or respond to note offs.
>
>
> ***
> Therefore, putting all the above together, I think we need a new
>   MidiInstrument class member: bool useNoteOffs
>
> We will first fix the driver code above to pass the off velocity
>   when note offs are being sent.
>
> Next, the lines in midi.cpp will become like this:
>    md->addStuckNote(MusECore::MidiPlayEvent(tick + len, port, channel,
>      instr->useNoteOffs ? MusECore::ME_NOTEOFF :
>            MusECore::ME_NOTEON, pitch, 0));
>
> And yes, each device including synths will have to honour the
>   zero-velocity note on scheme, always converting to note offs.
> (The if(b) part in DSSI.)
>
> It will be a per-instrument setting, users can edit it.
> But synths are a MidiInstrument class but cannot be edited by the user.
> Synths supply all the information instead.
>
> But here we may have a problem:
>
> As mentioned above, LV2 /clearly/ sets its policy, requiring note offs.
> Good. Do your internal 0V note on to note off conversions and you're OK.
> But what about VST and DSSI?
> Are there clear policies, or is it up to each synth how to use note offs
>   and 0V note ons?
> That would be... a nightmare unless the we can retrieve a
>   parameter for each synth indicating yes or no...
> A global MusE setting or even per-instrument setting would not
>   cover this situation.
>
> Andrew what do you think about all that?
> And what do you know about the VST and DSSI policies?
>
> Thanks for bringing this to the front.
> Hopefully we can correct these problems.
>
> Tim.
>
>
> ------------------------------------------------------------------------------
> _______________________________________________
> Lmuse-developer mailing list
> [email protected]
> https://lists.sourceforge.net/lists/listinfo/lmuse-developer

-- 
Regards,
Andrew


------------------------------------------------------------------------------
_______________________________________________
Lmuse-developer mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/lmuse-developer

Reply via email to