hydrogen 1.2.6
TransportPosition.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2025 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
24
25#include <core/Basics/Pattern.h>
27#include <core/Basics/Song.h>
28#include <core/Hydrogen.h>
30#include <core/Timeline.h>
31#include <core/config.h>
32
33#define TRANSPORT_POSITION_DEBUG 0
34
35#define TP_DEBUGLOG(x) if ( __logger->should_log( Logger::Debug ) ) { \
36 __logger->log( Logger::Debug, _class_name(), __FUNCTION__, \
37 QString( "%1" ).arg( x ), "\033[33;1m" ); }
38
39namespace H2Core {
40
42 : m_sLabel( sLabel )
43{
45 m_pPlayingPatterns->setNeedsLock( true );
47 m_pNextPatterns->setNeedsLock( true );
48
49 reset();
50}
51
52TransportPosition::TransportPosition( std::shared_ptr<TransportPosition> pOther ) {
54 m_pPlayingPatterns->setNeedsLock( true );
56 m_pNextPatterns->setNeedsLock( true );
57
58 set( pOther );
59}
60
62 // We just hold copies of current patterns. We do not want to discard them.
63 m_pPlayingPatterns->clear();
64 delete m_pPlayingPatterns;
65
66 m_pNextPatterns->clear();
67 delete m_pNextPatterns;
68}
69
70void TransportPosition::set( std::shared_ptr<TransportPosition> pOther ) {
71 m_nFrame = pOther->m_nFrame;
72 m_fTick = pOther->m_fTick;
73 m_fTickSize = pOther->m_fTickSize;
74 m_fBpm = pOther->m_fBpm;
75 m_nPatternStartTick = pOther->m_nPatternStartTick;
76 m_nPatternTickPosition = pOther->m_nPatternTickPosition;
77 m_nColumn = pOther->m_nColumn;
78 m_fTickMismatch = pOther->m_fTickMismatch;
79 m_nFrameOffsetTempo = pOther->m_nFrameOffsetTempo;
80 m_fTickOffsetQueuing = pOther->m_fTickOffsetQueuing;
81 m_fTickOffsetSongSize = pOther->m_fTickOffsetSongSize;
82
83 m_pPlayingPatterns->clear();
84 for ( const auto ppPattern : *pOther->m_pPlayingPatterns ) {
85 if ( ppPattern != nullptr ) {
86 m_pPlayingPatterns->add( ppPattern );
87 }
88 }
89 m_pNextPatterns->clear();
90 for ( const auto ppPattern : *pOther->m_pNextPatterns ) {
91 if ( ppPattern != nullptr ) {
92 m_pNextPatterns->add( ppPattern );
93 }
94 }
95 m_nPatternSize = pOther->m_nPatternSize;
96 m_nLastLeadLagFactor = pOther->m_nLastLeadLagFactor;
97 m_nBar = pOther->m_nBar;
98 m_nBeat = pOther->m_nBeat;
99}
100
102 m_nFrame = 0;
103 m_fTick = 0;
104 m_fTickSize = 400;
105 m_fBpm = 120;
108 m_nColumn = -1;
109 m_fTickMismatch = 0;
113
114 m_pPlayingPatterns->clear();
115 m_pNextPatterns->clear();
118 m_nBar = 1;
119 m_nBeat = 1;
120}
121
122void TransportPosition::setBpm( float fNewBpm ) {
123 if ( fNewBpm > MAX_BPM ) {
124 ERRORLOG( QString( "[%1] Provided bpm [%2] is too high. Assigning upper bound %3 instead" )
125 .arg( m_sLabel ).arg( fNewBpm ).arg( MAX_BPM ) );
126 fNewBpm = MAX_BPM;
127 } else if ( fNewBpm < MIN_BPM ) {
128 ERRORLOG( QString( "[%1] Provided bpm [%2] is too low. Assigning lower bound %3 instead" )
129 .arg( m_sLabel ).arg( fNewBpm ).arg( MIN_BPM ) );
130 fNewBpm = MIN_BPM;
131 }
132
133 m_fBpm = fNewBpm;
134
135 if ( Preferences::get_instance()->getRubberBandBatchMode() ) {
137 }
138}
139
140void TransportPosition::setFrame( long long nNewFrame ) {
141 if ( nNewFrame < 0 ) {
142 ERRORLOG( QString( "[%1] Provided frame [%2] is negative. Setting frame 0 instead." )
143 .arg( m_sLabel ).arg( nNewFrame ) );
144 nNewFrame = 0;
145 }
146
147 m_nFrame = nNewFrame;
148}
149
150void TransportPosition::setTick( double fNewTick ) {
151 if ( fNewTick < 0 ) {
152 ERRORLOG( QString( "[%1] Provided tick [%2] is negative. Setting frame 0 instead." )
153 .arg( m_sLabel ).arg( fNewTick ) );
154 fNewTick = 0;
155 }
156
157 m_fTick = fNewTick;
158}
159
160void TransportPosition::setTickSize( float fNewTickSize ) {
161 if ( fNewTickSize <= 0 ) {
162 ERRORLOG( QString( "[%1] Provided tick size [%2] is too small. Using 400 as a fallback instead." )
163 .arg( m_sLabel ).arg( fNewTickSize ) );
164 fNewTickSize = 400;
165 }
166
167 m_fTickSize = fNewTickSize;
168}
169
170void TransportPosition::setPatternStartTick( long nPatternStartTick ) {
171 if ( nPatternStartTick < 0 ) {
172 ERRORLOG( QString( "[%1] Provided tick [%2] is negative. Setting frame 0 instead." )
173 .arg( m_sLabel ).arg( nPatternStartTick ) );
174 nPatternStartTick = 0;
175 }
176
177 m_nPatternStartTick = nPatternStartTick;
178}
179
180void TransportPosition::setPatternTickPosition( long nPatternTickPosition ) {
181 if ( nPatternTickPosition < 0 ) {
182 ERRORLOG( QString( "[%1] Provided tick [%2] is negative. Setting frame 0 instead." )
183 .arg( m_sLabel ).arg( nPatternTickPosition ) );
184 nPatternTickPosition = 0;
185 }
186
187 m_nPatternTickPosition = nPatternTickPosition;
188}
189
190void TransportPosition::setColumn( int nColumn ) {
191 if ( nColumn < -1 ) {
192 ERRORLOG( QString( "[%1] Provided column [%2] it too small. Using [-1] as a fallback instead." )
193 .arg( m_sLabel ).arg( nColumn ) );
194 nColumn = -1;
195 }
196
197 m_nColumn = nColumn;
198}
199
200
201void TransportPosition::setPatternSize( int nPatternSize ) {
202 if ( nPatternSize < 0 ) {
203 ERRORLOG( QString( "[%1] Provided pattern size [%2] it too small. Using [0] as a fallback instead." )
204 .arg( m_sLabel ).arg( nPatternSize ) );
205 nPatternSize = 0;
206 }
207
208 m_nPatternSize = nPatternSize;
209}
211 if ( nBar < 1 ) {
212 ERRORLOG( QString( "[%1] Provided bar [%2] it too small. Using [1] as a fallback instead." )
213 .arg( m_sLabel ).arg( nBar ) );
214 nBar = 1;
215 }
216 m_nBar = nBar;
217}
218
219void TransportPosition::setBeat( int nBeat ) {
220 if ( nBeat < 1 ) {
221 ERRORLOG( QString( "[%1] Provided beat [%2] it too small. Using [1] as a fallback instead." )
222 .arg( m_sLabel ).arg( nBeat ) );
223 nBeat = 1;
224 }
225 m_nBeat = nBeat;
226}
227
228// This function uses the assumption that sample rate and resolution
229// are constant over the whole song.
230long long TransportPosition::computeFrameFromTick( const double fTick, double* fTickMismatch, int nSampleRate ) {
231
232 const auto pHydrogen = Hydrogen::get_instance();
233 const auto pSong = pHydrogen->getSong();
234 const auto pTimeline = pHydrogen->getTimeline();
235 const auto pAudioEngine = pHydrogen->getAudioEngine();
236 const auto pAudioDriver = pHydrogen->getAudioOutput();
237
238 if ( pAudioDriver == nullptr ) {
239 ERRORLOG( "AudioDriver is not ready!" );
240 *fTickMismatch = 0;
241 return 0;
242 }
243
244 int nResolution;
245 if ( pSong != nullptr ) {
246 nResolution = pSong->getResolution();
247 } else {
248 nResolution = Song::nDefaultResolution;
249 }
250
251 if ( nSampleRate == 0 ) {
252 nSampleRate = pAudioDriver->getSampleRate();
253 }
254 const double fSongSizeInTicks = pAudioEngine->getSongSizeInTicks();
255
256 if ( nSampleRate == 0 || nResolution == 0 ) {
257 ERRORLOG( "Not properly initialized yet" );
258 *fTickMismatch = 0;
259 return 0;
260 }
261
262 if ( fTick == 0 ) {
263 *fTickMismatch = 0;
264 return 0;
265 }
266
267 std::vector<std::shared_ptr<const Timeline::TempoMarker>> tempoMarkers;
268 bool bSpecialFirstMarker = false;
269 if ( pTimeline != nullptr ) {
270 tempoMarkers = pTimeline->getAllTempoMarkers();
271 bSpecialFirstMarker = pTimeline->isFirstTempoMarkerSpecial();
272 }
273
274 int nColumns = 0;
275 if ( pSong != nullptr ) {
276 nColumns = pSong->getPatternGroupVector()->size();
277 }
278
279 // If there are no patterns in the current, we treat song mode
280 // like pattern mode.
281 long long nNewFrame = 0;
282 if ( pHydrogen->isTimelineEnabled() &&
283 ! ( tempoMarkers.size() == 1 && bSpecialFirstMarker ) &&
284 pHydrogen->getMode() == Song::Mode::Song && nColumns > 0 ) {
285
286 double fNewTick = fTick;
287 double fRemainingTicks = fTick;
288 double fNextTick, fPassedTicks = 0;
289 double fNextTickSize;
290 double fNewFrame = 0;
291 int ii;
292
293 auto handleEnd = [&]() {
294 // The next frame is within this segment.
295 fNewFrame += fRemainingTicks * fNextTickSize;
296
297 nNewFrame = static_cast<long long>( std::round( fNewFrame ) );
298
299 // Keep track of the rounding error to be able to switch
300 // between fTick and its frame counterpart later on. In
301 // case fTick is located close to a tempo marker we will
302 // only cover the part up to the tempo marker in here as
303 // only this region is governed by fNextTickSize.
304 const double fRoundingErrorInTicks =
305 ( fNewFrame - static_cast<double>( nNewFrame ) ) /
306 fNextTickSize;
307
308 // Compares the negative distance between current position
309 // (fNewFrame) and the one resulting from rounding -
310 // fRoundingErrorInTicks - with the negative distance
311 // between current position (fNewFrame) and location of
312 // next tempo marker.
313 if ( fRoundingErrorInTicks >
314 fPassedTicks + fRemainingTicks - fNextTick ) {
315 // Whole mismatch located within the current tempo
316 // interval.
317 *fTickMismatch = fRoundingErrorInTicks;
318
319 }
320 else {
321 // Mismatch at this side of the tempo marker.
322 *fTickMismatch = fPassedTicks + fRemainingTicks - fNextTick;
323
324 const double fFinalFrame = fNewFrame +
325 ( fNextTick - fPassedTicks - fRemainingTicks ) * fNextTickSize;
326
327 // Mismatch located beyond the tempo marker.
328 double fFinalTickSize;
329 if ( ii < tempoMarkers.size() ) {
330 fFinalTickSize = AudioEngine::computeDoubleTickSize(
331 nSampleRate, tempoMarkers[ ii ]->fBpm, nResolution );
332 }
333 else {
334 fFinalTickSize = AudioEngine::computeDoubleTickSize(
335 nSampleRate, tempoMarkers[ 0 ]->fBpm, nResolution );
336 }
337
338#if TRANSPORT_POSITION_DEBUG
339 TP_DEBUGLOG( QString( "[::computeFrameFromTick mismatch : 2] fTickMismatch: [%1 + %2], static_cast<double>(nNewFrame): %3, fNewFrame: %4, fFinalFrame: %5, fNextTickSize: %6, fPassedTicks: %7, fRemainingTicks: %8, fFinalTickSize: %9" )
340 .arg( fPassedTicks + fRemainingTicks - fNextTick )
341 .arg( ( fFinalFrame - static_cast<double>(nNewFrame) ) / fNextTickSize )
342 .arg( nNewFrame )
343 .arg( fNewFrame, 0, 'f' )
344 .arg( fFinalFrame, 0, 'f' )
345 .arg( fNextTickSize, 0, 'f' )
346 .arg( fPassedTicks, 0, 'f' )
347 .arg( fRemainingTicks, 0, 'f' )
348 .arg( fFinalTickSize, 0, 'f' ));
349#endif
350
351 *fTickMismatch += ( fFinalFrame - static_cast<double>(nNewFrame) ) /
352 fFinalTickSize;
353 }
354
355#if TRANSPORT_POSITION_DEBUG
356 TP_DEBUGLOG( QString( "[::computeFrameFromTick end] fTick: %1, fNewFrame: %2, fNextTick: %3, fRemainingTicks: %4, fPassedTicks: %5, fNextTickSize: %6, tempoMarkers[ ii - 1 ]->nColumn: %7, tempoMarkers[ ii - 1 ]->fBpm: %8, nNewFrame: %9, fTickMismatch: %10, frame increment (fRemainingTicks * fNextTickSize): %11, fRoundingErrorInTicks: %12" )
357 .arg( fTick, 0, 'f' )
358 .arg( fNewFrame, 0, 'g', 30 )
359 .arg( fNextTick, 0, 'f' )
360 .arg( fRemainingTicks, 0, 'f' )
361 .arg( fPassedTicks, 0, 'f' )
362 .arg( fNextTickSize, 0, 'f' )
363 .arg( tempoMarkers[ ii - 1 ]->nColumn )
364 .arg( tempoMarkers[ ii - 1 ]->fBpm )
365 .arg( nNewFrame )
366 .arg( *fTickMismatch, 0, 'g', 30 )
367 .arg( fRemainingTicks * fNextTickSize, 0, 'g', 30 )
368 .arg( fRoundingErrorInTicks, 0, 'f' )
369 );
370#endif
371
372 fRemainingTicks -= fNewTick - fPassedTicks;
373 };
374
375 while ( fRemainingTicks > 0 ) {
376
377 for ( ii = 1; ii <= tempoMarkers.size(); ++ii ) {
378 if ( ii == tempoMarkers.size() ||
379 tempoMarkers[ ii ]->nColumn >= nColumns ) {
380 fNextTick = fSongSizeInTicks;
381 } else {
382 fNextTick =
383 static_cast<double>(pHydrogen->getTickForColumn( tempoMarkers[ ii ]->nColumn ) );
384 }
385
386 fNextTickSize =
388 tempoMarkers[ ii - 1 ]->fBpm,
389 nResolution );
390
391 if ( fRemainingTicks > ( fNextTick - fPassedTicks ) ) {
392 // The whole segment of the timeline covered by tempo
393 // marker ii is left of the current transport position.
394 fNewFrame += ( fNextTick - fPassedTicks ) * fNextTickSize;
395
396#if TRANSPORT_POSITION_DEBUG
397 TP_DEBUGLOG( QString( "[segment] fTick: %1, fNewFrame: %2, fNextTick: %3, fRemainingTicks: %4, fPassedTicks: %5, fNextTickSize: %6, tempoMarkers[ ii - 1 ]->nColumn: %7, tempoMarkers[ ii - 1 ]->fBpm: %8, tick increment (fNextTick - fPassedTicks): %9, frame increment (fRemainingTicks * fNextTickSize): %10" )
398 .arg( fTick, 0, 'f' )
399 .arg( fNewFrame, 0, 'g', 30 )
400 .arg( fNextTick, 0, 'f' )
401 .arg( fRemainingTicks, 0, 'f' )
402 .arg( fPassedTicks, 0, 'f' )
403 .arg( fNextTickSize, 0, 'f' )
404 .arg( tempoMarkers[ ii - 1 ]->nColumn )
405 .arg( tempoMarkers[ ii - 1 ]->fBpm )
406 .arg( fNextTick - fPassedTicks, 0, 'f' )
407 .arg( ( fNextTick - fPassedTicks ) * fNextTickSize, 0, 'g', 30 )
408 );
409#endif
410
411 fRemainingTicks -= fNextTick - fPassedTicks;
412
413 fPassedTicks = fNextTick;
414
415 }
416 else {
417 handleEnd();
418 break;
419 }
420 }
421
422 if ( fRemainingTicks > 0 ) {
423 // The provided fTick is larger than the song. But,
424 // luckily, we just calculated the song length in
425 // frames (fNewFrame).
426 const int nRepetitions = std::floor(fTick / fSongSizeInTicks);
427 const double fSongSizeInFrames = fNewFrame;
428
429 fNewFrame *= static_cast<double>(nRepetitions);
430 fNewTick = std::fmod( fTick, fSongSizeInTicks );
431 fRemainingTicks = fNewTick;
432 fPassedTicks = 0;
433
434#if TRANSPORT_POSITION_DEBUG
435 TP_DEBUGLOG( QString( "[repeat] fTick: %1, fNewFrames: %2, fNewTick: %3, fRemainingTicks: %4, nRepetitions: %5, fSongSizeInTicks: %6, fSongSizeInFrames: %7" )
436 .arg( fTick, 0, 'g',30 )
437 .arg( fNewFrame, 0, 'g', 30 )
438 .arg( fNewTick, 0, 'g', 30 )
439 .arg( fRemainingTicks, 0, 'g', 30 )
440 .arg( nRepetitions )
441 .arg( fSongSizeInTicks, 0, 'g', 30 )
442 .arg( fSongSizeInFrames, 0, 'g', 30 )
443 );
444#endif
445
446 if ( std::isinf( fNewFrame ) ||
447 static_cast<long long>(fNewFrame) >
448 std::numeric_limits<long long>::max() ) {
449 ERRORLOG( QString( "Provided ticks [%1] are too large." ).arg( fTick ) );
450 return 0;
451 }
452
453 // The target tick matches a multiple of the song
454 // size. We need to reproduce the context within the
455 // last tempo marker in order to get the mismatch
456 // right.
457 if ( fRemainingTicks == 0 ) {
458 ii = tempoMarkers.size();
459 fNextTick = static_cast<double>(pHydrogen->getTickForColumn(
460 tempoMarkers[ 0 ]->nColumn ) );
462 nSampleRate, tempoMarkers[ ii - 1 ]->fBpm, nResolution );
463
464 handleEnd();
465 }
466 }
467 }
468 }
469 else {
470 // There may be neither Timeline nor Song.
471
472 // As the timeline is not activate, the column passed is of no
473 // importance. But we harness the ability of getBpmAtColumn()
474 // to collect and choose between tempo information gathered
475 // from various sources.
476 const float fBpm = AudioEngine::getBpmAtColumn( 0 );
477
478 const double fTickSize =
479 AudioEngine::computeDoubleTickSize( nSampleRate, fBpm,
480 nResolution );
481
482 // Single tempo for the whole song.
483 const double fNewFrame = static_cast<double>(fTick) *
484 fTickSize;
485 nNewFrame = static_cast<long long>( std::round( fNewFrame ) );
486 *fTickMismatch = ( fNewFrame - static_cast<double>(nNewFrame ) ) /
487 fTickSize;
488
489#if TRANSPORT_POSITION_DEBUG
490 TP_DEBUGLOG(QString("[no-timeline] nNewFrame: %1, fTick: %2, fTickSize: %3, fTickMismatch: %4" )
491 .arg( nNewFrame ).arg( fTick, 0, 'f' ).arg( fTickSize, 0, 'f' )
492 .arg( *fTickMismatch, 0, 'g', 30 ));
493#endif
494
495 }
496
497 return nNewFrame;
498}
499
500// This function uses the assumption that sample rate and resolution
501// are constant over the whole song.
502double TransportPosition::computeTickFromFrame( const long long nFrame, int nSampleRate ) {
503 const auto pHydrogen = Hydrogen::get_instance();
504
505 if ( nFrame < 0 ) {
506 ERRORLOG( QString( "Provided frame [%1] must be non-negative" ).arg( nFrame ) );
507 }
508
509 const auto pSong = pHydrogen->getSong();
510 const auto pTimeline = pHydrogen->getTimeline();
511 const auto pAudioEngine = pHydrogen->getAudioEngine();
512 const auto pAudioDriver = pHydrogen->getAudioOutput();
513
514 if ( pAudioDriver == nullptr ) {
515 ERRORLOG( "AudioDriver is not ready!" );
516 return 0;
517 }
518
519 if ( nSampleRate == 0 ) {
520 nSampleRate = pAudioDriver->getSampleRate();
521 }
522
523 int nResolution;
524 if ( pSong != nullptr ) {
525 nResolution = pSong->getResolution();
526 } else {
527 nResolution = Song::nDefaultResolution;
528 }
529
530 double fTick = 0;
531
532 const double fSongSizeInTicks = pAudioEngine->getSongSizeInTicks();
533
534 if ( nSampleRate == 0 || nResolution == 0 ) {
535 ERRORLOG( "Not properly initialized yet" );
536 return fTick;
537 }
538
539 if ( nFrame == 0 ) {
540 return fTick;
541 }
542
543 std::vector<std::shared_ptr<const Timeline::TempoMarker>> tempoMarkers;
544 bool bSpecialFirstMarker = false;
545 if ( pTimeline != nullptr ) {
546 tempoMarkers = pTimeline->getAllTempoMarkers();
547 bSpecialFirstMarker = pTimeline->isFirstTempoMarkerSpecial();
548 }
549
550 int nColumns = 0;
551 if ( pSong != nullptr ) {
552 nColumns = pSong->getPatternGroupVector()->size();
553 }
554
555 // If there are no patterns in the current, we treat song mode
556 // like pattern mode.
557 if ( pHydrogen->isTimelineEnabled() &&
558 ! ( tempoMarkers.size() == 1 && bSpecialFirstMarker ) &&
559 pHydrogen->getMode() == Song::Mode::Song && nColumns > 0 ) {
560
561 // We are using double precision in here to avoid rounding
562 // errors.
563 const double fTargetFrame = static_cast<double>(nFrame);
564 double fPassedFrames = 0;
565 double fNextFrame = 0;
566 double fNextTicks, fPassedTicks = 0;
567 double fNextTickSize;
568 long long nRemainingFrames;
569
570 const int nColumns = pSong->getPatternGroupVector()->size();
571
572 while ( fPassedFrames < fTargetFrame ) {
573
574 for ( int ii = 1; ii <= tempoMarkers.size(); ++ii ) {
575
576 fNextTickSize =
578 tempoMarkers[ ii - 1 ]->fBpm,
579 nResolution );
580
581 if ( ii == tempoMarkers.size() ||
582 tempoMarkers[ ii ]->nColumn >= nColumns ) {
583 fNextTicks = fSongSizeInTicks;
584 } else {
585 fNextTicks =
586 static_cast<double>(pHydrogen->getTickForColumn( tempoMarkers[ ii ]->nColumn ));
587 }
588 fNextFrame = (fNextTicks - fPassedTicks) * fNextTickSize;
589
590 if ( fNextFrame < ( fTargetFrame -
591 fPassedFrames ) ) {
592
593#if TRANSPORT_POSITION_DEBUG
594 TP_DEBUGLOG(QString( "[segment] nFrame: %1, fTick: %2, nSampleRate: %3, fNextTickSize: %4, fNextTicks: %5, fNextFrame: %6, tempoMarkers[ ii -1 ]->nColumn: %7, tempoMarkers[ ii -1 ]->fBpm: %8, fPassedTicks: %9, fPassedFrames: %10, fNewTick (tick increment): %11, fNewTick * fNextTickSize (frame increment): %12" )
595 .arg( nFrame )
596 .arg( fTick, 0, 'f' )
597 .arg( nSampleRate )
598 .arg( fNextTickSize, 0, 'f' )
599 .arg( fNextTicks, 0, 'f' )
600 .arg( fNextFrame, 0, 'f' )
601 .arg( tempoMarkers[ ii -1 ]->nColumn )
602 .arg( tempoMarkers[ ii -1 ]->fBpm )
603 .arg( fPassedTicks, 0, 'f' )
604 .arg( fPassedFrames, 0, 'f' )
605 .arg( fNextTicks - fPassedTicks, 0, 'f' )
606 .arg( (fNextTicks - fPassedTicks) * fNextTickSize, 0, 'g', 30 )
607 );
608#endif
609
610 // The whole segment of the timeline covered by tempo
611 // marker ii is left of the transport position.
612 fTick += fNextTicks - fPassedTicks;
613
614 fPassedFrames += fNextFrame;
615 fPassedTicks = fNextTicks;
616
617 } else {
618 // The target frame is located within a segment.
619 const double fNewTick = (fTargetFrame - fPassedFrames ) /
620 fNextTickSize;
621
622 fTick += fNewTick;
623
624#if TRANSPORT_POSITION_DEBUG
625 TP_DEBUGLOG(QString( "[end] nFrame: %1, fTick: %2, nSampleRate: %3, fNextTickSize: %4, fNextTicks: %5, fNextFrame: %6, tempoMarkers[ ii -1 ]->nColumn: %7, tempoMarkers[ ii -1 ]->fBpm: %8, fPassedTicks: %9, fPassedFrames: %10, fNewTick (tick increment): %11, fNewTick * fNextTickSize (frame increment): %12" )
626 .arg( nFrame )
627 .arg( fTick, 0, 'f' )
628 .arg( nSampleRate )
629 .arg( fNextTickSize, 0, 'f' )
630 .arg( fNextTicks, 0, 'f' )
631 .arg( fNextFrame, 0, 'f' )
632 .arg( tempoMarkers[ ii -1 ]->nColumn )
633 .arg( tempoMarkers[ ii -1 ]->fBpm )
634 .arg( fPassedTicks, 0, 'f' )
635 .arg( fPassedFrames, 0, 'f' )
636 .arg( fNewTick, 0, 'f' )
637 .arg( fNewTick * fNextTickSize, 0, 'g', 30 )
638 );
639#endif
640
641 fPassedFrames = fTargetFrame;
642
643 break;
644 }
645 }
646
647 if ( fPassedFrames != fTargetFrame ) {
648 // The provided nFrame is larger than the song. But,
649 // luckily, we just calculated the song length in
650 // frames.
651 const double fSongSizeInFrames = fPassedFrames;
652 const int nRepetitions = std::floor(fTargetFrame / fSongSizeInFrames);
653 if ( fSongSizeInTicks * nRepetitions >
654 std::numeric_limits<double>::max() ) {
655 ERRORLOG( QString( "Provided frames [%1] are too large." ).arg( nFrame ) );
656 return 0;
657 }
658 fTick = fSongSizeInTicks * nRepetitions;
659
660 fPassedFrames = static_cast<double>(nRepetitions) *
661 fSongSizeInFrames;
662 fPassedTicks = 0;
663
664#if TRANSPORT_POSITION_DEBUG
665 TP_DEBUGLOG( QString( "[repeat] frames covered: %1, frames remaining: %2, ticks covered: %3, nRepetitions: %4, fSongSizeInFrames: %5, fSongSizeInTicks: %6" )
666 .arg( fPassedFrames, 0, 'g', 30 )
667 .arg( fTargetFrame - fPassedFrames, 0, 'g', 30 )
668 .arg( fTick, 0, 'g', 30 )
669 .arg( nRepetitions )
670 .arg( fSongSizeInFrames, 0, 'g', 30 )
671 .arg( fSongSizeInTicks, 0, 'g', 30 )
672 );
673#endif
674
675 }
676 }
677 }
678 else {
679 // There may be neither Timeline nor Song.
680
681 // As the timeline is not activate, the column passed is of no
682 // importance. But we harness the ability of getBpmAtColumn()
683 // to collect and choose between tempo information gathered
684 // from various sources.
685 const float fBpm = AudioEngine::getBpmAtColumn( 0 );
686 const double fTickSize =
687 AudioEngine::computeDoubleTickSize( nSampleRate, fBpm,
688 nResolution );
689
690 // Single tempo for the whole song.
691 fTick = static_cast<double>(nFrame) / fTickSize;
692
693#if TRANSPORT_POSITION_DEBUG
694 TP_DEBUGLOG(QString( "[no timeline] nFrame: %1, sampleRate: %2, tickSize: %3" )
695 .arg( nFrame ).arg( nSampleRate ).arg( fTickSize, 0, 'f' ) );
696#endif
697
698 }
699
700 return fTick;
701}
702
703long long TransportPosition::computeFrame( double fTick, float fTickSize ) {
704 return std::round( fTick * fTickSize );
705}
706
707double TransportPosition::computeTick( long long nFrame, float fTickSize ) {
708 return nFrame / fTickSize;
709}
710
711bool operator==( std::shared_ptr<TransportPosition> pLhs,
712 std::shared_ptr<TransportPosition> pRhs ) {
713 if ( ( pLhs->m_pPlayingPatterns != nullptr &&
714 pRhs->m_pPlayingPatterns == nullptr ) ||
715 ( pLhs->m_pPlayingPatterns == nullptr &&
716 pRhs->m_pPlayingPatterns != nullptr ) ) {
717 return false;
718 }
719 else if ( pLhs->m_pPlayingPatterns != nullptr &&
720 pRhs->m_pPlayingPatterns != nullptr &&
721 *pLhs->m_pPlayingPatterns != *pRhs->m_pPlayingPatterns ) {
722 return false;
723 }
724
725 if ( ( pLhs->m_pNextPatterns != nullptr &&
726 pRhs->m_pNextPatterns == nullptr ) ||
727 ( pLhs->m_pNextPatterns == nullptr &&
728 pRhs->m_pNextPatterns != nullptr ) ) {
729 return false;
730 }
731 else if ( pLhs->m_pNextPatterns != nullptr &&
732 pRhs->m_pNextPatterns != nullptr &&
733 *pLhs->m_pNextPatterns != *pRhs->m_pNextPatterns ) {
734 return false;
735 }
736
737 return (
738 pLhs->m_nFrame == pRhs->m_nFrame &&
739 std::abs( pLhs->m_fTick - pRhs->m_fTick ) < 1E-5 &&
740 std::abs( pLhs->m_fTickSize - pRhs->m_fTickSize ) < 1E-2 &&
741 std::abs( pLhs->m_fBpm - pRhs->m_fBpm ) < 1E-2 &&
742 pLhs->m_nPatternStartTick == pRhs->m_nPatternStartTick &&
743 pLhs->m_nPatternTickPosition == pRhs->m_nPatternTickPosition &&
744 pLhs->m_nColumn == pRhs->m_nColumn &&
745 std::abs( pLhs->m_fTickMismatch - pRhs->m_fTickMismatch ) < 1E-5 &&
746 pLhs->m_nFrameOffsetTempo == pRhs->m_nFrameOffsetTempo &&
747 std::abs( pLhs->m_fTickOffsetQueuing -
748 pRhs->m_fTickOffsetQueuing ) < 1E-5 &&
749 std::abs( pLhs->m_fTickOffsetSongSize -
750 pRhs->m_fTickOffsetSongSize ) < 1E-5 &&
751 pLhs->m_nPatternSize == pRhs->m_nPatternSize &&
752 pLhs->m_nLastLeadLagFactor == pRhs->m_nLastLeadLagFactor &&
753 pLhs->m_nBar == pRhs->m_nBar &&
754 pLhs->m_nBeat == pRhs->m_nBeat );
755}
756
757bool operator!=( std::shared_ptr<TransportPosition> pLhs,
758 std::shared_ptr<TransportPosition> pRhs ) {
759 if ( ( pLhs->m_pPlayingPatterns != nullptr &&
760 pRhs->m_pPlayingPatterns == nullptr ) ||
761 ( pLhs->m_pPlayingPatterns == nullptr &&
762 pRhs->m_pPlayingPatterns != nullptr ) ) {
763 return true;
764 }
765 else if ( pLhs->m_pPlayingPatterns != nullptr &&
766 pRhs->m_pPlayingPatterns != nullptr &&
767 *pLhs->m_pPlayingPatterns != *pRhs->m_pPlayingPatterns ) {
768 return true;
769 }
770
771 if ( ( pLhs->m_pNextPatterns != nullptr &&
772 pRhs->m_pNextPatterns == nullptr ) ||
773 ( pLhs->m_pNextPatterns == nullptr &&
774 pRhs->m_pNextPatterns != nullptr ) ) {
775 return true;
776 }
777 else if ( pLhs->m_pNextPatterns != nullptr &&
778 pRhs->m_pNextPatterns != nullptr &&
779 *pLhs->m_pNextPatterns != *pRhs->m_pNextPatterns ) {
780 return true;
781 }
782
783
784 return (
785 pLhs->m_nFrame != pRhs->m_nFrame ||
786 std::abs( pLhs->m_fTick - pRhs->m_fTick ) > 1E-5 ||
787 std::abs( pLhs->m_fTickSize - pRhs->m_fTickSize ) > 1E-2 ||
788 std::abs( pLhs->m_fBpm - pRhs->m_fBpm ) > 1E-2 ||
789 pLhs->m_nPatternStartTick != pRhs->m_nPatternStartTick ||
790 pLhs->m_nPatternTickPosition != pRhs->m_nPatternTickPosition ||
791 pLhs->m_nColumn != pRhs->m_nColumn ||
792 std::abs( pLhs->m_fTickMismatch - pRhs->m_fTickMismatch ) > 1E-5 ||
793 pLhs->m_nFrameOffsetTempo != pRhs->m_nFrameOffsetTempo ||
794 std::abs( pLhs->m_fTickOffsetQueuing -
795 pRhs->m_fTickOffsetQueuing ) > 1E-5 ||
796 std::abs( pLhs->m_fTickOffsetSongSize -
797 pRhs->m_fTickOffsetSongSize ) > 1E-5 ||
798 pLhs->m_nPatternSize != pRhs->m_nPatternSize ||
799 pLhs->m_nLastLeadLagFactor != pRhs->m_nLastLeadLagFactor ||
800 pLhs->m_nBar != pRhs->m_nBar ||
801 pLhs->m_nBeat != pRhs->m_nBeat );
802}
803
804QString TransportPosition::toQString( const QString& sPrefix, bool bShort ) const {
805 QString s = Base::sPrintIndention;
806 QString sOutput;
807 if ( ! bShort ) {
808 sOutput = QString( "%1[TransportPosition]\n" ).arg( sPrefix )
809 .append( QString( "%1%2m_sLabel: %3\n" ).arg( sPrefix ).arg( s ).arg( m_sLabel ) )
810 .append( QString( "%1%2m_nFrame: %3\n" ).arg( sPrefix ).arg( s ).arg( getFrame() ) )
811 .append( QString( "%1%2m_fTick: %3\n" ).arg( sPrefix ).arg( s ).arg( getDoubleTick(), 0, 'f' ) )
812 .append( QString( "%1%2m_fTick (rounded): %3\n" ).arg( sPrefix ).arg( s ).arg( getTick() ) )
813 .append( QString( "%1%2m_fTickSize: %3\n" ).arg( sPrefix ).arg( s ).arg( getTickSize(), 0, 'f' ) )
814 .append( QString( "%1%2m_fBpm: %3\n" ).arg( sPrefix ).arg( s ).arg( getBpm(), 0, 'f' ) )
815 .append( QString( "%1%2m_nPatternStartTick: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nPatternStartTick ) )
816 .append( QString( "%1%2m_nPatternTickPosition: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nPatternTickPosition ) )
817 .append( QString( "%1%2m_nColumn: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nColumn ) )
818 .append( QString( "%1%2m_fTickMismatch: %3\n" ).arg( sPrefix ).arg( s ).arg( m_fTickMismatch, 0, 'f' ) )
819 .append( QString( "%1%2m_nFrameOffsetTempo: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nFrameOffsetTempo ) )
820 .append( QString( "%1%2m_fTickOffsetQueuing: %3\n" ).arg( sPrefix ).arg( s ).arg( m_fTickOffsetQueuing, 0, 'f' ) )
821 .append( QString( "%1%2m_fTickOffsetSongSize: %3\n" ).arg( sPrefix ).arg( s ).arg( m_fTickOffsetSongSize, 0, 'f' ) );
822 if ( m_pPlayingPatterns != nullptr ) {
823 sOutput.append( QString( "%1%2m_pPlayingPatterns: %3\n" ).arg( sPrefix ).arg( s ).arg( m_pPlayingPatterns->toQString( sPrefix + s ), bShort ) );
824 }
825 if ( m_pNextPatterns != nullptr ) {
826 sOutput.append( QString( "%1%2m_pNextPatterns: %3\n" ).arg( sPrefix ).arg( s ).arg( m_pNextPatterns->toQString( sPrefix + s ), bShort ) );
827 }
828 sOutput.append( QString( "%1%2m_nPatternSize: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nPatternSize ) )
829 .append( QString( "%1%2m_nLastLeadLagFactor: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nLastLeadLagFactor ) )
830 .append( QString( "%1%2m_nBar: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nBar ) )
831 .append( QString( "%1%2m_nBeat: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nBeat ) );
832 }
833 else {
834 sOutput = QString( "%1[TransportPosition]" ).arg( sPrefix )
835 .append( QString( " m_sLabel: %1" ).arg( m_sLabel ) )
836 .append( QString( ", m_nFrame: %1" ).arg( getFrame() ) )
837 .append( QString( ", m_fTick: %1" ).arg( getDoubleTick(), 0, 'f' ) )
838 .append( QString( ", m_fTick (rounded): %1" ).arg( getTick() ) )
839 .append( QString( ", m_fTickSize: %1" ).arg( getTickSize(), 0, 'f' ) )
840 .append( QString( ", m_fBpm: %1" ).arg( getBpm(), 0, 'f' ) )
841 .append( QString( ", m_nPatternStartTick: %1" ).arg( m_nPatternStartTick ) )
842 .append( QString( ", m_nPatternTickPosition: %1" ).arg( m_nPatternTickPosition ) )
843 .append( QString( ", m_nColumn: %1" ).arg( m_nColumn ) )
844 .append( QString( ", m_fTickMismatch: %1" ).arg( m_fTickMismatch, 0, 'f' ) )
845 .append( QString( ", m_nFrameOffsetTempo: %1" ).arg( m_nFrameOffsetTempo ) )
846 .append( QString( ", m_fTickOffsetQueuing: %1" ).arg( m_fTickOffsetQueuing, 0, 'f' ) )
847 .append( QString( ", m_fTickOffsetSongSize: %1" ).arg( m_fTickOffsetSongSize, 0, 'f' ) );
848 if ( m_pPlayingPatterns != nullptr ) {
849 sOutput.append( QString( ", m_pPlayingPatterns: %1" ).arg( m_pPlayingPatterns->toQString( sPrefix + s ), bShort ) );
850 }
851 if ( m_pNextPatterns != nullptr ) {
852 sOutput.append( QString( ", m_pNextPatterns: %1" ).arg( m_pNextPatterns->toQString( sPrefix + s ), bShort ) );
853 }
854 sOutput.append( QString( ", m_nPatternSize: %1" ).arg( m_nPatternSize ) )
855 .append( QString( ", m_nLastLeadLagFactor: %1" ).arg( m_nLastLeadLagFactor ) )
856 .append( QString( ", m_nBar: %1" ).arg( m_nBar ) )
857 .append( QString( ", m_nBeat: %1" ).arg( m_nBeat ) );
858
859 }
860
861 return sOutput;
862}
863
864};
#define ERRORLOG(x)
Definition Object.h:242
#define TP_DEBUGLOG(x)
static float getBpmAtColumn(int nColumn)
static double computeDoubleTickSize(const int nSampleRate, const float fBpm, const int nResolution)
static QString sPrintIndention
String used to format the debugging string output of some core classes.
Definition Object.h:127
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
void recalculateRubberband(float fBpm)
Recalculates all Samples using RubberBand for a specific tempo fBpm.
PatternList is a collection of patterns.
Definition PatternList.h:43
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
static constexpr int nDefaultResolution
Definition Song.h:135
double m_fTickOffsetQueuing
Tick offset introduced when changing the tempo of the song.
long long m_nFrameOffsetTempo
Frame offset introduced when changing the tempo of the song, switching to Timeline,...
TransportPosition(const QString sLabel="")
void setPatternStartTick(long nPatternStartTick)
long m_nPatternTickPosition
Ticks passed since m_nPatternStartTick.
double m_fTickMismatch
Number of ticks m_nFrame is ahead/behind of m_fTick.
int m_nColumn
Specifies the column transport is located in and can be used as the index of the current PatternList/...
long m_nPatternStartTick
Dicstance in ticks between the beginning of the song and the beginning of the current column (m_nColu...
static double computeTick(long long nFrame, float fTickSize)
Converts frames into ticks under the assumption of a constant fTickSize (sample rate,...
static long long computeFrameFromTick(double fTick, double *fTickMismatch, int nSampleRate=0)
Calculates frame equivalent of fTick.
float m_fBpm
Current tempo in beats per minute.
int m_nBeat
Last bar passed since m_nBar.
long getTick() const
Retrieve a rounded version of m_fTick.
void setTick(double fNewTick)
void setPatternTickPosition(long nPatternTickPosition)
void set(std::shared_ptr< TransportPosition > pOther)
Copying the content of one position into the other is a lot cheaper than performing computations,...
void setFrame(long long nNewFrame)
int m_nPatternSize
Maximum size of all patterns in m_pPlayingPatterns.
int m_nBar
Last beat (column + 1) passed.
PatternList * m_pPlayingPatterns
Contains all Patterns currently played back.
void setTickSize(float fNewTickSize)
static double computeTickFromFrame(long long nFrame, int nSampleRate=0)
Calculates tick equivalent of nFrame.
long long m_nFrame
Current transport position in number of frames since the beginning of the song.
double m_fTickOffsetSongSize
Tick offset introduced when changing the size of the song.
float m_fTickSize
Number of frames that make up one tick.
QString toQString(const QString &sPrefix="", bool bShort=true) const override
Formatted string version for debugging purposes.
double m_fTick
Current transport position in number of ticks since the beginning of the song.
void setPatternSize(int nPatternSize)
static long long computeFrame(double fTick, float fTickSize)
Converts ticks into frames under the assumption of a constant fTickSize (sample rate,...
PatternList * m_pNextPatterns
Patterns used to toggle the ones in m_pPlayingPatterns in Song::PatternMode::Stacked.
const QString m_sLabel
Identifier of the transport position.
#define MAX_NOTES
Maximum number of notes.
Definition config.dox:79
#define MAX_BPM
Definition Globals.h:36
#define MIN_BPM
Definition Globals.h:35
bool operator!=(std::shared_ptr< TransportPosition > pLhs, std::shared_ptr< TransportPosition > pRhs)
bool operator==(std::shared_ptr< TransportPosition > pLhs, std::shared_ptr< TransportPosition > pRhs)