42#include <core/config.h>
47#ifdef H2CORE_HAVE_JACK
54 auto pCoreActionController = pHydrogen->getCoreActionController();
55 auto pAE = pHydrogen->getAudioEngine();
57 pCoreActionController->activateTimeline(
true );
58 pCoreActionController->addTempoMarker( 0, 120 );
59 pCoreActionController->addTempoMarker( 3, 100 );
60 pCoreActionController->addTempoMarker( 5, 40 );
61 pCoreActionController->addTempoMarker( 7, 200 );
63 auto checkFrame = [](
long long nFrame,
double fTolerance ) {
67 const long long nFrameCheck =
70 if ( nFrameCheck != nFrame || std::abs( fTickMismatch ) > fTolerance ) {
72 QString(
"[testFrameToTickConversion::checkFrame] nFrame: %1, fTick: %2, nFrameComputed: %3, fTickMismatch: %4, frame diff: %5, fTolerance: %6" )
73 .arg( nFrame ).arg( fTick, 0,
'E', -1 ).arg( nFrameCheck )
74 .arg( fTickMismatch, 0,
'E', -1 ).arg( nFrameCheck - nFrame )
75 .arg( fTolerance, 0,
'E', -1 ) );
78 checkFrame( 342732, 1e-10 );
79 checkFrame( 1037223, 1e-10 );
80 checkFrame( 453610333722, 1e-6 );
82 auto checkTick = [](
double fTick,
double fTolerance ) {
84 const long long nFrame =
87 const double fTickCheck =
90 if ( abs( fTickCheck - fTick ) > fTolerance ) {
92 QString(
"[testFrameToTickConversion::checkTick] nFrame: %1, fTick: %2, fTickComputed: %3, fTickMismatch: %4, tick diff: %5, fTolerance: %6" )
93 .arg( nFrame ).arg( fTick, 0,
'E', -1 ).arg( fTickCheck, 0,
'E', -1 )
94 .arg( fTickMismatch, 0,
'E', -1 ).arg( fTickCheck - fTick, 0,
'E', -1 )
95 .arg( fTolerance, 0,
'E', -1 ));
98 checkTick( 552, 1e-9 );
99 checkTick( 1939, 1e-9 );
100 checkTick( 534623409, 1e-6 );
101 checkTick( pAE->m_fSongSizeInTicks * 3, 1e-9 );
106 auto pSong = pHydrogen->getSong();
108 auto pCoreActionController = pHydrogen->getCoreActionController();
109 auto pAE = pHydrogen->getAudioEngine();
110 auto pTransportPos = pAE->getTransportPosition();
111 auto pQueuingPos = pAE->m_pQueuingPosition;
113 pCoreActionController->activateTimeline(
false );
114 pCoreActionController->activateLoopMode(
true );
119 std::random_device randomSeed;
120 std::default_random_engine randomEngine( randomSeed() );
121 std::uniform_int_distribution<int> frameDist( 1, pPref->m_nBufferSize );
122 std::uniform_real_distribution<float> tempoDist(
MIN_BPM,
MAX_BPM );
132 double fCheckTick, fLastTickIntervalEnd;
133 long long nCheckFrame, nLastTransportFrame, nTotalFrames, nLastLookahead;
134 long nLastQueuingTick;
137 auto resetVariables = [&]() {
138 nLastTransportFrame = 0;
139 nLastQueuingTick = 0;
140 fLastTickIntervalEnd = 0;
147 const int nMaxCycles =
148 std::max( std::ceil(
static_cast<double>(pAE->m_fSongSizeInTicks) /
149 static_cast<double>(pPref->m_nBufferSize) *
150 static_cast<double>(pTransportPos->getTickSize()) * 4.0 ),
151 static_cast<double>(pAE->m_fSongSizeInTicks) );
153 while ( pTransportPos->getDoubleTick() <
154 pAE->getSongSizeInTicks() ) {
155 nFrames = frameDist( randomEngine );
157 "testTransportProcessing : song mode : constant tempo", nFrames,
158 &nLastLookahead, &nLastTransportFrame, &nTotalFrames,
159 &nLastQueuingTick, &fLastTickIntervalEnd,
true );
162 if ( nn > nMaxCycles ) {
164 QString(
"[testTransportProcessing] [song mode : constant tempo] end of the song wasn't reached in time. pTransportPos->getFrame(): %1, pTransportPos->getDoubleTick(): %2, pTransportPos->getTickSize(): %3, pAE->getSongSizeInTicks(): %4, nMaxCycles: %5" )
165 .arg( pTransportPos->getFrame() )
166 .arg( pTransportPos->getDoubleTick(), 0,
'f' )
167 .arg( pTransportPos->getTickSize(), 0,
'f' )
168 .arg( pAE->getSongSizeInTicks(), 0,
'f' )
169 .arg( nMaxCycles ) );
180 pCoreActionController->activateLoopMode(
false );
185 while ( nn <= nMaxCycles ) {
186 nFrames = frameDist( randomEngine );
187 pAE->incrementTransportPosition( nFrames );
189 if ( pAE->isEndOfSongReached( pAE->m_pTransportPosition ) ) {
191 if ( pTransportPos->getTick() < pAE->getSongSizeInTicks() ) {
193 QString(
"[testTransportProcessing] [song mode : no looping] final tick was not reached at song end. pTransportPos->getTick: [%1], pAE->getSongSizeInTicks: %2" )
194 .arg( pTransportPos->getTick() ).arg( pAE->getSongSizeInTicks() ) );
200 if ( nn > nMaxCycles ) {
202 QString(
"[testTransportProcessing] [song mode : no looping] end of the song wasn't reached in time. pTransportPos->getFrame(): %1, pTransportPos->getDoubleTick(): %2, pTransportPos->getTickSize(): %3, pAE->getSongSizeInTicks(): %4, nMaxCycles: %5" )
203 .arg( pTransportPos->getFrame() )
204 .arg( pTransportPos->getDoubleTick(), 0,
'f' )
205 .arg( pTransportPos->getTickSize(), 0,
'f' )
206 .arg( pAE->getSongSizeInTicks(), 0,
'f' )
207 .arg( nMaxCycles ) );
217 pCoreActionController->activateLoopMode(
true );
225 float fLastBpm = pTransportPos->getBpm();
227 const int nCyclesPerTempo = 11;
228 while ( pTransportPos->getDoubleTick() <
229 pAE->getSongSizeInTicks() ) {
231 fBpm = tempoDist( randomEngine );
232 pAE->setNextBpm( fBpm );
233 pAE->updateBpmAndTickSize( pTransportPos );
234 pAE->updateBpmAndTickSize( pQueuingPos );
238 for (
int cc = 0; cc < nCyclesPerTempo; ++cc ) {
239 nFrames = frameDist( randomEngine );
241 QString(
"testTransportProcessing : song mode : variable tempo %1->%2" )
242 .arg( fLastBpm, 0,
'f' ).arg( fBpm, 0,
'f' ), nFrames, &nLastLookahead,
243 &nLastTransportFrame, &nTotalFrames, &nLastQueuingTick,
244 &fLastTickIntervalEnd,
true );
250 if ( nn > nMaxCycles ) {
252 "[testTransportProcessing] [song mode : variable tempo] end of the song wasn't reached in time." );
260 pCoreActionController->activateSongMode(
false );
267 fLastBpm = pTransportPos->getBpm();
269 const int nDifferentTempos = 10;
270 for (
int tt = 0; tt < nDifferentTempos; ++tt ) {
272 fBpm = tempoDist( randomEngine );
274 pAE->setNextBpm( fBpm );
275 pAE->updateBpmAndTickSize( pTransportPos );
276 pAE->updateBpmAndTickSize( pQueuingPos );
280 for (
int cc = 0; cc < nCyclesPerTempo; ++cc ) {
281 nFrames = frameDist( randomEngine );
283 QString(
"testTransportProcessing : pattern mode : variable tempo %1->%2" )
284 .arg( fLastBpm ).arg( fBpm ), nFrames, &nLastLookahead,
285 &nLastTransportFrame, &nTotalFrames, &nLastQueuingTick,
286 &fLastTickIntervalEnd,
true );
294 pCoreActionController->activateSongMode(
true );
299 auto pSong = pHydrogen->getSong();
300 auto pTimeline = pHydrogen->getTimeline();
302 auto pCoreActionController = pHydrogen->getCoreActionController();
303 auto pAE = pHydrogen->getAudioEngine();
304 auto pTransportPos = pAE->getTransportPosition();
305 auto pQueuingPos = pAE->m_pQueuingPosition;
307 pCoreActionController->activateLoopMode(
true );
313 auto activateTimeline = [&](
bool bEnabled ) {
314 pPref->setUseTimelineBpm( bEnabled );
315 pSong->setIsTimelineActivated( bEnabled );
318 pTimeline->activate();
320 pTimeline->deactivate();
323 pAE->handleTimelineChange();
325 activateTimeline(
true );
327 std::random_device randomSeed;
328 std::default_random_engine randomEngine( randomSeed() );
329 std::uniform_int_distribution<int> frameDist( 1, pPref->m_nBufferSize );
330 std::uniform_real_distribution<float> tempoDist(
MIN_BPM,
MAX_BPM );
340 double fCheckTick, fLastTickIntervalEnd;
341 long long nCheckFrame, nLastTransportFrame, nTotalFrames, nLastLookahead;
342 long nLastQueuingTick;
345 auto resetVariables = [&]() {
346 nLastTransportFrame = 0;
347 nLastQueuingTick = 0;
348 fLastTickIntervalEnd = 0;
355 const int nMaxCycles =
356 std::max( std::ceil(
static_cast<double>(pAE->m_fSongSizeInTicks) /
357 static_cast<double>(pPref->m_nBufferSize) *
358 static_cast<double>(pTransportPos->getTickSize()) * 4.0 ),
359 static_cast<double>(pAE->m_fSongSizeInTicks) );
361 while ( pTransportPos->getDoubleTick() <
362 pAE->getSongSizeInTicks() ) {
363 nFrames = frameDist( randomEngine );
365 QString(
"[testTransportProcessingTimeline : song mode : all timeline]" ),
366 nFrames, &nLastLookahead, &nLastTransportFrame, &nTotalFrames,
367 &nLastQueuingTick, &fLastTickIntervalEnd,
false );
370 if ( nn > nMaxCycles ) {
372 QString(
"[testTransportProcessingTimeline] [all timeline] end of the song wasn't reached in time. pTransportPos->getFrame(): %1, pTransportPos->getDoubleTick(): %2, pTransportPos->getTickSize(): %3, pAE->getSongSizeInTicks(): %4, nMaxCycles: %5" )
373 .arg( pTransportPos->getFrame() )
374 .arg( pTransportPos->getDoubleTick(), 0,
'f' )
375 .arg( pTransportPos->getTickSize(), 0,
'f' )
376 .arg( pAE->getSongSizeInTicks(), 0,
'f' )
377 .arg( nMaxCycles ) );
388 float fLastBpm = pTransportPos->getBpm();
390 const int nCyclesPerTempo = 11;
391 while ( pTransportPos->getDoubleTick() <
392 pAE->getSongSizeInTicks() ) {
396 activateTimeline(
false );
397 fBpm = tempoDist( randomEngine );
398 pAE->setNextBpm( fBpm );
399 pAE->updateBpmAndTickSize( pTransportPos );
400 pAE->updateBpmAndTickSize( pQueuingPos );
402 sContext =
"no timeline";
405 activateTimeline(
true );
408 sContext =
"timeline";
411 for (
int cc = 0; cc < nCyclesPerTempo; ++cc ) {
412 nFrames = frameDist( randomEngine );
414 QString(
"testTransportProcessing : alternating timeline : bpm %1->%2 : %3" )
415 .arg( fLastBpm ).arg( fBpm ).arg( sContext ),
416 nFrames, &nLastLookahead, &nLastTransportFrame, &nTotalFrames,
417 &nLastQueuingTick, &fLastTickIntervalEnd,
false );
423 if ( nn > nMaxCycles ) {
425 "[testTransportProcessingTimeline] [alternating timeline] end of the song wasn't reached in time." );
435 auto pSong = pHydrogen->getSong();
437 auto pCoreActionController = pHydrogen->getCoreActionController();
438 auto pAE = pHydrogen->getAudioEngine();
439 auto pTransportPos = pAE->getTransportPosition();
441 pCoreActionController->activateLoopMode(
true );
442 pCoreActionController->activateSongMode(
true );
454 double fLastTickIntervalEnd;
455 long long nLastTransportFrame, nTotalFrames, nLastLookahead;
456 long nLastQueuingTick;
459 auto resetVariables = [&]() {
460 nLastTransportFrame = 0;
461 nLastQueuingTick = 0;
462 fLastTickIntervalEnd = 0;
469 const int nLoops = 3;
470 const double fSongSizeInTicks = pAE->m_fSongSizeInTicks;
472 const int nMaxCycles =
473 std::max( std::ceil( fSongSizeInTicks /
474 static_cast<double>(pPref->m_nBufferSize) *
475 static_cast<double>(pTransportPos->getTickSize()) * 4.0 ),
484 bool bLoopEnabled =
true;
486 while ( pTransportPos->getDoubleTick() <
487 fSongSizeInTicks * ( nLoops + 2 ) ) {
489 QString(
"[testTransportProcessingTimeline : song mode : all timeline]" ),
490 pPref->m_nBufferSize, &nLastLookahead, &nLastTransportFrame,
491 &nTotalFrames, &nLastQueuingTick, &fLastTickIntervalEnd,
false );
500 if ( bLoopEnabled && pTransportPos->getDoubleTick() >
501 fSongSizeInTicks * ( nLoops - 1 ) ) {
504 pCoreActionController->activateLoopMode(
false );
510 if ( nn > nMaxCycles ||
511 pTransportPos->getDoubleTick() > fSongSizeInTicks * nLoops ) {
513 QString(
"[testLoopMode] transport is rolling for too long. pTransportPos: %1,\n\tfSongSizeInTicks(): %2, nLoops: %3, pPref->m_nBufferSize: %4, nMaxCycles: %5" )
514 .arg( pTransportPos->toQString() )
515 .arg( fSongSizeInTicks, 0,
'f' ).arg( nLoops )
516 .arg( pPref->m_nBufferSize ).arg( nMaxCycles ) );
521 if ( pAE->m_pQueuingPosition->getDoubleTick() < fSongSizeInTicks * nLoops ) {
523 QString(
"[testLoopMode] transport ended prematurely. pAE->m_pQueuingPosition: %1,\n\tfSongSizeInTicks(): %2, nLoops: %3, pPref->m_nBufferSize: %4" )
524 .arg( pAE->m_pQueuingPosition->toQString() )
525 .arg( fSongSizeInTicks, 0,
'f' ).arg( nLoops )
526 .arg( pPref->m_nBufferSize ) );
536 long long* nLastLookahead,
537 long long* nLastTransportFrame,
538 long long* nTotalFrames,
539 long* nLastQueuingTick,
540 double* fLastTickIntervalEnd,
541 bool bCheckLookahead ) {
545 auto pQueuingPos = pAE->m_pQueuingPosition;
547 double fTickStart, fTickEnd;
548 const long long nLeadLag =
549 pAE->computeTickInterval( &fTickStart, &fTickEnd, nFrames );
550 fTickStart = pAE->coarseGrainTick( fTickStart );
551 fTickEnd = pAE->coarseGrainTick( fTickEnd );
553 if ( bCheckLookahead ) {
556 if ( *nLastLookahead != 0 &&
559 QString(
"[processTransport : lookahead] [%1] with one and the same BPM/tick size the lookahead must be consistent! [ %2 -> %3 ]" )
560 .arg( sContext ).arg( *nLastLookahead )
566 pAE->updateNoteQueue( nFrames );
567 pAE->incrementTransportPosition( nFrames );
569 if ( pAE->isEndOfSongReached( pAE->m_pTransportPosition ) ) {
576 pTransportPos,
"[processTransport] " + sContext );
579 pQueuingPos,
"[processTransport] " + sContext );
581 if ( pTransportPos->getFrame() - nFrames -
582 pTransportPos->getFrameOffsetTempo() != *nLastTransportFrame ) {
584 QString(
"[processTransport : transport] [%1] inconsistent frame update. pTransportPos->getFrame(): %2, nFrames: %3, nLastTransportFrame: %4, pTransportPos->getFrameOffsetTempo(): %5, pAE->m_fSongSizeInTicks: %6, pAE->m_nLoopsDone: %7" )
585 .arg( sContext ).arg( pTransportPos->getFrame() ).arg( nFrames )
586 .arg( *nLastTransportFrame ).arg( pTransportPos->getFrameOffsetTempo() )
587 .arg( pAE->m_fSongSizeInTicks ).arg( pAE->m_nLoopsDone ) );
589 *nLastTransportFrame = pTransportPos->getFrame() -
590 pTransportPos->getFrameOffsetTempo();
592 const int nNoteQueueUpdate =
593 static_cast<int>( fTickEnd ) -
static_cast<int>( fTickStart );
597 if ( *nLastQueuingTick > 0 && nNoteQueueUpdate > 0 ) {
598 if ( pQueuingPos->getTick() - nNoteQueueUpdate !=
600 ! pAE->isEndOfSongReached( pQueuingPos ) ) {
602 QString(
"[processTransport : queuing pos] [%1] inconsistent tick update. pQueuingPos->getTick(): %2, nNoteQueueUpdate: %3, nLastQueuingTick: %4, fTickStart: %5, fTickEnd: %6, nFrames = %7, pTransportPos: %8, pQueuingPos: %9, pAE->m_fSongSizeInTicks: %10, pAE->m_nLoopsDone: %11" )
603 .arg( sContext ).arg( pQueuingPos->getTick() )
604 .arg( nNoteQueueUpdate ).arg( *nLastQueuingTick )
605 .arg( fTickStart, 0,
'f' ).arg( fTickEnd, 0,
'f' )
606 .arg( nFrames ).arg( pTransportPos->toQString() )
607 .arg( pQueuingPos->toQString() )
608 .arg( pAE->m_fSongSizeInTicks ).arg( pAE->m_nLoopsDone ));
611 *nLastQueuingTick = pQueuingPos->getTick();
617 if ( std::abs( fTickStart - *fLastTickIntervalEnd ) > 1E-4 ||
618 fTickStart > fTickEnd ) {
620 QString(
"[processTransport : tick interval] [%1] inconsistent update. old: [ ... : %2 ], new: [ %3, %4 ], pTransportPos->getTickOffsetQueuing(): %5, diff: %6" )
621 .arg( sContext ).arg( *fLastTickIntervalEnd ).arg( fTickStart )
622 .arg( fTickEnd ).arg( pTransportPos->getTickOffsetQueuing() )
623 .arg( std::abs( fTickStart - *fLastTickIntervalEnd ), 0,
'E', -1 ) );
625 *fLastTickIntervalEnd = fTickEnd;
630 *nTotalFrames += nFrames;
631 if ( pTransportPos->getFrame() - pTransportPos->getFrameOffsetTempo() !=
634 QString(
"[processTransport : total] [%1] total frames incorrect. pTransportPos->getFrame(): %2, pTransportPos->getFrameOffsetTempo(): %3, nTotalFrames: %4" )
635 .arg( sContext ).arg( pTransportPos->getFrame() )
636 .arg( pTransportPos->getFrameOffsetTempo() ).arg( *nTotalFrames ) );
644 auto pSong = pHydrogen->getSong();
645 auto pCoreActionController = pHydrogen->getCoreActionController();
647 auto pAE = pHydrogen->getAudioEngine();
648 auto pTransportPos = pAE->getTransportPosition();
653 std::random_device randomSeed;
654 std::default_random_engine randomEngine( randomSeed() );
655 std::uniform_real_distribution<double> tickDist( 0, pAE->m_fSongSizeInTicks );
656 std::uniform_int_distribution<long long> frameDist( 0, pPref->m_nBufferSize );
667 const int nProcessCycles = 100;
668 for (
int nn = 0; nn < nProcessCycles; ++nn ) {
670 if ( nn < nProcessCycles - 2 ) {
671 fNewTick = tickDist( randomEngine );
673 else if ( nn < nProcessCycles - 1 ) {
676 fNewTick = pSong->lengthInTicks() - 1 + 0.928009209;
680 fNewTick = std::fmin( 960, pSong->lengthInTicks() );
683 pAE->locate( fNewTick,
false );
686 pTransportPos,
"[testTransportRelocation] mismatch tick-based" );
689 nNewFrame = frameDist( randomEngine );
690 pAE->locateToFrame( nNewFrame );
693 pTransportPos,
"[testTransportRelocation] mismatch frame-based" );
704 auto pCoreActionController = pHydrogen->getCoreActionController();
705 auto pSong = pHydrogen->getSong();
706 auto pAE = pHydrogen->getAudioEngine();
708 const int nTestColumn = 4;
716 pCoreActionController->activateLoopMode(
true );
717 pCoreActionController->locateToColumn( nTestColumn );
729 long nNextTick = pHydrogen->getTickForColumn( nTestColumn );
730 if ( nNextTick == -1 ) {
732 QString(
"[testSongSizeChange] Bad test design: there is no column [%1]" )
733 .arg( nTestColumn ) );
736 nNextTick += pSong->lengthInTicks();
738 pAE->locate( nNextTick );
747 pCoreActionController->activateLoopMode(
false );
752 auto pSong = pHydrogen->getSong();
753 auto pCoreActionController = pHydrogen->getCoreActionController();
755 auto pAE = pHydrogen->getAudioEngine();
756 auto pTransportPos = pAE->getTransportPosition();
758 pCoreActionController->activateTimeline(
false );
759 pCoreActionController->activateLoopMode(
true );
764 const int nColumns = pSong->getPatternGroupVector()->size();
766 std::random_device randomSeed;
767 std::default_random_engine randomEngine( randomSeed() );
768 std::uniform_real_distribution<double> tickDist( 1, pPref->m_nBufferSize );
769 std::uniform_int_distribution<int> columnDist( nColumns, nColumns + 100 );
775 const uint32_t nFrames = 500;
776 const double fInitialSongSize = pAE->m_fSongSizeInTicks;
780 auto checkState = [&](
const QString& sContext,
bool bSongSizeShouldChange ){
783 QString(
"[testSongSizeChangeInLoopMode::checkState] [%1] before increment" )
786 if ( bSongSizeShouldChange &&
787 fInitialSongSize == pAE->m_fSongSizeInTicks ) {
789 QString(
"[testSongSizeChangeInLoopMode] [%1] song size stayed the same [%2->%3]")
790 .arg( sContext ).arg( fInitialSongSize ).arg( pAE->m_fSongSizeInTicks ) );
792 else if ( ! bSongSizeShouldChange &&
793 fInitialSongSize != pAE->m_fSongSizeInTicks ) {
795 QString(
"[testSongSizeChangeInLoopMode] [%1] unexpected song enlargement [%2->%3]")
796 .arg( sContext ).arg( fInitialSongSize ).arg( pAE->m_fSongSizeInTicks ) );
799 pAE->incrementTransportPosition( nFrames );
803 QString(
"[testSongSizeChangeInLoopMode::checkState] [%1] after increment" )
807 const int nNumberOfTogglings = 5;
808 for (
int nn = 0; nn < nNumberOfTogglings; ++nn ) {
810 fTick = tickDist( randomEngine );
811 pAE->locate( fInitialSongSize + fTick );
813 checkState( QString(
"relocation to [%1]" ).arg( fTick ),
false );
815 nNewColumn = columnDist( randomEngine );
819 pCoreActionController->toggleGridCell( nNewColumn, 0 );
823 checkState( QString(
"toggling column [%1]" ).arg( nNewColumn ),
true );
827 pCoreActionController->toggleGridCell( nNewColumn, 0 );
831 checkState( QString(
"again toggling column [%1]" ).arg( nNewColumn ),
false );
840 auto pSong = pHydrogen->getSong();
841 auto pCoreActionController = pHydrogen->getCoreActionController();
843 auto pAE = pHydrogen->getAudioEngine();
844 auto pSampler = pAE->getSampler();
845 auto pTransportPos = pAE->getTransportPosition();
846 auto pQueuingPos = pAE->m_pQueuingPosition;
848 pCoreActionController->activateTimeline(
false );
849 pCoreActionController->activateLoopMode(
false );
850 pCoreActionController->activateSongMode(
true );
854 std::random_device randomSeed;
855 std::default_random_engine randomEngine( randomSeed() );
856 std::uniform_int_distribution<int> frameDist( pPref->m_nBufferSize / 2,
857 pPref->m_nBufferSize );
870 std::max( std::ceil(
static_cast<double>(pAE->m_fSongSizeInTicks) /
871 static_cast<double>(pPref->m_nBufferSize) *
872 static_cast<double>(pTransportPos->getTickSize()) * 4.0 ),
873 static_cast<double>(pAE->m_fSongSizeInTicks) );
879 auto notesInSong = pSong->getAllNotes();
880 std::vector<std::shared_ptr<Note>> notesInSongQueue;
881 std::vector<std::shared_ptr<Note>> notesInSamplerQueue;
883 auto retrieveNotes = [&](
const QString& sContext ) {
887 pAE->processAudio( nFrames );
890 pSampler->getPlayingNotesQueue() );
892 pAE->incrementTransportPosition( nFrames );
895 if ( nn > nMaxCycles ) {
897 QString(
"[testNoteEnqueuing::retrieveNotes] [%1] end of the song wasn't reached in time. pTransportPos->getFrame(): %2, pTransportPos->getDoubleTick(): %3, getTickSize(): %4, pAE->m_fSongSizeInTicks: %5, nMaxCycles: %6" )
898 .arg( sContext ).arg( pTransportPos->getFrame() )
899 .arg( pTransportPos->getDoubleTick(), 0,
'f' )
900 .arg( pTransportPos->getTickSize(), 0,
'f' )
901 .arg( pAE->m_fSongSizeInTicks, 0,
'f' ).arg( nMaxCycles ) );
907 while ( pQueuingPos->getDoubleTick() < pAE->m_fSongSizeInTicks ) {
909 nFrames = frameDist( randomEngine );
910 pAE->updateNoteQueue( nFrames );
911 retrieveNotes(
"song mode" );
914 auto checkQueueConsistency = [&](
const QString& sContext ) {
915 if ( notesInSongQueue.size() !=
916 notesInSong.size() ) {
917 QString sMsg = QString(
"[testNoteEnqueuing::checkQueueConsistency] [%1] Mismatch between notes count in Song [%2] and NoteQueue [%3]. Song:\n" )
918 .arg( sContext ).arg( notesInSong.size() )
919 .arg( notesInSongQueue.size() );
920 for (
int ii = 0; ii < notesInSong.size(); ++ii ) {
921 auto note = notesInSong[ ii ];
922 sMsg.append( QString(
"\t[%1] instr: %2, position: %3, noteStart: %4, velocity: %5\n")
924 .arg( note->get_instrument()->get_name() )
925 .arg( note->get_position() )
926 .arg( note->getNoteStart() )
927 .arg( note->get_velocity() ) );
929 sMsg.append(
"NoteQueue:\n" );
930 for (
int ii = 0; ii < notesInSongQueue.size(); ++ii ) {
931 auto note = notesInSongQueue[ ii ];
932 sMsg.append( QString(
"\t[%1] instr: %2, position: %3, noteStart: %4, velocity: %5\n")
934 .arg( note->get_instrument()->get_name() )
935 .arg( note->get_position() )
936 .arg( note->getNoteStart() )
937 .arg( note->get_velocity() ) );
946 if ( notesInSamplerQueue.size() !=
947 notesInSong.size() &&
948 pPref->m_nBufferSize < 1024 ) {
949 QString sMsg = QString(
"[testNoteEnqueuing::checkQueueConsistency] [%1] Mismatch between notes count in Song [%2] and Sampler [%3]. Song:\n" )
950 .arg( sContext ).arg( notesInSong.size() )
951 .arg( notesInSamplerQueue.size() );
952 for (
int ii = 0; ii < notesInSong.size(); ++ii ) {
953 auto note = notesInSong[ ii ];
954 sMsg.append( QString(
"\t[%1] instr: %2, position: %3, noteStart: %4, velocity: %5\n")
956 .arg( note->get_instrument()->get_name() )
957 .arg( note->get_position() )
958 .arg( note->getNoteStart() )
959 .arg( note->get_velocity() ) );
961 sMsg.append(
"SamplerQueue:\n" );
962 for (
int ii = 0; ii < notesInSamplerQueue.size(); ++ii ) {
963 auto note = notesInSamplerQueue[ ii ];
964 sMsg.append( QString(
"\t[%1] instr: %2, position: %3, noteStart: %4, velocity: %5\n")
966 .arg( note->get_instrument()->get_name() )
967 .arg( note->get_position() )
968 .arg( note->getNoteStart() )
969 .arg( note->get_velocity() ) );
975 checkQueueConsistency(
"song mode" );
984 pCoreActionController->activateSongMode(
false );
986 pHydrogen->setSelectedPatternNumber( 4 );
994 pSong->getPatternList()->get( pHydrogen->getSelectedPatternNumber() );
995 if ( pPattern ==
nullptr ) {
997 QString(
"[testNoteEnqueuing] null pattern selected [%1]" )
998 .arg( pHydrogen->getSelectedPatternNumber() ) );
1002 notesInSong.clear();
1003 for (
int ii = 0; ii < nLoops; ++ii ) {
1005 if ( it->second !=
nullptr ) {
1006 auto note = std::make_shared<Note>( it->second );
1007 note->set_position( note->get_position() +
1008 ii * pPattern->get_length() );
1009 notesInSong.push_back( note );
1014 notesInSongQueue.clear();
1015 notesInSamplerQueue.clear();
1018 static_cast<int>(std::max(
static_cast<float>(pPattern->get_length()) *
1019 static_cast<float>(nLoops) *
1020 pTransportPos->getTickSize() * 4 /
1021 static_cast<float>(pPref->m_nBufferSize),
1023 static_cast<float>(nLoops) ));
1025 while ( pQueuingPos->getDoubleTick() < pPattern->get_length() * nLoops ) {
1027 nFrames = frameDist( randomEngine );
1028 pAE->updateNoteQueue( nFrames );
1029 retrieveNotes(
"pattern mode" );
1034 auto popSurplusNotes = [&]( std::vector<std::shared_ptr<Note>>* queue ) {
1035 const int nNoteNumber = queue->size();
1036 for (
int ii = 0; ii < nNoteNumber; ++ii ) {
1037 auto pNote = queue->at( nNoteNumber - 1 - ii );
1038 if ( pNote !=
nullptr &&
1039 pNote->get_position() >= pPattern->get_length() * nLoops ) {
1046 popSurplusNotes( ¬esInSongQueue );
1047 popSurplusNotes( ¬esInSamplerQueue );
1049 checkQueueConsistency(
"pattern mode" );
1061 pCoreActionController->activateLoopMode(
true );
1062 pCoreActionController->activateSongMode(
true );
1066 pAE->reset(
false );
1071 std::max( std::ceil(
static_cast<double>(pAE->m_fSongSizeInTicks) /
1072 static_cast<double>(pPref->m_nBufferSize) *
1073 static_cast<double>(pTransportPos->getTickSize()) * 4.0 ),
1074 static_cast<double>(pAE->m_fSongSizeInTicks) ) *
1079 notesInSong.clear();
1080 for (
int ii = 0; ii <= nLoops; ++ii ) {
1081 auto notesVec = pSong->getAllNotes();
1082 for (
auto nnote : notesVec ) {
1083 nnote->set_position( nnote->get_position() +
1084 ii * pAE->m_fSongSizeInTicks );
1086 notesInSong.insert( notesInSong.end(), notesVec.begin(), notesVec.end() );
1089 notesInSongQueue.clear();
1090 notesInSamplerQueue.clear();
1093 while ( pQueuingPos->getDoubleTick() <
1094 pAE->m_fSongSizeInTicks * ( nLoops + 1 ) ) {
1096 nFrames = frameDist( randomEngine );
1099 if ( ( pTransportPos->getDoubleTick() >
1100 pAE->m_fSongSizeInTicks * nLoops + 100 ) &&
1104 pCoreActionController->activateLoopMode(
false );
1109 pAE->updateNoteQueue( nFrames );
1110 retrieveNotes(
"looped song mode" );
1113 checkQueueConsistency(
"looped song mode" );
1121 auto pSong = pHydrogen->getSong();
1122 auto pAE = pHydrogen->getAudioEngine();
1123 auto pSampler = pAE->getSampler();
1124 auto pTransportPos = pAE->getTransportPosition();
1130 std::random_device randomSeed;
1131 std::default_random_engine randomEngine( randomSeed() );
1132 std::uniform_int_distribution<int> frameDist( pPref->m_nBufferSize / 2,
1133 pPref->m_nBufferSize );
1137 pAE->reset(
false );
1142 const int nMaxCycles =
1143 std::max( std::ceil(
static_cast<double>(pAE->m_fSongSizeInTicks) /
1144 static_cast<double>(pPref->m_nBufferSize) *
1145 static_cast<double>(pTransportPos->getTickSize()) * 4.0 ),
1146 static_cast<double>(pAE->m_fSongSizeInTicks) );
1148 auto notesInSong = pSong->getAllNotes();
1149 std::vector<std::shared_ptr<Note>> notesInSongQueue;
1150 std::vector<std::shared_ptr<Note>> notesInSamplerQueue;
1153 while ( pTransportPos->getDoubleTick() <
1154 pAE->m_fSongSizeInTicks ) {
1156 nFrames = frameDist( randomEngine );
1158 pAE->updateNoteQueue( nFrames );
1164 pAE->processAudio( nFrames );
1167 pSampler->getPlayingNotesQueue() );
1169 pAE->incrementTransportPosition( nFrames );
1172 if ( nn > nMaxCycles ) {
1174 QString(
"[testNoteEnqueuingTimeline] end of the song wasn't reached in time. pTransportPos->getFrame(): %1, pTransportPos->getDoubleTick(): %2, getTickSize(): %3, pAE->m_fSongSizeInTicks: %4, nMaxCycles: %5" )
1175 .arg( pTransportPos->getFrame() )
1176 .arg( pTransportPos->getDoubleTick(), 0,
'f' )
1177 .arg( pTransportPos->getTickSize(), 0,
'f' )
1178 .arg( pAE->m_fSongSizeInTicks, 0,
'f' )
1179 .arg( nMaxCycles ) );
1183 if ( notesInSongQueue.size() != notesInSong.size() ) {
1185 QString(
"Mismatching number of notes in song [%1] and note queue [%2]" )
1186 .arg( notesInSong.size() )
1187 .arg( notesInSongQueue.size() ) );
1190 if ( notesInSamplerQueue.size() != notesInSong.size() ) {
1192 QString(
"Mismatching number of notes in song [%1] and sampler queue [%2]" )
1193 .arg( notesInSong.size() )
1194 .arg( notesInSamplerQueue.size() ) );
1198 for (
int ii = 0; ii < notesInSong.size(); ++ii ) {
1199 if ( ! notesInSong[ ii ]->match( notesInSongQueue[ ii ] ) ) {
1201 QString(
"Mismatch at note [%1] between song [%2] and song queue [%3]" )
1204 .arg( notesInSongQueue[ ii ]->
toQString() ) );
1206 if ( ! notesInSong[ ii ]->match( notesInSamplerQueue[ ii ] ) ) {
1208 QString(
"Mismatch at note [%1] between song [%2] and sampler queue [%3]" )
1211 .arg( notesInSamplerQueue[ ii ]->
toQString() ) );
1221 auto pSong = pHydrogen->getSong();
1222 auto pAE = pHydrogen->getAudioEngine();
1223 auto pSampler = pAE->getSampler();
1224 auto pTransportPos = pAE->getTransportPosition();
1226 auto pCoreActionController = pHydrogen->getCoreActionController();
1228 pCoreActionController->activateLoopMode(
false );
1229 pCoreActionController->activateSongMode(
true );
1236 pAE->reset(
false );
1240 auto getNotes = [&]( std::vector<std::shared_ptr<Note>> *notes ) {
1249 const double fStep = 10.0;
1250 const int nMaxCycles =
1251 std::max( std::ceil(
static_cast<double>(pAE->m_fSongSizeInTicks) /
1252 static_cast<double>(pPref->m_nBufferSize) * fStep *
1253 static_cast<double>(pTransportPos->getTickSize()) * 4.0 ),
1254 static_cast<double>(pAE->m_fSongSizeInTicks) );
1255 const uint32_t nFrames =
static_cast<uint32_t
>(
1256 std::round(
static_cast<double>(pPref->m_nBufferSize) / fStep ) );
1260 QString sPlayingPatterns;
1261 for (
const auto& pattern : *pTransportPos->getPlayingPatterns() ) {
1262 sPlayingPatterns +=
" " + pattern->get_name();
1266 while ( pTransportPos->getDoubleTick() < pAE->m_fSongSizeInTicks ||
1267 pAE->getEnqueuedNotesNumber() > 0 ) {
1269 pAE->updateNoteQueue( nFrames );
1271 pAE->processAudio( nFrames );
1274 pSampler->getPlayingNotesQueue() );
1276 pAE->incrementTransportPosition( nFrames );
1279 if ( nn > nMaxCycles ) {
1281 QString(
"[testHumanization::getNotes] end of the song wasn't reached in time. pTransportPos->getFrame(): %1, pTransportPos->getDoubleTick(): %2, getTickSize(): %3, pAE->m_fSongSizeInTicks: %4, nMaxCycles: %5" )
1282 .arg( pTransportPos->getFrame() )
1283 .arg( pTransportPos->getDoubleTick(), 0,
'f' )
1284 .arg( pTransportPos->getTickSize(), 0,
'f' )
1285 .arg( pAE->m_fSongSizeInTicks, 0,
'f' )
1286 .arg( nMaxCycles ) );
1295 auto setHumanization = [&](
double fValue ) {
1296 fValue = std::clamp( fValue, 0.0, 1.0 );
1298 pSong->setHumanizeTimeValue( fValue );
1299 pSong->setHumanizeVelocityValue( fValue );
1301 pSong->getInstrumentList()->get( 0 )->set_random_pitch_factor( fValue );
1304 auto setSwing = [&](
double fValue ) {
1305 fValue = std::clamp( fValue, 0.0, 1.0 );
1307 pSong->setSwingFactor( fValue );
1312 auto notesInSong = pSong->getAllNotes();
1315 setHumanization( 0 );
1318 std::vector<std::shared_ptr<Note>> notesReference;
1319 getNotes( ¬esReference );
1321 if ( notesReference.size() != notesInSong.size() ) {
1323 QString(
"[testHumanization] [references] Bad test setup. Mismatching number of notes [%1 : %2]" )
1324 .arg( notesReference.size() )
1325 .arg( notesInSong.size() ) );
1335 pCoreActionController->toggleGridCell( 0, 0 );
1336 pCoreActionController->toggleGridCell( 0, 1 );
1340 std::vector<std::shared_ptr<Note>> notesCustomized;
1341 getNotes( ¬esCustomized );
1343 if ( notesReference.size() != notesCustomized.size() ) {
1345 QString(
"[testHumanization] [customization] Mismatching number of notes [%1 : %2]" )
1346 .arg( notesReference.size() )
1347 .arg( notesCustomized.size() ) );
1350 for (
int ii = 0; ii < notesReference.size(); ++ii ) {
1351 auto pNoteReference = notesReference[ ii ];
1352 auto pNoteCustomized = notesCustomized[ ii ];
1354 if ( pNoteReference !=
nullptr && pNoteCustomized !=
nullptr ) {
1355 if ( pNoteReference->get_velocity() ==
1356 pNoteCustomized->get_velocity() ) {
1358 QString(
"[testHumanization] [customization] Velocity of note [%1] was not altered" )
1360 }
else if ( pNoteReference->get_lead_lag() ==
1361 pNoteCustomized->get_lead_lag() ) {
1363 QString(
"[testHumanization] [customization] Lead Lag of note [%1] was not altered" )
1365 }
else if ( pNoteReference->getNoteStart() ==
1366 pNoteCustomized->getNoteStart() ) {
1371 QString(
"[testHumanization] [customization] Note start of note [%1] was not altered" )
1373 }
else if ( pNoteReference->getPan() ==
1374 pNoteCustomized->getPan() ) {
1376 QString(
"[testHumanization] [customization] Pan of note [%1] was not altered" )
1378 }
else if ( pNoteReference->get_total_pitch() ==
1379 pNoteCustomized->get_total_pitch() ) {
1381 QString(
"[testHumanization] [customization] Total Pitch of note [%1] was not altered" )
1386 QString(
"[testHumanization] [customization] Unable to access note [%1]" )
1400 pCoreActionController->toggleGridCell( 0, 1 );
1401 pCoreActionController->toggleGridCell( 0, 0 );
1405 auto checkHumanization = [&](
double fValue, std::vector<std::shared_ptr<Note>>* pNotes ) {
1407 if ( notesReference.size() != pNotes->size() ) {
1409 QString(
"[testHumanization] [humanization] Mismatching number of notes [%1 : %2]" )
1410 .arg( notesReference.size() )
1411 .arg( pNotes->size() ) );
1414 auto checkDeviation = []( std::vector<float>* pDeviations,
float fTargetSD,
const QString& sContext ) {
1416 float fMean = std::accumulate( pDeviations->begin(), pDeviations->end(),
1417 0.0, std::plus<float>() ) /
1418 static_cast<float>( pDeviations->size() );
1421 auto compVariance = [&](
float fValue,
float fElement ) {
1422 return fValue + ( fElement - fMean ) * ( fElement - fMean );
1424 float fSD = std::sqrt( std::accumulate( pDeviations->begin(),
1426 0.0, compVariance ) /
1427 static_cast<float>( pDeviations->size() ) );
1432 if ( std::abs( fMean ) > std::abs( fSD ) * 0.5 ) {
1434 QString(
"[testHumanization] [%1] Mismatching mean [%2] != [0] with std. deviation [%3]" )
1435 .arg( sContext ).arg( fMean, 0,
'E', -1 )
1436 .arg( fSD, 0,
'E', -1 ) );
1438 if ( std::abs( fSD - fTargetSD ) > fTargetSD * 0.5 ) {
1440 QString(
"[testHumanization] [%1] Mismatching standard deviation [%2] != [%3], diff [%4]" )
1441 .arg( sContext ).arg( fSD, 0,
'E', -1 )
1442 .arg( fTargetSD, 0,
'E', -1 )
1443 .arg( fSD - fTargetSD, 0,
'E', -1 ) );
1448 std::vector<float> deviationsPitch( notesReference.size() );
1449 std::vector<float> deviationsVelocity( notesReference.size() );
1450 std::vector<float> deviationsTiming( notesReference.size() );
1452 for (
int ii = 0; ii < pNotes->size(); ++ii ) {
1453 auto pNoteReference = notesReference[ ii ];
1454 auto pNoteHumanized = pNotes->at( ii );
1456 if ( pNoteReference !=
nullptr && pNoteHumanized !=
nullptr ) {
1457 deviationsVelocity[ ii ] =
1458 pNoteReference->get_velocity() - pNoteHumanized->get_velocity();
1459 deviationsPitch[ ii ] =
1460 pNoteReference->get_pitch() - pNoteHumanized->get_pitch();
1461 deviationsTiming[ ii ] =
1462 pNoteReference->getNoteStart() - pNoteHumanized->getNoteStart();
1465 QString(
"[testHumanization] [swing] Unable to access note [%1]" )
1474 checkDeviation( &deviationsVelocity,
1476 checkDeviation( &deviationsTiming,
1479 checkDeviation( &deviationsPitch,
1483 setHumanization( 0.2 );
1484 std::vector<std::shared_ptr<Note>> notesHumanizedWeak;
1485 getNotes( ¬esHumanizedWeak );
1486 checkHumanization( 0.2, ¬esHumanizedWeak );
1488 setHumanization( 0.8 );
1489 std::vector<std::shared_ptr<Note>> notesHumanizedStrong;
1490 getNotes( ¬esHumanizedStrong );
1491 checkHumanization( 0.8, ¬esHumanizedStrong );
1501 setHumanization( 0 );
1503 std::vector<std::shared_ptr<Note>> notesSwing;
1504 getNotes( ¬esSwing );
1506 if ( notesReference.size() != notesSwing.size() ) {
1508 QString(
"[testHumanization] [swing] Mismatching number of notes [%1 : %2]" )
1509 .arg( notesReference.size() )
1510 .arg( notesSwing.size() ) );
1513 bool bNoteAltered =
false;
1514 for (
int ii = 0; ii < notesReference.size(); ++ii ) {
1515 auto pNoteReference = notesReference[ ii ];
1516 auto pNoteSwing = notesSwing[ ii ];
1518 if ( pNoteReference !=
nullptr && pNoteSwing !=
nullptr ) {
1519 if ( pNoteReference->getNoteStart() !=
1520 pNoteSwing->getNoteStart() ) {
1521 bNoteAltered =
true;
1525 QString(
"[testHumanization] [swing] Unable to access note [%1]" )
1529 if ( ! bNoteAltered ) {
1541 for (
const auto& newNote : newNotes ) {
1544 for (
const auto& presentNote : *noteList ) {
1545 if ( newNote !=
nullptr && presentNote !=
nullptr ) {
1546 if ( newNote->match( presentNote.get() ) &&
1547 newNote->get_position() == presentNote->get_position() &&
1548 newNote->get_velocity() == presentNote->get_velocity() ) {
1554 if ( ! bNoteFound ) {
1555 noteList->push_back( std::make_shared<Note>(newNote.get()) );
1563 for (
const auto& newNote : newNotes ) {
1566 for (
const auto& presentNote : *noteList ) {
1567 if ( newNote !=
nullptr && presentNote !=
nullptr ) {
1568 if ( newNote->match( presentNote.get() ) &&
1569 newNote->get_position() == presentNote->get_position() &&
1570 newNote->get_velocity() == presentNote->get_velocity() ) {
1576 if ( ! bNoteFound ) {
1577 noteList->push_back( std::make_shared<Note>(newNote) );
1585 auto pSong = pHydrogen->getSong();
1586 auto pAE = pHydrogen->getAudioEngine();
1588 double fCheckTickMismatch;
1589 const long long nCheckFrame =
1591 pPos->getDoubleTick(), &fCheckTickMismatch );
1592 const double fCheckTick =
1595 if ( abs( fCheckTick + fCheckTickMismatch - pPos->getDoubleTick() ) > 1e-9 ||
1596 abs( fCheckTickMismatch - pPos->m_fTickMismatch ) > 1e-9 ) {
1598 QString(
"[checkTransportPosition] [%8] [tick mismatch]. original position: [%1],\nnCheckFrame: %2, fCheckTick: %3, fCheckTickMismatch: %4, fCheckTick + fCheckTickMismatch - pPos->getDoubleTick(): %5, fCheckTickMismatch - pPos->m_fTickMismatch: %6, nCheckFrame - pPos->getFrame(): %7" )
1599 .arg( pPos->toQString(
"",
true ) ).arg( nCheckFrame )
1600 .arg( fCheckTick, 0 ,
'f', 9 ).arg( fCheckTickMismatch, 0 ,
'f', 9 )
1601 .arg( fCheckTick + fCheckTickMismatch - pPos->getDoubleTick(), 0,
'E' )
1602 .arg( fCheckTickMismatch - pPos->m_fTickMismatch, 0,
'E' )
1603 .arg( nCheckFrame - pPos->getFrame() ).arg( sContext ) );
1606 if ( nCheckFrame != pPos->getFrame() ) {
1608 QString(
"[checkTransportPosition] [%8] [frame mismatch]. original position: [%1],\nnCheckFrame: %2, fCheckTick: %3, fCheckTickMismatch: %4, fCheckTick + fCheckTickMismatch - pPos->getDoubleTick(): %5, fCheckTickMismatch - pPos->m_fTickMismatch: %6, nCheckFrame - pPos->getFrame(): %7" )
1609 .arg( pPos->toQString(
"",
true ) ).arg( nCheckFrame )
1610 .arg( fCheckTick, 0 ,
'f', 9 ).arg( fCheckTickMismatch, 0 ,
'f', 9 )
1611 .arg( fCheckTick + fCheckTickMismatch - pPos->getDoubleTick(), 0,
'E' )
1612 .arg( fCheckTickMismatch - pPos->m_fTickMismatch, 0,
'E' )
1613 .arg( nCheckFrame - pPos->getFrame() ).arg( sContext ) );
1616 long nCheckPatternStartTick;
1617 const int nCheckColumn = pHydrogen->getColumnForTick(
1618 pPos->getTick(), pSong->isLoopEnabled(), &nCheckPatternStartTick );
1619 const long nTicksSinceSongStart =
static_cast<long>(std::floor(
1620 std::fmod( pPos->getDoubleTick(), pAE->m_fSongSizeInTicks ) ));
1622 if ( pHydrogen->getMode() ==
Song::Mode::Song && pPos->getColumn() != -1 &&
1623 ( nCheckColumn != pPos->getColumn() ) ) {
1625 QString(
"[checkTransportPosition] [%7] [column mismatch]. current position: [%1],\nnCheckColumn: %2, nCheckPatternStartTick: %3, nCheckPatternTickPosition: %4, nTicksSinceSongStart: %5, pAE->m_fSongSizeInTicks: %6" )
1626 .arg( pPos->toQString(
"",
true ) ).arg( nCheckColumn )
1627 .arg( nCheckPatternStartTick )
1628 .arg( nTicksSinceSongStart - nCheckPatternStartTick )
1629 .arg( nTicksSinceSongStart ).arg( pAE->m_fSongSizeInTicks, 0,
'f' )
1633 if ( pHydrogen->getMode() ==
Song::Mode::Song && pPos->getColumn() != -1 &&
1634 ( ( nCheckPatternStartTick != pPos->getPatternStartTick() ) ||
1635 ( nTicksSinceSongStart - nCheckPatternStartTick !=
1636 pPos->getPatternTickPosition() ) ) ) {
1638 QString(
"[checkTransportPosition] [%7] [pattern tick mismatch]. current position: [%1],\nnCheckColumn: %2, nCheckPatternStartTick: %3, nCheckPatternTickPosition: %4, nTicksSinceSongStart: %5, pAE->m_fSongSizeInTicks: %6" )
1639 .arg( pPos->toQString(
"",
true ) ).arg( nCheckColumn )
1640 .arg( nCheckPatternStartTick )
1641 .arg( nTicksSinceSongStart - nCheckPatternStartTick )
1642 .arg( nTicksSinceSongStart ).arg( pAE->m_fSongSizeInTicks, 0,
'f' )
1648 const std::vector<std::shared_ptr<Note>> newNotes,
1649 const QString& sContext,
1650 int nPassedFrames,
bool bTestAudio,
1651 float fPassedTicks ) {
1653 auto pSong = pHydrogen->getSong();
1654 auto pAE = pHydrogen->getAudioEngine();
1655 auto pTransportPos = pAE->getTransportPosition();
1657 double fPassedFrames =
static_cast<double>(nPassedFrames);
1658 const int nSampleRate = pHydrogen->getAudioOutput()->getSampleRate();
1660 int nNotesFound = 0;
1661 for (
const auto& ppNewNote : newNotes ) {
1662 for (
const auto& ppOldNote : oldNotes ) {
1663 if ( ppNewNote->match( ppOldNote.get() ) &&
1664 ppNewNote->get_humanize_delay() ==
1665 ppOldNote->get_humanize_delay() &&
1666 ppNewNote->get_velocity() == ppOldNote->get_velocity() ) {
1672 for (
int nn = 0; nn < ppNewNote->get_instrument()->get_components()->size(); nn++ ) {
1673 auto pSelectedLayer = ppOldNote->get_layer_selected( nn );
1681 if ( ppOldNote->getSample( nn )->get_sample_rate() !=
1683 ppOldNote->get_total_pitch() != 0.0 ) {
1685 fPassedFrames =
static_cast<double>(nPassedFrames) *
1687 static_cast<float>(ppOldNote->getSample( nn )->get_sample_rate()) /
1688 static_cast<float>(nSampleRate);
1691 const int nSampleFrames =
1692 ppNewNote->get_instrument()->get_component( nn )
1693 ->get_layer( pSelectedLayer->nSelectedLayer )
1694 ->get_sample()->get_frames();
1695 const double fExpectedFrames =
1696 std::min(
static_cast<double>(pSelectedLayer->fSamplePosition) +
1698 static_cast<double>(nSampleFrames) );
1699 if ( std::abs( ppNewNote->get_layer_selected( nn )->fSamplePosition -
1700 fExpectedFrames ) > 1 ) {
1702 QString(
"[checkAudioConsistency] [%4] glitch in audio render. Diff: %9\nPre: %1\nPost: %2\nwith passed frames: %3, nSampleFrames: %5, fExpectedFrames: %6, sample sampleRate: %7, driver sampleRate: %8\n" )
1703 .arg( ppOldNote->toQString(
"",
true ) )
1704 .arg( ppNewNote->toQString(
"",
true ) )
1705 .arg( fPassedFrames, 0,
'f' ).arg( sContext )
1706 .arg( nSampleFrames ).arg( fExpectedFrames, 0,
'f' )
1707 .arg( ppOldNote->getSample( nn )->get_sample_rate() )
1709 .arg( ppNewNote->get_layer_selected( nn )->fSamplePosition -
1710 fExpectedFrames, 0,
'g', 30 ) );
1718 if ( ppNewNote->get_position() - fPassedTicks !=
1719 ppOldNote->get_position() ) {
1721 QString(
"[checkAudioConsistency] [%5] glitch in note queue.\n\tPre: %1\n\tPost: %2\n\tfPassedTicks: %3, diff (new - passed - old): %4" )
1722 .arg( ppOldNote->toQString(
"",
true ) )
1723 .arg( ppNewNote->toQString(
"",
true ) )
1724 .arg( fPassedTicks )
1725 .arg( ppNewNote->get_position() - fPassedTicks -
1726 ppOldNote->get_position() ).arg( sContext ) );
1737 if ( nNotesFound == 0 &&
1738 oldNotes.size() > 0 &&
1739 newNotes.size() > 0 ) {
1740 QString sMsg = QString(
"[checkAudioConsistency] [%1] bad test design. No notes played back." )
1742 sMsg.append(
"\nold notes:" );
1743 for (
auto const& nnote : oldNotes ) {
1744 sMsg.append(
"\n" + nnote->toQString(
" ",
true ) );
1746 sMsg.append(
"\nnew notes:" );
1747 for (
auto const& nnote : newNotes ) {
1748 sMsg.append(
"\n" + nnote->toQString(
" ",
true ) );
1750 sMsg.append( QString(
"\n\npTransportPos->getDoubleTick(): %1, pTransportPos->getFrame(): %2, nPassedFrames: %3, fPassedTicks: %4, pTransportPos->getTickSize(): %5" )
1751 .arg( pTransportPos->getDoubleTick(), 0,
'f' )
1752 .arg( pTransportPos->getFrame() )
1753 .arg( nPassedFrames )
1754 .arg( fPassedTicks, 0,
'f' )
1755 .arg( pTransportPos->getTickSize(), 0,
'f' ) );
1756 sMsg.append(
"\n\n notes in song:" );
1757 for (
auto const& nnote : pSong->getAllNotes() ) {
1758 sMsg.append(
"\n" + nnote->toQString(
" ",
true ) );
1766 std::vector<Note*> rawNotes;
1767 std::vector<std::shared_ptr<Note>> notes;
1768 for ( ; ! pAE->m_songNoteQueue.empty(); pAE->m_songNoteQueue.pop() ) {
1769 rawNotes.push_back( pAE->m_songNoteQueue.top() );
1770 notes.push_back( std::make_shared<Note>( pAE->m_songNoteQueue.top() ) );
1773 for (
auto nnote : rawNotes ) {
1774 pAE->m_songNoteQueue.push( nnote );
1782 auto pCoreActionController = pHydrogen->getCoreActionController();
1783 auto pSong = pHydrogen->getSong();
1784 auto pAE = pHydrogen->getAudioEngine();
1785 auto pSampler = pAE->getSampler();
1786 auto pTransportPos = pAE->getTransportPosition();
1788 const unsigned long nBufferSize = pHydrogen->getAudioOutput()->getBufferSize();
1790 pAE->updateNoteQueue( nBufferSize );
1791 pAE->processAudio( nBufferSize );
1792 pAE->incrementTransportPosition( nBufferSize );
1797 float fPrevTempo, fPrevTickSize;
1798 double fPrevTickStart, fPrevTickEnd;
1799 long long nPrevLeadLag;
1801 std::vector<std::shared_ptr<Note>> notesSamplerPreToggle,
1802 notesSamplerPostToggle, notesSamplerPostRolling;
1806 auto toggleAndCheck = [&](
const QString& sContext ) {
1807 notesSamplerPreToggle.clear();
1808 for (
const auto& ppNote : pSampler->getPlayingNotesQueue() ) {
1809 notesSamplerPreToggle.push_back( std::make_shared<Note>( ppNote ) );
1812 nPrevLeadLag = pAE->computeTickInterval( &fPrevTickStart, &fPrevTickEnd,
1814 nOldSongSize = pSong->lengthInTicks();
1815 nOldColumn = pTransportPos->getColumn();
1816 fPrevTempo = pTransportPos->getBpm();
1817 fPrevTickSize = pTransportPos->getTickSize();
1821 pCoreActionController->toggleGridCell( nToggleColumn, nToggleRow );
1825 const QString sNewContext =
1826 QString(
"toggleAndCheckConsistency::toggleAndCheck : %1 : toggling (%2,%3)" )
1827 .arg( sContext ).arg( nToggleColumn ).arg( nToggleRow );
1830 const long nNewSongSize = pSong->lengthInTicks();
1831 if ( nNewSongSize == nOldSongSize ) {
1833 QString(
"[%1] no change in song size" ).arg( sNewContext ) );
1843 notesSongQueuePreToggle, notesSongQueuePostToggle,
1844 sNewContext +
" : song queue", 0,
false,
1845 pTransportPos->getTickOffsetSongSize() );
1849 notesSongQueuePreToggle = notesSongQueuePostToggle;
1851 notesSamplerPostToggle.clear();
1852 for (
const auto& ppNote : pSampler->getPlayingNotesQueue() ) {
1853 notesSamplerPostToggle.push_back( std::make_shared<Note>( ppNote ) );
1856 notesSamplerPreToggle, notesSamplerPostToggle,
1857 sNewContext +
" : sampler queue", 0,
true,
1858 pTransportPos->getTickOffsetSongSize() );
1863 if ( nOldColumn < pSong->getPatternGroupVector()->size() ) {
1866 if ( nOldColumn != pTransportPos->getColumn() &&
1867 nOldColumn < pSong->getPatternGroupVector()->size() ) {
1869 QString(
"[%3] Column changed old: %1, new: %2" )
1870 .arg( nOldColumn ).arg( pTransportPos->getColumn() )
1871 .arg( sNewContext ) );
1874 double fTickEnd, fTickStart;
1875 const long long nLeadLag =
1876 pAE->computeTickInterval( &fTickStart, &fTickEnd, nBufferSize );
1877 if ( std::abs( nLeadLag - nPrevLeadLag ) > 1 ) {
1879 QString(
"[%3] LeadLag should be constant since there should be change in tick size. old: %1, new: %2" )
1880 .arg( nPrevLeadLag ).arg( nLeadLag ).arg( sNewContext ) );
1882 if ( std::abs( fTickStart - pTransportPos->getTickOffsetSongSize() - fPrevTickStart ) > 4e-3 ) {
1884 QString(
"[%5] Mismatch in the start of the tick interval handled by updateNoteQueue new [%1] != [%2] old+offset, old: %3, offset: %4" )
1885 .arg( fTickStart, 0,
'f' )
1886 .arg( fPrevTickStart + pTransportPos->getTickOffsetSongSize(), 0,
'f' )
1887 .arg( fPrevTickStart, 0,
'f' )
1888 .arg( pTransportPos->getTickOffsetSongSize(), 0,
'f' )
1889 .arg( sNewContext ) );
1891 if ( std::abs( fTickEnd - pTransportPos->getTickOffsetSongSize() - fPrevTickEnd ) > 4e-3 ) {
1893 QString(
"[%5] Mismatch in the end of the tick interval handled by updateNoteQueue new [%1] != [%2] old+offset, old: %3, offset: %4" )
1894 .arg( fTickEnd, 0,
'f' )
1895 .arg( fPrevTickEnd + pTransportPos->getTickOffsetSongSize(), 0,
'f' )
1896 .arg( fPrevTickEnd, 0,
'f' )
1897 .arg( pTransportPos->getTickOffsetSongSize(), 0,
'f' )
1898 .arg( sNewContext ) );
1901 else if ( pTransportPos->getColumn() != 0 &&
1902 nOldColumn >= pSong->getPatternGroupVector()->size() ) {
1904 QString(
"[%4] Column reset failed nOldColumn: %1, pTransportPos->getColumn() (new): %2, pSong->getPatternGroupVector()->size() (new): %3" )
1906 .arg( pTransportPos->getColumn() )
1907 .arg( pSong->getPatternGroupVector()->size() )
1908 .arg( sNewContext ) );
1917 pAE->incrementTransportPosition( nBufferSize );
1918 pAE->processAudio( nBufferSize );
1922 double fTickEndRolling, fTickStartUnused;
1923 pAE->computeTickInterval( &fTickStartUnused, &fTickEndRolling, nBufferSize );
1925 pAE->incrementTransportPosition( nBufferSize );
1926 pAE->processAudio( nBufferSize );
1928 pAE->m_fLastTickEnd = fTickEndRolling;
1931 if ( fPrevTempo != pTransportPos->getBpm() ||
1932 fPrevTickSize != pTransportPos->getTickSize() ) {
1934 QString(
"[%1] tempo and ticksize are affected" )
1935 .arg( sNewContext ) );
1938 notesSamplerPostRolling.clear();
1939 for (
const auto& ppNote : pSampler->getPlayingNotesQueue() ) {
1940 notesSamplerPostRolling.push_back( std::make_shared<Note>( ppNote ) );
1943 notesSamplerPostToggle, notesSamplerPostRolling,
1944 QString(
"toggleAndCheckConsistency::toggleAndCheck : %1 : rolling after toggle (%2,%3)" )
1945 .arg( sContext ).arg( nToggleColumn ).arg( nToggleRow ),
1946 nBufferSize * 2,
true );
1950 toggleAndCheck( sContext +
" : 1. toggle" );
1953 toggleAndCheck( sContext +
" : 2. toggle" );
1958 auto pSong = pHydrogen->getSong();
1959 auto pAE = pHydrogen->getAudioEngine();
1960 auto pSampler = pAE->getSampler();
1965 const int nMaxCleaningCycles = 5000;
1969 while ( pSampler->isRenderingNotes() ) {
1970 pAE->processAudio( pPref->m_nBufferSize );
1971 pAE->incrementTransportPosition( pPref->m_nBufferSize );
1989 if ( nn > nMaxCleaningCycles ) {
1991 QString(
"[%1] Sampler is in weird state" )
1996 pAE->reset(
false );
2001 auto pSong = pHydrogen->getSong();
2002 auto pCoreActionController = pHydrogen->getCoreActionController();
2003 auto pAE = pHydrogen->getAudioEngine();
2011 auto pTransportOld =
2012 std::make_shared<TransportPosition>( pAE->getTransportPosition() );
2014 std::make_shared<TransportPosition>( pAE->m_pQueuingPosition );
2016 auto pTestPos = std::make_shared<TransportPosition>(
"test" );
2017 const long long nFrame = 3521;
2019 pAE->updateTransportPosition( fTick, nFrame, pTestPos );
2021 if ( pAE->getTransportPosition() != pTransportOld ) {
2022 throwException( QString(
"[testUpdateTransportPosition] Glitch in pAE->m_pTransportPosition:\nOld: %1\nNew: %2" )
2023 .arg( pTransportOld->toQString() )
2024 .arg( pAE->getTransportPosition()->toQString() ) );
2026 if ( pAE->m_pQueuingPosition != pQueuingOld ) {
2027 throwException( QString(
"[testUpdateTransportPosition] Glitch in pAE->m_pQueuingPosition:\nOld: %1\nNew: %2" )
2028 .arg( pQueuingOld->toQString() )
2029 .arg( pAE->m_pQueuingPosition->toQString() ) );
2032 if ( pTransportOld == pTestPos ) {
2033 throwException(
"[testUpdateTransportPosition] Test position shouldn't coincide with pAE->m_pTransportPosition" );
2042 pHydrogen->setSong(
nullptr );
2045 auto pNullPos = std::make_shared<TransportPosition>(
"null" );
2046 pAE->updateTransportPosition( fTick, nFrame, pNullPos );
2049 pHydrogen->setSong( pSong );
2052#ifdef H2CORE_HAVE_JACK
2053void AudioEngineTests::testTransportProcessingJack() {
2055 auto pSong = pHydrogen->getSong();
2056 auto pCoreActionController = pHydrogen->getCoreActionController();
2057 auto pAE = pHydrogen->getAudioEngine();
2061 pCoreActionController->activateLoopMode(
false );
2067 auto pDriver = startJackAudioDriver();
2068 if ( pDriver ==
nullptr ) {
2069 throwException(
"[testTransportProcessingJack] Unable to use JACK driver" );
2076 bool bTempoChangeEncountered;
2080 fBpm = pAE->getBpmAtColumn( 0 );
2090 QTest::qSleep( 400 );
2092 const int nMaxMilliSeconds = 11500;
2093 int nMilliSeconds = 0;
2094 const int nIncrement = 100;
2097 if ( ! bTempoChangeEncountered && fBpm != pAE->getBpmAtColumn( 0 ) ) {
2098 bTempoChangeEncountered =
true;
2101 if ( nMilliSeconds >= nMaxMilliSeconds ) {
2103 QString(
"[testTransportProcessingJack] playback takes too long" ) );
2106 QTest::qSleep( nIncrement );
2107 nMilliSeconds += nIncrement;
2113 pAE->stopPlayback();
2119 ! bTempoChangeEncountered ) {
2120 throwException(
"[testTransportProcessingJack] no tempo changes received from JACK Timebase controller" );
2123 stopJackAudioDriver();
2126void AudioEngineTests::testTransportProcessingOffsetsJack() {
2128 auto pSong = pHydrogen->getSong();
2129 auto pCoreActionController = pHydrogen->getCoreActionController();
2130 auto pAE = pHydrogen->getAudioEngine();
2131 auto pTransportPos = pAE->getTransportPosition();
2136 if ( pHydrogen->getJackTimebaseState() ==
2143 pCoreActionController->activateLoopMode(
false );
2144 pCoreActionController->activateTimeline(
false );
2146 std::random_device randomSeed;
2147 std::default_random_engine randomEngine( randomSeed() );
2148 std::uniform_real_distribution<float> tempoDist(
MIN_BPM,
MAX_BPM );
2154 auto pDriver = startJackAudioDriver();
2155 if ( pDriver ==
nullptr ) {
2156 throwException(
"[testTransportProcessingOffsetsJack] Unable to use JACK driver" );
2159 float fBpm, fLastBpm;
2160 bool bTempoChanged =
false;
2161 const int nToggleColumn = 4;
2162 const int nToggleRow = 4;
2163 const float fOriginalSongSize = pAE->m_fSongSizeInTicks;
2166 fLastBpm = pAE->getBpmAtColumn( 0 );
2175 QTest::qSleep( 400 );
2177 const int nMaxMilliSeconds = 11500;
2178 int nMilliSeconds = 0;
2179 const int nIncrement = 100;
2182 if ( ! bTempoChanged && fLastBpm != pAE->getBpmAtColumn( 0 ) ) {
2183 bTempoChanged =
true;
2186 if ( nMilliSeconds >= nMaxMilliSeconds ) {
2188 QString(
"[testTransportProcessingOffsetsJack] playback takes too long" ) );
2196 const auto nOldSongSize = pAE->m_fSongSizeInTicks;
2197 pCoreActionController->toggleGridCell( nToggleColumn, nToggleRow );
2198 if ( nOldSongSize == pAE->m_fSongSizeInTicks ) {
2199 throwException(
"[testTransportProcessingOffsetsJack] song size did not change." );
2201 INFOLOG( QString(
"[testTransportProcessingOffsetsJack] update song size [%1] -> [%2]" )
2202 .arg( nOldSongSize ).arg( pAE->m_fSongSizeInTicks ) );
2205 pTransportPos,
"[testTransportProcessingOffsetsJack] mismatch after song size update" );
2209 QTest::qSleep( nIncrement );
2210 nMilliSeconds += nIncrement;
2212 fBpm = tempoDist( randomEngine );
2214 INFOLOG( QString(
"[testTransportProcessingOffsetsJack] changing tempo [%1]->[%2]" )
2215 .arg( pAE->getBpmAtColumn( 0 ) ).arg( fBpm ) );
2216 pAE->setNextBpm( fBpm );
2219 QTest::qSleep( nIncrement );
2220 nMilliSeconds += nIncrement;
2226 pAE->stopPlayback();
2231 if ( ! bTempoChanged ) {
2232 throwException(
"[testTransportProcessingOffsetsJack] tempo was not change. Decrease time increments!" );
2237 if ( pAE->m_fSongSizeInTicks != fOriginalSongSize ) {
2238 pCoreActionController->toggleGridCell( nToggleColumn, nToggleRow );
2241 stopJackAudioDriver();
2244void AudioEngineTests::testTransportRelocationJack() {
2246 auto pSong = pHydrogen->getSong();
2248 auto pAE = pHydrogen->getAudioEngine();
2249 auto pTransportPos = pAE->getTransportPosition();
2254 pAE->stopPlayback();
2262 auto pDriver = startJackAudioDriver();
2263 if ( pDriver ==
nullptr ) {
2264 throwException(
"[testTransportRelocationJack] Unable to use JACK driver" );
2268#ifdef HAVE_INTEGRATION_TESTS
2269 JackAudioDriver::m_nIntegrationLastRelocationFrame = -1;
2270 pDriver->m_bIntegrationCheckRelocationLoop =
true;
2274 std::random_device randomSeed;
2275 std::default_random_engine randomEngine( randomSeed() );
2276 std::uniform_real_distribution<double> tickDist( 0, pAE->m_fSongSizeInTicks );
2281 long long nNewFrame;
2284 double fTickMismatch;
2285 const int nProcessCycles = 100;
2286 for (
int nn = 0; nn < nProcessCycles; ++nn ) {
2291 if ( pHydrogen->getJackTimebaseState() !=
2293 if ( nn < nProcessCycles - 2 ) {
2294 fNewTick = tickDist( randomEngine );
2296 else if ( nn < nProcessCycles - 1 ) {
2299 fNewTick = pSong->lengthInTicks() - 1 + 0.928009209;
2303 fNewTick = std::fmin( 960, pSong->lengthInTicks() );
2310 while ( std::abs( fNewTick - pTransportPos->getDoubleTick() ) < 1 ) {
2311 fNewTick = tickDist( randomEngine );
2314 INFOLOG( QString(
"relocate to tick [%1]->[%2]" )
2315 .arg( pTransportPos->getDoubleTick() ).arg( fNewTick ) );
2316 pAE->locate( fNewTick,
true );
2319 AudioEngineTests::waitForRelocation( pDriver, fNewTick, -1 );
2323 if ( abs( pTransportPos->getDoubleTick() - fNewTick ) > 1e-1 ) {
2324 throwException( QString(
"[testTransportRelocationJack::tick] failed to relocate to tick. [%1] != [%2]" )
2325 .arg( pTransportPos->getDoubleTick() ).arg( fNewTick ) );
2328#ifdef HAVE_INTEGRATION_TESTS
2332 if ( pDriver->m_bIntegrationRelocationLoop ) {
2333 throwException(
"[testTransportRelocationJack::frame] relocation loop detected" );
2338 pTransportPos,
"[testTransportRelocationJack::tick] mismatch tick-based" );
2343 if ( nn < nProcessCycles - 1 ) {
2345 tickDist( randomEngine ), &fTickMismatch );
2348 nNewFrame = std::min(
static_cast<long long>(2174246),
2350 pSong->lengthInTicks(), &fTickMismatch ) );
2355 while ( nNewFrame == pTransportPos->getFrame() ) {
2357 tickDist( randomEngine ), &fTickMismatch );
2360#ifdef HAVE_INTEGRATION_TESTS
2361 if ( pHydrogen->getJackTimebaseState() ==
2367 pDriver->m_nTimebaseFrameOffset = 0;
2368 JackAudioDriver::m_nIntegrationLastRelocationFrame = -1;
2372 INFOLOG( QString(
"relocate to frame [%1]->[%2]" )
2373 .arg( pTransportPos->getFrame() ).arg( nNewFrame ) );
2374 pDriver->locateTransport( nNewFrame );
2377 AudioEngineTests::waitForRelocation( pDriver, -1, nNewFrame );
2379 long long nCurrentFrame;
2380 if ( pHydrogen->getJackTimebaseState() ==
2382 nCurrentFrame = pDriver->m_JackTransportPos.frame;
2385 nCurrentFrame = pTransportPos->getFrame();
2388 if ( nNewFrame != nCurrentFrame ) {
2389 throwException( QString(
"[testTransportRelocationJack::frame] failed to relocate to frame. timebase state: [%1], nNewFrame [%2] != nCurrentFrame [%3], pPos->getFrame(): [%4]" )
2391 pDriver->getTimebaseState() ) )
2393 .arg( nCurrentFrame )
2394 .arg( pTransportPos->getFrame() ) );
2397#ifdef HAVE_INTEGRATION_TESTS
2401 if ( pDriver->m_bIntegrationRelocationLoop ) {
2402 throwException(
"[testTransportRelocationJack::frame] relocation loop detected" );
2407 pTransportPos,
"[testTransportRelocationJack::frame] mismatch frame-based" );
2411#ifdef HAVE_INTEGRATION_TESTS
2412 pDriver->m_bIntegrationCheckRelocationLoop =
false;
2413 JackAudioDriver::m_nIntegrationLastRelocationFrame = -1;
2418 stopJackAudioDriver();
2421void AudioEngineTests::testTransportRelocationOffsetsJack() {
2423 auto pSong = pHydrogen->getSong();
2425 auto pAE = pHydrogen->getAudioEngine();
2426 auto pTransportPos = pAE->getTransportPosition();
2427 auto pCoreActionController = pHydrogen->getCoreActionController();
2432 if ( pHydrogen->getJackTimebaseState() ==
2437 pCoreActionController->activateTimeline(
false );
2442 pAE->stopPlayback();
2450 auto pDriver = startJackAudioDriver();
2451 if ( pDriver ==
nullptr ) {
2452 throwException(
"[testTransportRelocationOffsetsJack] Unable to use JACK driver" );
2454 float fBpm, fLastBpm;
2455 bool bTempoChanged =
false;
2456 const int nToggleColumn = 4;
2457 const int nToggleRow = 4;
2458 const float fOriginalSongSize = pAE->m_fSongSizeInTicks;
2461 fLastBpm = pAE->getBpmAtColumn( 0 );
2462#ifdef HAVE_INTEGRATION_TESTS
2463 JackAudioDriver::m_nIntegrationLastRelocationFrame = -1;
2464 pDriver->m_bIntegrationCheckRelocationLoop =
true;
2468 std::random_device randomSeed;
2469 std::default_random_engine randomEngine( randomSeed() );
2470 std::uniform_real_distribution<double> tickDist( 0, pAE->m_fSongSizeInTicks );
2471 std::uniform_real_distribution<float> tempoDist(
MIN_BPM,
MAX_BPM );
2476 long long nNewFrame;
2477 double fTickMismatch;
2478 const int nProcessCycles = 100;
2479 for (
int nn = 0; nn < nProcessCycles; ++nn ) {
2480 if ( ! bTempoChanged && fLastBpm != pAE->getBpmAtColumn( 0 ) ) {
2481 bTempoChanged =
true;
2487 if ( pHydrogen->getJackTimebaseState() !=
2489 if ( nn < nProcessCycles - 2 ) {
2490 fNewTick = tickDist( randomEngine );
2492 else if ( nn < nProcessCycles - 1 ) {
2495 fNewTick = pSong->lengthInTicks() - 1 + 0.928009209;
2499 fNewTick = std::fmin( 960, pSong->lengthInTicks() );
2506 while ( std::abs( fNewTick - pTransportPos->getDoubleTick() ) < 1 ) {
2507 fNewTick = tickDist( randomEngine );
2510 INFOLOG( QString(
"relocate to tick [%1]->[%2]" )
2511 .arg( pTransportPos->getDoubleTick() ).arg( fNewTick ) );
2512 pAE->locate( fNewTick,
true );
2515 AudioEngineTests::waitForRelocation( pDriver, fNewTick, -1 );
2519 if ( abs( pTransportPos->getDoubleTick() - fNewTick ) > 1e-1 ) {
2520 throwException( QString(
"[testTransportRelocationOffsetsJack::tick] failed to relocate to tick. [%1] != [%2]" )
2521 .arg( pTransportPos->getDoubleTick() ).arg( fNewTick ) );
2524#ifdef HAVE_INTEGRATION_TESTS
2528 if ( pDriver->m_bIntegrationRelocationLoop ) {
2529 throwException(
"[testTransportRelocationOffsetsJack::frame] relocation loop detected" );
2534 pTransportPos,
"[testTransportRelocationOffsetsJack::tick] mismatch tick-based" );
2542 const auto nOldSongSize = pAE->m_fSongSizeInTicks;
2543 pCoreActionController->toggleGridCell( 4, 4 );
2544 if ( nOldSongSize == pAE->m_fSongSizeInTicks ) {
2545 throwException(
"[testTransportRelocationOffsetsJack] song size did not change." );
2547 INFOLOG( QString(
"[testTransportRelocationOffsetsJack] update song size [%1] -> [%2]" )
2548 .arg( nOldSongSize ).arg( pAE->m_fSongSizeInTicks ) );
2552 if ( nn < nProcessCycles - 1 ) {
2554 tickDist( randomEngine ), &fTickMismatch );
2557 nNewFrame = std::min(
static_cast<long long>(2174246),
2559 pSong->lengthInTicks(), &fTickMismatch ) );
2564 while ( nNewFrame == pTransportPos->getFrame() ) {
2566 tickDist( randomEngine ), &fTickMismatch );
2569#ifdef HAVE_INTEGRATION_TESTS
2570 if ( pHydrogen->getJackTimebaseState() ==
2576 pDriver->m_nTimebaseFrameOffset = 0;
2577 JackAudioDriver::m_nIntegrationLastRelocationFrame = -1;
2582 pTransportPos,
"[testTransportRelocationOffsetsJack] mismatch after song size update" );
2584 INFOLOG( QString(
"relocate to frame [%1]->[%2]" )
2585 .arg( pTransportPos->getFrame() ).arg( nNewFrame ) );
2586 pDriver->locateTransport( nNewFrame );
2589 AudioEngineTests::waitForRelocation( pDriver, -1, nNewFrame );
2591 long long nCurrentFrame;
2592 if ( pHydrogen->getJackTimebaseState() ==
2594 nCurrentFrame = pDriver->m_JackTransportPos.frame;
2597 nCurrentFrame = pTransportPos->getFrame();
2600 if ( nNewFrame != nCurrentFrame ) {
2601 throwException( QString(
"[testTransportRelocationOffsetsJack::frame] failed to relocate to frame. timebase state: [%1], nNewFrame [%2] != nCurrentFrame [%3], pPos->getFrame(): [%4]" )
2603 pDriver->getTimebaseState() ) )
2605 .arg( nCurrentFrame )
2606 .arg( pTransportPos->getFrame() ) );
2609#ifdef HAVE_INTEGRATION_TESTS
2613 if ( pDriver->m_bIntegrationRelocationLoop ) {
2614 throwException(
"[testTransportRelocationOffsetsJack::frame] relocation loop detected" );
2619 pTransportPos,
"[testTransportRelocationOffsetsJack::frame] mismatch frame-based" );
2622 fBpm = tempoDist( randomEngine );
2624 INFOLOG( QString(
"[testTransportRelocationOffsetsJack] changing tempo [%1]->[%2]" )
2625 .arg( pAE->getBpmAtColumn( 0 ) ).arg( fBpm ) );
2626 pAE->setNextBpm( fBpm );
2633 QTest::qSleep( 25 );
2636 pTransportPos,
"[testTransportRelocationOffsetsJack::tempo] mismatch after tempo change" );
2640#ifdef HAVE_INTEGRATION_TESTS
2641 pDriver->m_bIntegrationCheckRelocationLoop =
false;
2642 JackAudioDriver::m_nIntegrationLastRelocationFrame = -1;
2647 if ( ! bTempoChanged ) {
2648 throwException(
"[testTransportRelocationOffsetsJack] tempo was not change." );
2653 if ( pAE->m_fSongSizeInTicks != fOriginalSongSize ) {
2654 pCoreActionController->toggleGridCell( nToggleColumn, nToggleRow );
2657 stopJackAudioDriver();
2661 INFOLOG(
"Starting custom JACK audio driver..." );
2664 auto pAudioEngine = pHydrogen->getAudioEngine();
2668 throwException(
"[startJackAudioDriver] Engine must not be locked and in state testing yet!" );
2671 pAudioEngine->stopAudioDrivers();
2674 auto pDriver =
new JackAudioDriver( jackTestProcessCallback );
2675 if ( pDriver ==
nullptr ) {
2676 throwException(
"[startJackAudioDriver] Unable to create JackAudioDriver" );
2678#ifdef H2CORE_HAVE_JACK
2681 pDriver->setConnectDefaults(
false );
2683 throwException(
"[startJackAudioDriver] This function should not be run without JACK support" );
2687 if ( pDriver->init( pPref->m_nBufferSize ) != 0 ) {
2689 pAudioEngine->unlock();
2690 throwException(
"[startJackAudioDriver] Unable to initialize driver" );
2697 INFOLOG(
"Releasing test binary as Timebase controller" );
2698 pDriver->releaseTimebaseControl();
2702 INFOLOG(
"Register test binary as Timebase controller" );
2703 pDriver->initTimebaseControl();
2705 pDriver->m_timebaseState = m_referenceTimebase;
2707 pAudioEngine->m_MutexOutputPointer.lock();
2709 pAudioEngine->m_pAudioDriver = pDriver;
2712 pAudioEngine->m_MutexOutputPointer.unlock();
2713 pAudioEngine->unlock();
2715 if ( pDriver->connect() != 0 ) {
2716 pAudioEngine->restartAudioDrivers();
2717 throwException(
"[startJackAudioDriver] Unable to connect driver" );
2720 if ( pHydrogen->getSong() !=
nullptr ) {
2722 pAudioEngine->handleDriverChange();
2723 pAudioEngine->unlock();
2726 INFOLOG(
"DONE Starting custom JACK audio driver." );
2731void AudioEngineTests::stopJackAudioDriver() {
2732 INFOLOG(
"Stopping custom JACK audio driver..." );
2735 auto pAudioEngine = pHydrogen->getAudioEngine();
2738 throwException(
"[stopJackAudioDriver] Engine must not be locked and in state testing yet!" );
2742 pAudioEngine->restartAudioDrivers();
2744#ifdef H2CORE_HAVE_JACK
2745 auto pDriver =
dynamic_cast<JackAudioDriver*
>(pAudioEngine->m_pAudioDriver);
2746 if ( pDriver ==
nullptr ) {
2748 "[stopJackAudioDriver] No JACK driver after restart!" );
2751 pDriver->m_timebaseState = m_referenceTimebase;
2755 INFOLOG(
"DONE Stopping custom JACK audio driver." );
2759 double fTick,
long long nFrame ) {
2761 auto pAE = pHydrogen->getAudioEngine();
2762 auto pTransportPos = pAE->getTransportPosition();
2764 const int nMaxMilliSeconds = 5000;
2765 const int nSecondTryMilliSeconds = 1000;
2766 int nMilliSeconds = 0;
2767 const int nIncrement = 100;
2773 long long nCurrentFrame;
2774 if ( pHydrogen->getJackTimebaseState() ==
2776 nCurrentFrame = pDriver->m_JackTransportPos.frame;
2778 nCurrentFrame = pTransportPos->getFrame();
2781 if ( ( nFrame != -1 && nFrame == nCurrentFrame ) ||
2783 abs( pTransportPos->getDoubleTick() - fTick ) < 1e-1 ) ) {
2787 if ( nMilliSeconds >= nMaxMilliSeconds ) {
2789 if ( nFrame != -1 ) {
2790 sTarget = QString(
"frame [%1]" ).arg( nFrame );
2792 sTarget = QString(
"tick [%1]" ).arg( fTick );
2795 QString(
"[AudioEngineTests::waitForRelocation] playback takes too long to reach %1" )
2797 }
else if ( nMilliSeconds == nSecondTryMilliSeconds ) {
2798 WARNINGLOG( QString(
"[AudioEngineTests::waitForRelocation] Performing seconds attempt after [%1]ms" )
2799 .arg( nMilliSeconds ) );
2806 if ( fTick != -1 ) {
2808 pAE->locate( fTick,
true );
2814#ifdef HAVE_INTEGRATION_TESTS
2815 if ( pHydrogen->getJackTimebaseState() ==
2822 pDriver->m_nTimebaseFrameOffset = 0;
2823 JackAudioDriver::m_nIntegrationLastRelocationFrame = -1;
2827 pDriver->locateTransport( nFrame );
2832 QTest::qSleep( nIncrement );
2833 nMilliSeconds += nIncrement;
2837int AudioEngineTests::jackTestProcessCallback( uint32_t nframes,
void* args ) {
2840 auto pDriver =
dynamic_cast<JackAudioDriver*
>(pAudioEngine->m_pAudioDriver);
2841 if ( pDriver ==
nullptr ) {
2843 "[jackTestProcessCallback] No JACK driver!" );
2857 QString(
"[jackTestProcessCallback] [%1] engine must not be in state Testing" )
2861 pAudioEngine->clearAudioBuffers( nframes );
2866 float sampleRate =
static_cast<float>(pDriver->getSampleRate());
2867 pAudioEngine->m_fMaxProcessTime = 1000.0 / ( sampleRate / nframes );
2868 float fSlackTime = pAudioEngine->m_fMaxProcessTime - pAudioEngine->m_fProcessTime;
2873 if ( fSlackTime < 0.0 ) {
2884 if ( !pAudioEngine->tryLockFor( std::chrono::microseconds( (
int)(1000.0*fSlackTime) ),
2886 ___ERRORLOG( QString(
"[%1] Failed to lock audioEngine in allowed %2 ms, missed buffer" )
2887 .arg( sDrivers ).arg( fSlackTime ) );
2894 pAudioEngine->unlock();
2899 std::shared_ptr<Song> pSong = pHydrogen->getSong();
2900 if ( pSong ==
nullptr ) {
2902 QString(
"[jackTestProcessCallback] [%1] no song set yet" )
2907 pAudioEngine->getTransportPosition(),
2908 QString(
"[jackTestProcessCallback] [%1] : pre updated" )
2913#ifdef H2CORE_HAVE_JACK
2915 pDriver->updateTransportPosition();
2917#ifdef HAVE_INTEGRATION_TESTS
2918 if ( pDriver->m_bIntegrationRelocationLoop ) {
2920 "[jackTestProcessCallback] Relocation loop detected!" );
2925 const auto jackPos = pDriver->getJackPosition();
2926 if ( ( jackPos.valid & JackPositionBBT ) &&
2929 "[jackTestProcessCallback] Inconsistent JACK BBT information" );
2933 const auto pTransportPos = pAudioEngine->getTransportPosition();
2934 jack_position_t testPos;
2935 if ( pTransportPos->getDoubleTick() >=
2936 pAudioEngine->m_fSongSizeInTicks ) {
2940 testPos.frame = pTransportPos->getFrame();
2941 testPos.tick = pTransportPos->getDoubleTick();
2947 "[jackTestProcessCallback::transportToBBT] Invalid JACK position: %1,\ntransport pos: %2" )
2949 .arg( pTransportPos->toQString() ) );
2953 if ( pTransportPos->getDoubleTick() <
2954 pAudioEngine->m_fSongSizeInTicks &&
2955 std::abs( fTick - pTransportPos->getTick() ) > 1e-5 ) {
2957 "[jackTestProcessCallback] Mismatching ticks after BBT conversion: diff: %1, fTick: %2, pAE->m_fSongSizeInTicks: %3\nJACK pos: %4\nTransport pos: %5" )
2958 .arg( std::abs( fTick - pTransportPos->getTick() ), 0,
'f' )
2959 .arg( fTick ).arg( pAudioEngine->m_fSongSizeInTicks )
2961 .arg( pTransportPos->toQString() ) );
2963 else if ( pTransportPos->getDoubleTick() >=
2964 pAudioEngine->m_fSongSizeInTicks && fTick != 0 ) {
2966 "[jackTestProcessCallback] Mismatching ticks after BBT conversion at end of song: fTick: %1, pAE->m_fSongSizeInTicks: %2\nJACK pos: %3\nTransport pos: %4" )
2967 .arg( fTick ).arg( pAudioEngine->m_fSongSizeInTicks )
2969 .arg( pTransportPos->toQString() ) );
2974 QString(
"[jackTestProcessCallback] [%1] callback should only be used with JACK driver!" )
2979 if ( pDriver->getTimebaseState() != AudioEngineTests::m_referenceTimebase ) {
2981 QString(
"[jackTestProcessCallback] Timebase state changed. Current: [%1], reference: [%2]" )
2984 AudioEngineTests::m_referenceTimebase ) ) );
2988 pAudioEngine->updateBpmAndTickSize( pAudioEngine->m_pTransportPosition );
2989 pAudioEngine->updateBpmAndTickSize( pAudioEngine->m_pQueuingPosition );
2992 pAudioEngine->getTransportPosition(),
2993 QString(
"[jackTestProcessCallback] [%1] : post JACK" )
3000 pAudioEngine->startPlayback();
3003 pAudioEngine->setRealtimeFrame( pAudioEngine->m_pTransportPosition->getFrame() );
3006 pAudioEngine->stopPlayback();
3011 pAudioEngine->setRealtimeFrame( pAudioEngine->getRealtimeFrame() +
3012 static_cast<long long>(nframes) );
3017 pAudioEngine->updateNoteQueue( nframes );
3019 pAudioEngine->processAudio( nframes );
3024 if ( pAudioEngine->isEndOfSongReached(
3025 pAudioEngine->m_pTransportPosition ) ) {
3027 ___INFOLOG( QString(
"[%1] End of song received" ).arg( sDrivers ) );
3029 if ( pHydrogen->getMidiOutput() !=
nullptr ) {
3030 pHydrogen->getMidiOutput()->handleQueueAllNoteOff();
3033 pAudioEngine->stop();
3034 pAudioEngine->stopPlayback();
3035 pAudioEngine->locate( 0 );
3039 pAudioEngine->incrementTransportPosition( nframes );
3044 pAudioEngine->getTransportPosition(),
3045 QString(
"[jackTestProcessCallback] [%1] : post update" )
3048 pAudioEngine->unlock();
3056 auto pAE = pHydrogen->getAudioEngine();
3061 const auto sMsgLocal8Bit = sMsg.toLocal8Bit();
3062 throw std::runtime_error( sMsgLocal8Bit.data() );
#define RIGHT_HERE
Macro intended to be used for the logging of the locking of the H2Core::AudioEngine.
#define FOREACH_NOTE_CST_IT_BEGIN_LENGTH(_notes, _it, _pattern)
Iterate over all accessible notes between position 0 and length of _pattern in an immutable way.
static std::vector< std::shared_ptr< Note > > copySongNoteQueue()
static void testTransportProcessingTimeline()
More detailed test of the transport and playhead integrity in case Timeline/tempo markers are involve...
static void testUpdateTransportPosition()
Checks is reproducible and works even without any song set.
static void testLoopMode()
Unit test checking that loop mode as well as deactivating it works.
static void testSongSizeChange()
Unit test checking consistency of transport position when playback was looped at least once and the s...
static void toggleAndCheckConsistency(int nToggleColumn, int nToggleRow, const QString &sContext)
Toggles the grid cell defined by nToggleColumn and nToggleRow twice and checks whether the transport ...
static void testNoteEnqueuing()
Unit test checking that all notes in a song are picked up once.
static void checkTransportPosition(std::shared_ptr< TransportPosition > pPos, const QString &sContext)
Checks the consistency of the transport position pPos by converting the current tick,...
static void testTransportRelocation()
Unit test checking the relocation of the transport position in audioEngine_process().
static void testTransportProcessing()
Unit test checking the incremental update of the transport position in audioEngine_process().
static void testNoteEnqueuingTimeline()
Checks whether the order of notes enqueued and processed by the Sampler is consistent on tempo change...
static void testSongSizeChangeInLoopMode()
Unit test checking consistency of transport position when playback was looped at least once and the s...
static void resetSampler(const QString &sContext)
static int processTransport(const QString &sContext, int nFrames, long long *nLastLookahead, long long *nLastTransportFrame, long long *nTotalFrames, long *nLastPlayheadTick, double *fLastTickIntervalEnd, bool bCheckLookahead=true)
static void mergeQueues(std::vector< std::shared_ptr< Note > > *noteList, std::vector< std::shared_ptr< Note > > newNotes)
Add every Note in newNotes not yet contained in noteList to the latter.
static void throwException(const QString &sMsg)
static void testFrameToTickConversion()
Unit test checking for consistency when converting frames to ticks and back.
static void testHumanization()
Unit test checking that custom note properties take effect and that humanization works as expected.
static void checkAudioConsistency(const std::vector< std::shared_ptr< Note > > oldNotes, const std::vector< std::shared_ptr< Note > > newNotes, const QString &sContext, int nPassedFrames, bool bTestAudio=true, float fPassedTicks=0.0)
Takes two instances of Sampler::m_playingNotesQueue and checks whether matching notes have exactly nP...
QString getDriverNames() const
static float getBpmAtColumn(int nColumn)
static constexpr float fHumanizePitchSD
Maximum value the standard deviation of the Gaussian distribution the random pitch contribution will ...
@ Playing
Transport is rolling.
@ Ready
Ready to process audio.
@ Testing
State used during the unit tests of the AudioEngine.
static constexpr float fHumanizeTimingSD
Maximum value the standard deviation of the Gaussian distribution the random pitch contribution will ...
static constexpr int nMaxTimeHumanize
Maximum time (in frames) a note's position can be off due to the humanization (lead-lag).
static constexpr float fHumanizeVelocitySD
Maximum value the standard deviation of the Gaussian distribution the random velocity contribution wi...
const std::shared_ptr< TransportPosition > getTransportPosition() const
virtual QString toQString(const QString &sPrefix="", bool bShort=true) const
Formatted string version for debugging purposes.
std::shared_ptr< Song > getSong() const
Get the current song.
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
AudioEngine * getAudioEngine() const
JACK (Jack Audio Connection Kit) server driver.
@ Valid
Current timebase state is on par with JACK server.
static void transportToBBT(const TransportPosition &transportPos, jack_position_t *pPos)
static QString JackTransportPosToQString(const jack_position_t &pPos)
static double bbtToTick(const jack_position_t &pos)
Timebase
Whether Hydrogen or another program is in Timebase control.
@ Listener
An external program is Timebase controller and Hydrogen will disregard all tempo markers on the Timel...
@ None
Only normal clients registered.
@ Controller
Hydrogen itself is Timebase controller and provides its current tempo to other Timebase listeners.
static QString TimebaseToQString(const Timebase &t)
static bool isBBTValid(const jack_position_t &pos)
static double pitchToFrequency(double fPitch)
Convert a logarithmic pitch-space value in semitones to a frequency-domain value.
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
@ Selected
Only one pattern - the one currently selected in the GUI - will be played back.
static long long computeFrameFromTick(double fTick, double *fTickMismatch, int nSampleRate=0)
Calculates frame equivalent of fTick.
static double computeTickFromFrame(long long nFrame, int nSampleRate=0)
Calculates tick equivalent of nFrame.
#define MAX_NOTES
Maximum number of notes.