I'm still struggling with a fix for this, so am posting more details and updated code, and following up in detail on orthodox's points and questions, as I suspect a solution is there somewhere.
I'm trying to troubleshoot an issue I'm seeing when feeding CV Note data into the MIDI Out Device from my Rack Extension. When I send a new note that is lower than a previously played note and there are no gaps (rests) between the new note and the previous higher note, the higher note is repeating rather than the lower note being played. This is only occurring on descending notes, and applies to series of notes, so if a sequence of G - F - E - D are played and there no rests in the sequence, the G note will just play 4 times in a row.
Here are examples taken from the Reason Sequencer:
Here's an example of 2 note that work fine. Each note has a brief rest between it and the next note.
Here's an example of a lower note playing before a higher note, with no rest between the two notes. This is also working fine.
Here's an example of a higher note playing before a lower note without a rest between the two notes. This example causes the higher note to play twice rather than playing the lower note.
Here's an example that just plays the highest note 4 times in a row.
If I hook a device between my Rack Extension's CV Out and the MIDI Out's CV In to monitor the value of the Note CV signal going into the Midi Out device from my device, I can SEE the CV signal decrease, so it appears that my device is indeed outputting the note change. If I hook a Player such as Evolution directly to a MIDI Out device, I do get these lower notes to play, but if I connect the Evolution Player to my Rack Extension and then connect my device to the MIDI Out device the lower notes don't play. If I hook a virtual instrument up to my device (such as Subtractor), it plays the lower notes without issues. It just seems to be something going on between my device and the MIDI Out device, which is of course what I'm building my Rack Extension specifically to work with.
To address this, so far I've added a TON of code to track each individual note on and off state and also have started tracking the highest note currently played and the lowest note currently played, and tried setting the CV output to the lowest note, but this has not changed the behavior. I also have code in place that makes sure I'm not acting on "note off" events (where the NoteEvent appears in the diffs list but the Velocity is 0), so any note off events that occur at the same time as a new note on event shouldn't be getting acted upon.
Based on this description, can anyone offer a suggestion on why I'm getting these results?
Here's some code if it is helpful:
Code: Select all
void CMyPlugin::RenderBatch( const TJBox_PropertyDiff iPropertyDiffs[], TJBox_UInt32 iDiffCount ){
HandleNoteStates( iPropertyDiffs, iDiffCount );
CheckForNotesPressed();
HandleGateInCVChanges();
HandleGateOutJack();
}
void CMyPlugin::HandleNoteStates( const TJBox_PropertyDiff iPropertyDiffs[], TJBox_UInt32 iDiffCount ){
bool newNoteIsPressed = false;
int lowNote = 128;
for( TJBox_UInt32 i = 0; i < iDiffCount; ++i ){
if( iPropertyDiffs[ i ].fPropertyRef.fObject == fNoteStates ){
TJBox_NoteEvent noteEvent = JBox_AsNoteEvent( iPropertyDiffs[ i ] );
bool noteIsOn = noteEvent.fVelocity > 0.f;
if( noteIsOn && lowNote > noteEvent.fNoteNumber ){
lowNote = noteEvent.fNoteNumber;
newNoteIsPressed = true;
}
switch( noteEvent.fNoteNumber ){
case 0:
JBox_StoreMOMProperty( fNote0IsPressedPropertyRef, JBox_MakeBoolean( noteEvent.fVelocity > 0.f ) );
break;
case 1:
JBox_StoreMOMProperty( fNote1IsPressedPropertyRef, JBox_MakeBoolean( noteEvent.fVelocity > 0.f ) );
break;
... // YES, THERE ARE 126 MORE CASE STAMENTENTS
}
}
// HERE IS WHERE I SET THE NOTE CV OUT JACK VALUE
if( newNoteIsPressed ) JBox_StoreMOMProperty( fNoteOutCVJackValuePropertyRef, JBox_MakeNumber( lowNote / 127.f ) );
}
void CMyPlugin::CheckForNotesPressed(){
bool notesArePressed = false;
if( JBox_GetBoolean( JBox_LoadMOMProperty( fNote0IsPressedPropertyRef ) ) ) notesArePressed = true;
if( JBox_GetBoolean( JBox_LoadMOMProperty( fNote1IsPressedPropertyRef ) ) ) notesArePressed = true;
... // YES, THERE ARE 126 MORE IF STATEMENTS :-|
JBox_StoreMOMProperty( fNotesArePressedPropertyRef, JBox_MakeBoolean( notesArePressed ) );
}
void CMyPlugin::HandleGateInCVChanges(){
if( !JBox_GetBoolean( JBox_LoadMOMProperty( fGateInConnectedCVInputRef ) ) ) return;
TJBox_Value gateCVValue = JBox_LoadMOMProperty( fGateInValuePropertyRef );
TJBox_Tag gateCV = clamp( JBox_GetNumber( gateCVValue ) * 127.f, 0, 127 );
if( gateCV != fLastGateCV ){
JBox_StoreMOMProperty( fGateInputCVWentHighPropertyRef, JBox_MakeBoolean( gateCV > 0 ) );
fLastGateCV = gateCV;
} else JBox_StoreMOMProperty( fGateInputCVWentHighPropertyRef, JBox_MakeBoolean( false ) );
}
void CMyPlugin::HandleGateOutJack(){
bool gateOutJackIsOn = false;
if( JBox_GetBoolean( JBox_LoadMOMProperty( fGateInputCVWentHighPropertyRef ) ) ) gateOutJackIsOn = true;
if( !gateOutJackIsOn && JBox_GetBoolean( JBox_LoadMOMProperty( fNotesArePressedPropertyRef ) ) ) gateOutJackIsOn = true;
JBox_StoreMOMProperty( fGateOutCVJackValuePropertyRef, JBox_MakeNumber( gateOutJackIsOn ) );
}