2525#include < cassert>
2626#include < cmath>
2727#include < cstdlib>
28+ #include < list>
2829
2930#include < core/AudioEngine/AudioEngine.h>
3031#include < core/AudioEngine/TransportPosition.h>
@@ -172,26 +173,10 @@ void Sampler::process( uint32_t nFrames )
172173 }
173174
174175 // Only send Note-Off messages in case we already sent an Note-On.
175- if ( pNote->getMidiNoteOnSentFrame () != -1 ) {
176- // Ensure notes of custom length result in Note-On and Note-Off
177- // messages corresponding to the user-defined length (regardless
178- // of the underlying sample).
179- if ( pNote->getLength () != LENGTH_ENTIRE_SAMPLE ) {
180- const auto nPrevStart = pNote->getNoteStart ();
181- pNote->setMidiNoteOffFrame (
182- pNote->getMidiNoteOnSentFrame () +
183- TransportPosition::computeFrame (
184- pNote->getLength (), Hydrogen::get_instance ()
185- ->getAudioEngine ()
186- ->getTransportPosition ()
187- ->getTickSize ()
188- )
189- );
190- m_scheduledNoteOffQueue.push ( pNote );
191- }
192- else {
193- m_queuedNoteOffs.push_back ( pNote );
194- }
176+ // Notes of custom length will be handled using the scheduled queue.
177+ if ( pNote->getMidiNoteOnSentFrame () != -1 &&
178+ pNote->getLength () == LENGTH_ENTIRE_SAMPLE ) {
179+ m_queuedNoteOffs.push_back ( pNote );
195180 }
196181 }
197182 else if ( pNote == nullptr ) {
@@ -252,13 +237,13 @@ void Sampler::process( uint32_t nFrames )
252237 nCurrentFrame
253238 );
254239#if SAMPLER_DEBUG
255- INFOLOG ( QString ( " nCurrentFrame: [%1], Sending "
240+ INFOLOG ( QString ( " nCurrentFrame: [%1], Queuing "
256241 " immediate Note-Off [%2] for [%3]" )
257242 .arg ( nCurrentFrame )
258243 .arg ( midiMessage.toQString () )
259244 .arg ( pNote->toQString () ) );
260245#endif
261- pMidiDriver-> enqueueOutputMessage ( midiMessage );
246+ m_midiMessageQueue. push ( std::move ( midiMessage ) );
262247 }
263248 }
264249 else if ( pNote == nullptr ||
@@ -300,7 +285,7 @@ void Sampler::process( uint32_t nFrames )
300285
301286 if ( !sendNote ( pNote ) ) {
302287#if SAMPLER_DEBUG
303- INFOLOG ( QString ( " nCurrentFrame: [%1], Dropping queued "
288+ INFOLOG ( QString ( " nCurrentFrame: [%1], Dropping scheduled "
304289 " Note-Off for [%2]" )
305290 .arg ( nCurrentFrame )
306291 .arg ( pNote->toQString () ) );
@@ -336,13 +321,13 @@ void Sampler::process( uint32_t nFrames )
336321 nCurrentFrame
337322 );
338323#if SAMPLER_DEBUG
339- INFOLOG ( QString ( " nCurrentFrame: [%1], Sending "
340- " queued Note-Off [%2] for [%3]" )
324+ INFOLOG ( QString ( " nCurrentFrame: [%1], Queuing "
325+ " scheduled Note-Off [%2] for [%3]" )
341326 .arg ( nCurrentFrame )
342327 .arg ( midiMessage.toQString () )
343328 .arg ( pNote->toQString () ) );
344329#endif
345- pMidiDriver-> enqueueOutputMessage ( midiMessage );
330+ m_midiMessageQueue. push ( std::move ( midiMessage ) );
346331 }
347332 }
348333 else {
@@ -352,6 +337,8 @@ void Sampler::process( uint32_t nFrames )
352337 }
353338 }
354339
340+ processMidiEvents ();
341+
355342 processPlaybackTrack ( nFrames );
356343}
357344
@@ -838,23 +825,6 @@ bool Sampler::handleNote( std::shared_ptr<Note> pNote, unsigned nBufferSize )
838825 if ( !pNote->isPartiallyRendered () ) {
839826 long long nNoteStartInFrames = pNote->getNoteStart ();
840827
841- #if SAMPLER_DEBUG
842- DEBUGLOG (
843- QString ( " nCurrentFrame: %1, note pos: %2, "
844- " pAudioEngine->getTransportPosition()->getTickSize(): %3, "
845- " pAudioEngine->getTransportPosition()->getTick(): %4, "
846- " pAudioEngine->getTransportPosition()->getFrame(): %5, "
847- " nNoteStartInFrames: %6 " )
848- .arg ( nCurrentFrame )
849- .arg ( pNote->getPosition () )
850- .arg ( pAudioEngine->getTransportPosition ()->getTickSize () )
851- .arg ( pAudioEngine->getTransportPosition ()->getTick () )
852- .arg ( pAudioEngine->getTransportPosition ()->getFrame () )
853- .arg ( nNoteStartInFrames )
854- .append ( pNote->toQString ( " " , true ) )
855- );
856- #endif
857-
858828 if ( nNoteStartInFrames > nCurrentFrame ) {
859829 // The note doesn't start right at the beginning of the
860830 // buffer rendered in this cycle.
@@ -1052,7 +1022,7 @@ bool Sampler::handleNote( std::shared_ptr<Note> pNote, unsigned nBufferSize )
10521022 // We delay checking for the audio driver till here in order to allow
10531023 // usign Hydrogen in "MIDI-only" mode.
10541024 if ( pLayer == nullptr || pHydrogen->getAudioDriver () == nullptr ||
1055- bIsMuted ) {
1025+ bIsMuted && pNote-> getLength () == LENGTH_ENTIRE_SAMPLE ) {
10561026 // For note with neither custom length nor a backing sample, we will
10571027 // send a Note-Off immediately after its Note-On. But we have to
10581028 // watch out for notes associated with multi-component instruments
@@ -1089,48 +1059,55 @@ bool Sampler::handleNote( std::shared_ptr<Note> pNote, unsigned nBufferSize )
10891059 pNote->getLength () != LENGTH_ENTIRE_SAMPLE ) ) ) {
10901060 auto noteOffMessage = MidiMessage::from ( noteOnMessage );
10911061 noteOffMessage.setType ( MidiMessage::Type::NoteOff );
1092- noteOffMessage.setFrameOffset ( std::max (
1093- nInitialBufferPos - 1 , static_cast <long long >( 0 )
1094- ) );
1062+ noteOffMessage.setFrameOffset ( nInitialBufferPos );
10951063
10961064#if SAMPLER_DEBUG
1097- INFOLOG ( QString ( " nCurrentFrame: [%1], Sending "
1065+ INFOLOG ( QString ( " nCurrentFrame: [%1], Queuing "
10981066 " auto-stop Note-Off [%2] for [%3]" )
10991067 .arg ( nCurrentFrame )
11001068 .arg ( noteOffMessage.toQString () )
11011069 .arg ( pNote->toQString () ) );
11021070#endif
11031071
1104- pHydrogen->getMidiDriver ()->enqueueOutputMessage ( noteOffMessage
1105- );
1106-
1107- if ( nInitialBufferPos == 0 ) {
1108- // In case the new note is located at the very beginning of
1109- // the new buffer we deliberately delay it for one frame. As
1110- // such the MIDI output is not as precise as it could be.
1111- // But we need to avoid spurious reordering of messages send
1112- // at the same time stamp. Else the Note-Off could be
1113- // handled _after_ our Note-On.
1114- noteOnMessage.setFrameOffset ( 1 );
1115- pNote->setMidiNoteOnSentFrame ( nCurrentFrame + 1 );
1116- }
1117- else {
1118- pNote->setMidiNoteOnSentFrame ( nCurrentFrame );
1119- }
1120- }
1121- else {
1122- pNote->setMidiNoteOnSentFrame ( nCurrentFrame );
1072+ m_midiMessageQueue.push ( std::move ( noteOffMessage ) );
11231073 }
11241074
1075+ pNote->setMidiNoteOnSentFrame ( nCurrentFrame + nInitialBufferPos );
1076+
11251077#if SAMPLER_DEBUG
1126- INFOLOG ( QString ( " nCurrentFrame: [%1], Sending "
1078+ INFOLOG ( QString ( " nCurrentFrame: [%1], Queuing "
11271079 " Note-On [%2] for [%3]" )
11281080 .arg ( nCurrentFrame )
11291081 .arg ( noteOnMessage.toQString () )
11301082 .arg ( pNote->toQString () ) );
11311083#endif
11321084
1133- pHydrogen->getMidiDriver ()->enqueueOutputMessage ( noteOnMessage );
1085+ m_midiMessageQueue.push ( std::move ( noteOnMessage ) );
1086+
1087+ // Ensure notes of custom length result in Note-On and Note-Off
1088+ // messages corresponding to the user-defined length (regardless
1089+ // of the underlying sample).
1090+ if ( pNote->getLength () != LENGTH_ENTIRE_SAMPLE ) {
1091+ const auto nPrevStart = pNote->getNoteStart ();
1092+ pNote->setMidiNoteOffFrame (
1093+ nCurrentFrame + nInitialBufferPos +
1094+ TransportPosition::computeFrame (
1095+ pNote->getLength (), Hydrogen::get_instance ()
1096+ ->getAudioEngine ()
1097+ ->getTransportPosition ()
1098+ ->getTickSize ()
1099+ )
1100+ );
1101+
1102+ #if SAMPLER_DEBUG
1103+ INFOLOG ( QString ( " nCurrentFrame: [%1], Scheduling "
1104+ " a Note-Off for [%2]" )
1105+ .arg ( nCurrentFrame )
1106+ .arg ( pNote->toQString () ) );
1107+ #endif
1108+
1109+ m_scheduledNoteOffQueue.push ( pNote );
1110+ }
11341111 }
11351112 }
11361113
@@ -1340,6 +1317,65 @@ void resample(
13401317 }
13411318}
13421319
1320+ void Sampler::processMidiEvents ()
1321+ {
1322+ auto pMidiDriver = Hydrogen::get_instance ()->getMidiDriver ();
1323+ if ( pMidiDriver == nullptr ) {
1324+ return ;
1325+ }
1326+
1327+ // We only need to deduplicate and reorder messages of the same frame
1328+ // offset. When arranging Note-Off and Note-On events, the particular order
1329+ // of the Note-Off/On events is not important. We only need to ensure to
1330+ // send Note-Offs _prior_ to Note-Ons.
1331+ auto sendMessages = [&]( std::list<MidiMessage>& messageList ) {
1332+ for ( ; !messageList.empty (); messageList.pop_front () ) {
1333+ pMidiDriver->enqueueOutputMessage ( std::move ( messageList.front () )
1334+ );
1335+ }
1336+ };
1337+ std::list<MidiMessage> messagesPerTick;
1338+ int nCurrentFrameOffset = -1 ;
1339+ for ( ; !m_midiMessageQueue.empty (); m_midiMessageQueue.pop () ) {
1340+ const auto message = std::move ( m_midiMessageQueue.top () );
1341+
1342+ if ( message.getFrameOffset () != nCurrentFrameOffset ) {
1343+ nCurrentFrameOffset = message.getFrameOffset ();
1344+ if ( !messagesPerTick.empty () ) {
1345+ sendMessages ( messagesPerTick );
1346+ }
1347+ }
1348+
1349+ for ( const auto & qqueuedMessage : messagesPerTick ) {
1350+ if ( qqueuedMessage == message ) {
1351+ // Already present - deduplication. E.g. when a custom length
1352+ // tail touches a note head and auto-stop notes is enabled for
1353+ // that instrument.
1354+
1355+ #if SAMPLER_DEBUG
1356+ INFOLOG ( QString ( " Dropped during deduplication: [%1]" )
1357+ .arg ( message.toQString () ) );
1358+ #endif
1359+
1360+ continue ;
1361+ }
1362+ }
1363+
1364+ // We push all Note-Offs to the front and all Note-Ons to the back.
1365+ // This provides us with sufficient ordering when sending them.
1366+ if ( message.getType () == MidiMessage::Type::NoteOff ) {
1367+ messagesPerTick.push_front ( std::move ( message ) );
1368+ }
1369+ else {
1370+ messagesPerTick.push_back ( std::move ( message ) );
1371+ }
1372+ }
1373+
1374+ if ( !messagesPerTick.empty () ) {
1375+ sendMessages ( messagesPerTick );
1376+ }
1377+ }
1378+
13431379bool Sampler::processPlaybackTrack ( int nBufferSize )
13441380{
13451381 Hydrogen* pHydrogen = Hydrogen::get_instance ();
@@ -1602,6 +1638,7 @@ bool Sampler::renderNote(
16021638 pSelectedLayerInfo->fSamplePosition ) /
16031639 fFrequencyRatio
16041640 );
1641+ const int nNoteOffFrame = std::min ( nRemainingFrames, nBufferSize - 1 );
16051642
16061643 bool bRetValue = true ; // the note is ended
16071644 int nAvail_bytes;
@@ -1824,11 +1861,12 @@ bool Sampler::renderNote(
18241861 // Since the last portion of the layers's sample is rendered in this
18251862 // processing cycle, we store the corresponding frame in order to send
18261863 // MIDI Note-Off notes as precisely as possible.
1827- if ( pNote->getMidiNoteOffFrame () < nCurrentFrame + nRemainingFrames ) {
1864+ if ( pNote->getLength () == LENGTH_ENTIRE_SAMPLE &&
1865+ pNote->getMidiNoteOffFrame () < nCurrentFrame + nNoteOffFrame ) {
18281866 // For notes corresponding to instruments holding multiple
18291867 // components, we send a Note-Off after all of them have been
18301868 // rendered.
1831- pNote->setMidiNoteOffFrame ( nCurrentFrame + nRemainingFrames );
1869+ pNote->setMidiNoteOffFrame ( nCurrentFrame + nNoteOffFrame );
18321870 }
18331871 }
18341872
0 commit comments