hydrogen 1.2.3
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-2024 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
33namespace H2Core {
34
36 : m_sLabel( sLabel )
37{
42
43 reset();
44}
45
50
51void TransportPosition::set( std::shared_ptr<TransportPosition> pOther ) {
52 m_nFrame = pOther->m_nFrame;
53 m_fTick = pOther->m_fTick;
54 m_fTickSize = pOther->m_fTickSize;
55 m_fBpm = pOther->m_fBpm;
56 m_nPatternStartTick = pOther->m_nPatternStartTick;
57 m_nPatternTickPosition = pOther->m_nPatternTickPosition;
58 m_nColumn = pOther->m_nColumn;
59 m_fTickMismatch = pOther->m_fTickMismatch;
60 m_nFrameOffsetTempo = pOther->m_nFrameOffsetTempo;
61 m_fTickOffsetQueuing = pOther->m_fTickOffsetQueuing;
62 m_fTickOffsetSongSize = pOther->m_fTickOffsetSongSize;
63
65 for ( const auto ppattern : *pOther->m_pPlayingPatterns ) {
66 if ( ppattern != nullptr ) {
67 m_pPlayingPatterns->add( ppattern );
68 ppattern->addFlattenedVirtualPatterns( m_pPlayingPatterns );
69 }
70 }
72 for ( const auto ppattern : *pOther->m_pNextPatterns ) {
73 if ( ppattern != nullptr ) {
74 m_pNextPatterns->add( ppattern );
75 ppattern->addFlattenedVirtualPatterns( m_pNextPatterns );
76 }
77 }
78 m_nPatternSize = pOther->m_nPatternSize;
79 m_nLastLeadLagFactor = pOther->m_nLastLeadLagFactor;
80 m_nBar = pOther->m_nBar;
81 m_nBeat = pOther->m_nBeat;
82}
83
104
105void TransportPosition::setBpm( float fNewBpm ) {
106 if ( fNewBpm > MAX_BPM ) {
107 ERRORLOG( QString( "[%1] Provided bpm [%2] is too high. Assigning upper bound %3 instead" )
108 .arg( m_sLabel ).arg( fNewBpm ).arg( MAX_BPM ) );
109 fNewBpm = MAX_BPM;
110 } else if ( fNewBpm < MIN_BPM ) {
111 ERRORLOG( QString( "[%1] Provided bpm [%2] is too low. Assigning lower bound %3 instead" )
112 .arg( m_sLabel ).arg( fNewBpm ).arg( MIN_BPM ) );
113 fNewBpm = MIN_BPM;
114 }
115
116 m_fBpm = fNewBpm;
117
118 if ( Preferences::get_instance()->getRubberBandBatchMode() ) {
120 }
121}
122
123void TransportPosition::setFrame( long long nNewFrame ) {
124 if ( nNewFrame < 0 ) {
125 ERRORLOG( QString( "[%1] Provided frame [%2] is negative. Setting frame 0 instead." )
126 .arg( m_sLabel ).arg( nNewFrame ) );
127 nNewFrame = 0;
128 }
129
130 m_nFrame = nNewFrame;
131}
132
133void TransportPosition::setTick( double fNewTick ) {
134 if ( fNewTick < 0 ) {
135 ERRORLOG( QString( "[%1] Provided tick [%2] is negative. Setting frame 0 instead." )
136 .arg( m_sLabel ).arg( fNewTick ) );
137 fNewTick = 0;
138 }
139
140 m_fTick = fNewTick;
141}
142
143void TransportPosition::setTickSize( float fNewTickSize ) {
144 if ( fNewTickSize <= 0 ) {
145 ERRORLOG( QString( "[%1] Provided tick size [%2] is too small. Using 400 as a fallback instead." )
146 .arg( m_sLabel ).arg( fNewTickSize ) );
147 fNewTickSize = 400;
148 }
149
150 m_fTickSize = fNewTickSize;
151}
152
153void TransportPosition::setPatternStartTick( long nPatternStartTick ) {
154 if ( nPatternStartTick < 0 ) {
155 ERRORLOG( QString( "[%1] Provided tick [%2] is negative. Setting frame 0 instead." )
156 .arg( m_sLabel ).arg( nPatternStartTick ) );
157 nPatternStartTick = 0;
158 }
159
160 m_nPatternStartTick = nPatternStartTick;
161}
162
163void TransportPosition::setPatternTickPosition( long nPatternTickPosition ) {
164 if ( nPatternTickPosition < 0 ) {
165 ERRORLOG( QString( "[%1] Provided tick [%2] is negative. Setting frame 0 instead." )
166 .arg( m_sLabel ).arg( nPatternTickPosition ) );
167 nPatternTickPosition = 0;
168 }
169
170 m_nPatternTickPosition = nPatternTickPosition;
171}
172
173void TransportPosition::setColumn( int nColumn ) {
174 if ( nColumn < -1 ) {
175 ERRORLOG( QString( "[%1] Provided column [%2] it too small. Using [-1] as a fallback instead." )
176 .arg( m_sLabel ).arg( nColumn ) );
177 nColumn = -1;
178 }
179
180 m_nColumn = nColumn;
181}
182
183
184void TransportPosition::setPatternSize( int nPatternSize ) {
185 if ( nPatternSize < 0 ) {
186 ERRORLOG( QString( "[%1] Provided pattern size [%2] it too small. Using [0] as a fallback instead." )
187 .arg( m_sLabel ).arg( nPatternSize ) );
188 nPatternSize = 0;
189 }
190
191 m_nPatternSize = nPatternSize;
192}
194 if ( nBar < 1 ) {
195 ERRORLOG( QString( "[%1] Provided bar [%2] it too small. Using [1] as a fallback instead." )
196 .arg( m_sLabel ).arg( nBar ) );
197 nBar = 1;
198 }
199 m_nBar = nBar;
200}
201
202void TransportPosition::setBeat( int nBeat ) {
203 if ( nBeat < 1 ) {
204 ERRORLOG( QString( "[%1] Provided beat [%2] it too small. Using [1] as a fallback instead." )
205 .arg( m_sLabel ).arg( nBeat ) );
206 nBeat = 1;
207 }
208 m_nBeat = nBeat;
209}
210
211// This function uses the assumption that sample rate and resolution
212// are constant over the whole song.
213long long TransportPosition::computeFrameFromTick( const double fTick, double* fTickMismatch, int nSampleRate ) {
214
215 const auto pHydrogen = Hydrogen::get_instance();
216 const auto pSong = pHydrogen->getSong();
217 const auto pTimeline = pHydrogen->getTimeline();
218 const auto pAudioEngine = pHydrogen->getAudioEngine();
219 const auto pAudioDriver = pHydrogen->getAudioOutput();
220
221 if ( pSong == nullptr || pTimeline == nullptr ) {
222 ERRORLOG( "Invalid song" );
223 *fTickMismatch = 0;
224 return 0;
225 }
226 if ( pAudioDriver == nullptr ) {
227 ERRORLOG( "AudioDriver is not ready!" );
228 *fTickMismatch = 0;
229 return 0;
230 }
231
232 if ( nSampleRate == 0 ) {
233 nSampleRate = pAudioDriver->getSampleRate();
234 }
235 const int nResolution = pSong->getResolution();
236 const double fSongSizeInTicks = pAudioEngine->getSongSizeInTicks();
237
238 if ( nSampleRate == 0 || nResolution == 0 ) {
239 ERRORLOG( "Not properly initialized yet" );
240 *fTickMismatch = 0;
241 return 0;
242 }
243
244 if ( fTick == 0 ) {
245 *fTickMismatch = 0;
246 return 0;
247 }
248
249 const auto tempoMarkers = pTimeline->getAllTempoMarkers();
250
251 // If there are no patterns in the current, we treat song mode
252 // like pattern mode.
253 long long nNewFrame = 0;
254 if ( pHydrogen->isTimelineEnabled() &&
255 ! ( tempoMarkers.size() == 1 &&
256 pTimeline->isFirstTempoMarkerSpecial() ) &&
257 pHydrogen->getMode() == Song::Mode::Song &&
258 pSong->getPatternGroupVector()->size() > 0 ) {
259
260 double fNewTick = fTick;
261 double fRemainingTicks = fTick;
262 double fNextTick, fPassedTicks = 0;
263 double fNextTickSize;
264 double fNewFrame = 0;
265 int ii;
266
267 const int nColumns = pSong->getPatternGroupVector()->size();
268
269 auto handleEnd = [&]() {
270 // The next frame is within this segment.
271 fNewFrame += fRemainingTicks * fNextTickSize;
272
273 nNewFrame = static_cast<long long>( std::round( fNewFrame ) );
274
275 // Keep track of the rounding error to be able to switch
276 // between fTick and its frame counterpart later on. In
277 // case fTick is located close to a tempo marker we will
278 // only cover the part up to the tempo marker in here as
279 // only this region is governed by fNextTickSize.
280 const double fRoundingErrorInTicks =
281 ( fNewFrame - static_cast<double>( nNewFrame ) ) /
282 fNextTickSize;
283
284 // Compares the negative distance between current position
285 // (fNewFrame) and the one resulting from rounding -
286 // fRoundingErrorInTicks - with the negative distance
287 // between current position (fNewFrame) and location of
288 // next tempo marker.
289 if ( fRoundingErrorInTicks >
290 fPassedTicks + fRemainingTicks - fNextTick ) {
291 // Whole mismatch located within the current tempo
292 // interval.
293 *fTickMismatch = fRoundingErrorInTicks;
294
295 }
296 else {
297 // Mismatch at this side of the tempo marker.
298 *fTickMismatch = fPassedTicks + fRemainingTicks - fNextTick;
299
300 const double fFinalFrame = fNewFrame +
301 ( fNextTick - fPassedTicks - fRemainingTicks ) * fNextTickSize;
302
303 // Mismatch located beyond the tempo marker.
304 double fFinalTickSize;
305 if ( ii < tempoMarkers.size() ) {
306 fFinalTickSize = AudioEngine::computeDoubleTickSize(
307 nSampleRate, tempoMarkers[ ii ]->fBpm, nResolution );
308 }
309 else {
310 fFinalTickSize = AudioEngine::computeDoubleTickSize(
311 nSampleRate, tempoMarkers[ 0 ]->fBpm, nResolution );
312 }
313
314 // 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" )
315 // .arg( fPassedTicks + fRemainingTicks - fNextTick )
316 // .arg( ( fFinalFrame - static_cast<double>(nNewFrame) ) / fNextTickSize )
317 // .arg( nNewFrame )
318 // .arg( fNewFrame, 0, 'f' )
319 // .arg( fFinalFrame, 0, 'f' )
320 // .arg( fNextTickSize, 0, 'f' )
321 // .arg( fPassedTicks, 0, 'f' )
322 // .arg( fRemainingTicks, 0, 'f' )
323 // .arg( fFinalTickSize, 0, 'f' ));
324
325 *fTickMismatch += ( fFinalFrame - static_cast<double>(nNewFrame) ) /
326 fFinalTickSize;
327 }
328
329 // 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" )
330 // .arg( fTick, 0, 'f' )
331 // .arg( fNewFrame, 0, 'g', 30 )
332 // .arg( fNextTick, 0, 'f' )
333 // .arg( fRemainingTicks, 0, 'f' )
334 // .arg( fPassedTicks, 0, 'f' )
335 // .arg( fNextTickSize, 0, 'f' )
336 // .arg( tempoMarkers[ ii - 1 ]->nColumn )
337 // .arg( tempoMarkers[ ii - 1 ]->fBpm )
338 // .arg( nNewFrame )
339 // .arg( *fTickMismatch, 0, 'g', 30 )
340 // .arg( fRemainingTicks * fNextTickSize, 0, 'g', 30 )
341 // .arg( fRoundingErrorInTicks, 0, 'f' )
342 // );
343
344 fRemainingTicks -= fNewTick - fPassedTicks;
345 };
346
347 while ( fRemainingTicks > 0 ) {
348
349 for ( ii = 1; ii <= tempoMarkers.size(); ++ii ) {
350 if ( ii == tempoMarkers.size() ||
351 tempoMarkers[ ii ]->nColumn >= nColumns ) {
352 fNextTick = fSongSizeInTicks;
353 } else {
354 fNextTick =
355 static_cast<double>(pHydrogen->getTickForColumn( tempoMarkers[ ii ]->nColumn ) );
356 }
357
358 fNextTickSize =
360 tempoMarkers[ ii - 1 ]->fBpm,
361 nResolution );
362
363 if ( fRemainingTicks > ( fNextTick - fPassedTicks ) ) {
364 // The whole segment of the timeline covered by tempo
365 // marker ii is left of the current transport position.
366 fNewFrame += ( fNextTick - fPassedTicks ) * fNextTickSize;
367
368 // 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" )
369 // .arg( fTick, 0, 'f' )
370 // .arg( fNewFrame, 0, 'g', 30 )
371 // .arg( fNextTick, 0, 'f' )
372 // .arg( fRemainingTicks, 0, 'f' )
373 // .arg( fPassedTicks, 0, 'f' )
374 // .arg( fNextTickSize, 0, 'f' )
375 // .arg( tempoMarkers[ ii - 1 ]->nColumn )
376 // .arg( tempoMarkers[ ii - 1 ]->fBpm )
377 // .arg( fNextTick - fPassedTicks, 0, 'f' )
378 // .arg( ( fNextTick - fPassedTicks ) * fNextTickSize, 0, 'g', 30 )
379 // );
380
381 fRemainingTicks -= fNextTick - fPassedTicks;
382
383 fPassedTicks = fNextTick;
384
385 }
386 else {
387 handleEnd();
388 break;
389 }
390 }
391
392 if ( fRemainingTicks > 0 ) {
393 // The provided fTick is larger than the song. But,
394 // luckily, we just calculated the song length in
395 // frames (fNewFrame).
396 const int nRepetitions = std::floor(fTick / fSongSizeInTicks);
397 const double fSongSizeInFrames = fNewFrame;
398
399 fNewFrame *= static_cast<double>(nRepetitions);
400 fNewTick = std::fmod( fTick, fSongSizeInTicks );
401 fRemainingTicks = fNewTick;
402 fPassedTicks = 0;
403
404 // DEBUGLOG( QString( "[repeat] fTick: %1, fNewFrames: %2, fNewTick: %3, fRemainingTicks: %4, nRepetitions: %5, fSongSizeInTicks: %6, fSongSizeInFrames: %7" )
405 // .arg( fTick, 0, 'g',30 )
406 // .arg( fNewFrame, 0, 'g', 30 )
407 // .arg( fNewTick, 0, 'g', 30 )
408 // .arg( fRemainingTicks, 0, 'g', 30 )
409 // .arg( nRepetitions )
410 // .arg( fSongSizeInTicks, 0, 'g', 30 )
411 // .arg( fSongSizeInFrames, 0, 'g', 30 )
412 // );
413
414 if ( std::isinf( fNewFrame ) ||
415 static_cast<long long>(fNewFrame) >
416 std::numeric_limits<long long>::max() ) {
417 ERRORLOG( QString( "Provided ticks [%1] are too large." ).arg( fTick ) );
418 return 0;
419 }
420
421 // The target tick matches a multiple of the song
422 // size. We need to reproduce the context within the
423 // last tempo marker in order to get the mismatch
424 // right.
425 if ( fRemainingTicks == 0 ) {
426 ii = tempoMarkers.size();
427 fNextTick = static_cast<double>(pHydrogen->getTickForColumn(
428 tempoMarkers[ 0 ]->nColumn ) );
430 nSampleRate, tempoMarkers[ ii - 1 ]->fBpm, nResolution );
431
432 handleEnd();
433 }
434 }
435 }
436 } else {
437
438 // As the timeline is not activate, the column passed is of no
439 // importance. But we harness the ability of getBpmAtColumn()
440 // to collect and choose between tempo information gathered
441 // from various sources.
442 const float fBpm = AudioEngine::getBpmAtColumn( 0 );
443
444 const double fTickSize =
445 AudioEngine::computeDoubleTickSize( nSampleRate, fBpm,
446 nResolution );
447
448 // Single tempo for the whole song.
449 const double fNewFrame = static_cast<double>(fTick) *
450 fTickSize;
451 nNewFrame = static_cast<long long>( std::round( fNewFrame ) );
452 *fTickMismatch = ( fNewFrame - static_cast<double>(nNewFrame ) ) /
453 fTickSize;
454
455 // DEBUGLOG(QString("[no-timeline] nNewFrame: %1, fTick: %2, fTickSize: %3, fTickMismatch: %4" )
456 // .arg( nNewFrame ).arg( fTick, 0, 'f' ).arg( fTickSize, 0, 'f' )
457 // .arg( *fTickMismatch, 0, 'g', 30 ));
458
459 }
460
461 return nNewFrame;
462}
463
464// This function uses the assumption that sample rate and resolution
465// are constant over the whole song.
466double TransportPosition::computeTickFromFrame( const long long nFrame, int nSampleRate ) {
467 const auto pHydrogen = Hydrogen::get_instance();
468
469 if ( nFrame < 0 ) {
470 ERRORLOG( QString( "Provided frame [%1] must be non-negative" ).arg( nFrame ) );
471 }
472
473 const auto pSong = pHydrogen->getSong();
474 const auto pTimeline = pHydrogen->getTimeline();
475 const auto pAudioEngine = pHydrogen->getAudioEngine();
476 const auto pAudioDriver = pHydrogen->getAudioOutput();
477
478 if ( pSong == nullptr || pTimeline == nullptr ) {
479 ERRORLOG( "Invalid song" );
480 return 0;
481 }
482 if ( pAudioDriver == nullptr ) {
483 ERRORLOG( "AudioDriver is not ready!" );
484 return 0;
485 }
486
487 if ( nSampleRate == 0 ) {
488 nSampleRate = pAudioDriver->getSampleRate();
489 }
490 const int nResolution = pSong->getResolution();
491 double fTick = 0;
492
493 const double fSongSizeInTicks = pAudioEngine->getSongSizeInTicks();
494
495 if ( nSampleRate == 0 || nResolution == 0 ) {
496 ERRORLOG( "Not properly initialized yet" );
497 return fTick;
498 }
499
500 if ( nFrame == 0 ) {
501 return fTick;
502 }
503
504 const auto tempoMarkers = pTimeline->getAllTempoMarkers();
505
506 // If there are no patterns in the current, we treat song mode
507 // like pattern mode.
508 if ( pHydrogen->isTimelineEnabled() &&
509 ! ( tempoMarkers.size() == 1 &&
510 pTimeline->isFirstTempoMarkerSpecial() ) &&
511 pHydrogen->getMode() == Song::Mode::Song &&
512 pSong->getPatternGroupVector()->size() ) {
513
514 // We are using double precision in here to avoid rounding
515 // errors.
516 const double fTargetFrame = static_cast<double>(nFrame);
517 double fPassedFrames = 0;
518 double fNextFrame = 0;
519 double fNextTicks, fPassedTicks = 0;
520 double fNextTickSize;
521 long long nRemainingFrames;
522
523 const int nColumns = pSong->getPatternGroupVector()->size();
524
525 while ( fPassedFrames < fTargetFrame ) {
526
527 for ( int ii = 1; ii <= tempoMarkers.size(); ++ii ) {
528
529 fNextTickSize =
531 tempoMarkers[ ii - 1 ]->fBpm,
532 nResolution );
533
534 if ( ii == tempoMarkers.size() ||
535 tempoMarkers[ ii ]->nColumn >= nColumns ) {
536 fNextTicks = fSongSizeInTicks;
537 } else {
538 fNextTicks =
539 static_cast<double>(pHydrogen->getTickForColumn( tempoMarkers[ ii ]->nColumn ));
540 }
541 fNextFrame = (fNextTicks - fPassedTicks) * fNextTickSize;
542
543 if ( fNextFrame < ( fTargetFrame -
544 fPassedFrames ) ) {
545
546 // 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" )
547 // .arg( nFrame )
548 // .arg( fTick, 0, 'f' )
549 // .arg( nSampleRate )
550 // .arg( fNextTickSize, 0, 'f' )
551 // .arg( fNextTicks, 0, 'f' )
552 // .arg( fNextFrame, 0, 'f' )
553 // .arg( tempoMarkers[ ii -1 ]->nColumn )
554 // .arg( tempoMarkers[ ii -1 ]->fBpm )
555 // .arg( fPassedTicks, 0, 'f' )
556 // .arg( fPassedFrames, 0, 'f' )
557 // .arg( fNextTicks - fPassedTicks, 0, 'f' )
558 // .arg( (fNextTicks - fPassedTicks) * fNextTickSize, 0, 'g', 30 )
559 // );
560
561 // The whole segment of the timeline covered by tempo
562 // marker ii is left of the transport position.
563 fTick += fNextTicks - fPassedTicks;
564
565 fPassedFrames += fNextFrame;
566 fPassedTicks = fNextTicks;
567
568 } else {
569 // The target frame is located within a segment.
570 const double fNewTick = (fTargetFrame - fPassedFrames ) /
571 fNextTickSize;
572
573 fTick += fNewTick;
574
575 // 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" )
576 // .arg( nFrame )
577 // .arg( fTick, 0, 'f' )
578 // .arg( nSampleRate )
579 // .arg( fNextTickSize, 0, 'f' )
580 // .arg( fNextTicks, 0, 'f' )
581 // .arg( fNextFrame, 0, 'f' )
582 // .arg( tempoMarkers[ ii -1 ]->nColumn )
583 // .arg( tempoMarkers[ ii -1 ]->fBpm )
584 // .arg( fPassedTicks, 0, 'f' )
585 // .arg( fPassedFrames, 0, 'f' )
586 // .arg( fNewTick, 0, 'f' )
587 // .arg( fNewTick * fNextTickSize, 0, 'g', 30 )
588 // );
589
590 fPassedFrames = fTargetFrame;
591
592 break;
593 }
594 }
595
596 if ( fPassedFrames != fTargetFrame ) {
597 // The provided nFrame is larger than the song. But,
598 // luckily, we just calculated the song length in
599 // frames.
600 const double fSongSizeInFrames = fPassedFrames;
601 const int nRepetitions = std::floor(fTargetFrame / fSongSizeInFrames);
602 if ( fSongSizeInTicks * nRepetitions >
603 std::numeric_limits<double>::max() ) {
604 ERRORLOG( QString( "Provided frames [%1] are too large." ).arg( nFrame ) );
605 return 0;
606 }
607 fTick = fSongSizeInTicks * nRepetitions;
608
609 fPassedFrames = static_cast<double>(nRepetitions) *
610 fSongSizeInFrames;
611 fPassedTicks = 0;
612
613 // DEBUGLOG( QString( "[repeat] frames covered: %1, frames remaining: %2, ticks covered: %3, nRepetitions: %4, fSongSizeInFrames: %5, fSongSizeInTicks: %6" )
614 // .arg( fPassedFrames, 0, 'g', 30 )
615 // .arg( fTargetFrame - fPassedFrames, 0, 'g', 30 )
616 // .arg( fTick, 0, 'g', 30 )
617 // .arg( nRepetitions )
618 // .arg( fSongSizeInFrames, 0, 'g', 30 )
619 // .arg( fSongSizeInTicks, 0, 'g', 30 )
620 // );
621
622 }
623 }
624 }
625 else {
626 // As the timeline is not activate, the column passed is of no
627 // importance. But we harness the ability of getBpmAtColumn()
628 // to collect and choose between tempo information gathered
629 // from various sources.
630 const float fBpm = AudioEngine::getBpmAtColumn( 0 );
631 const double fTickSize =
632 AudioEngine::computeDoubleTickSize( nSampleRate, fBpm,
633 nResolution );
634
635 // Single tempo for the whole song.
636 fTick = static_cast<double>(nFrame) / fTickSize;
637
638 // DEBUGLOG(QString( "[no timeline] nFrame: %1, sampleRate: %2, tickSize: %3" )
639 // .arg( nFrame ).arg( nSampleRate ).arg( fTickSize, 0, 'f' ) );
640
641 }
642
643 return fTick;
644}
645
646long long TransportPosition::computeFrame( double fTick, float fTickSize ) {
647 return std::round( fTick * fTickSize );
648}
649
650double TransportPosition::computeTick( long long nFrame, float fTickSize ) {
651 return nFrame / fTickSize;
652}
653
654QString TransportPosition::toQString( const QString& sPrefix, bool bShort ) const {
655 QString s = Base::sPrintIndention;
656 QString sOutput;
657 if ( ! bShort ) {
658 sOutput = QString( "%1[TransportPosition]\n" ).arg( sPrefix )
659 .append( QString( "%1%2m_sLabel: %3\n" ).arg( sPrefix ).arg( s ).arg( m_sLabel ) )
660 .append( QString( "%1%2m_nFrame: %3\n" ).arg( sPrefix ).arg( s ).arg( getFrame() ) )
661 .append( QString( "%1%2m_fTick: %3\n" ).arg( sPrefix ).arg( s ).arg( getDoubleTick(), 0, 'f' ) )
662 .append( QString( "%1%2m_fTick (rounded): %3\n" ).arg( sPrefix ).arg( s ).arg( getTick() ) )
663 .append( QString( "%1%2m_fTickSize: %3\n" ).arg( sPrefix ).arg( s ).arg( getTickSize(), 0, 'f' ) )
664 .append( QString( "%1%2m_fBpm: %3\n" ).arg( sPrefix ).arg( s ).arg( getBpm(), 0, 'f' ) )
665 .append( QString( "%1%2m_nPatternStartTick: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nPatternStartTick ) )
666 .append( QString( "%1%2m_nPatternTickPosition: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nPatternTickPosition ) )
667 .append( QString( "%1%2m_nColumn: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nColumn ) )
668 .append( QString( "%1%2m_fTickMismatch: %3\n" ).arg( sPrefix ).arg( s ).arg( m_fTickMismatch, 0, 'f' ) )
669 .append( QString( "%1%2m_nFrameOffsetTempo: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nFrameOffsetTempo ) )
670 .append( QString( "%1%2m_fTickOffsetQueuing: %3\n" ).arg( sPrefix ).arg( s ).arg( m_fTickOffsetQueuing, 0, 'f' ) )
671 .append( QString( "%1%2m_fTickOffsetSongSize: %3\n" ).arg( sPrefix ).arg( s ).arg( m_fTickOffsetSongSize, 0, 'f' ) );
672 if ( m_pPlayingPatterns != nullptr ) {
673 sOutput.append( QString( "%1%2m_pPlayingPatterns: %3\n" ).arg( sPrefix ).arg( s ).arg( m_pPlayingPatterns->toQString( sPrefix + s ), bShort ) );
674 }
675 if ( m_pNextPatterns != nullptr ) {
676 sOutput.append( QString( "%1%2m_pNextPatterns: %3\n" ).arg( sPrefix ).arg( s ).arg( m_pNextPatterns->toQString( sPrefix + s ), bShort ) );
677 }
678 sOutput.append( QString( "%1%2m_nPatternSize: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nPatternSize ) )
679 .append( QString( "%1%2m_nLastLeadLagFactor: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nLastLeadLagFactor ) )
680 .append( QString( "%1%2m_nBar: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nBar ) )
681 .append( QString( "%1%2m_nBeat: %3\n" ).arg( sPrefix ).arg( s ).arg( m_nBeat ) );
682 }
683 else {
684 sOutput = QString( "%1[TransportPosition]" ).arg( sPrefix )
685 .append( QString( " m_sLabel: %1" ).arg( m_sLabel ) )
686 .append( QString( ", m_nFrame: %1" ).arg( getFrame() ) )
687 .append( QString( ", m_fTick: %1" ).arg( getDoubleTick(), 0, 'f' ) )
688 .append( QString( ", m_fTick (rounded): %1" ).arg( getTick() ) )
689 .append( QString( ", m_fTickSize: %1" ).arg( getTickSize(), 0, 'f' ) )
690 .append( QString( ", m_fBpm: %1" ).arg( getBpm(), 0, 'f' ) )
691 .append( QString( ", m_nPatternStartTick: %1" ).arg( m_nPatternStartTick ) )
692 .append( QString( ", m_nPatternTickPosition: %1" ).arg( m_nPatternTickPosition ) )
693 .append( QString( ", m_nColumn: %1" ).arg( m_nColumn ) )
694 .append( QString( ", m_fTickMismatch: %1" ).arg( m_fTickMismatch, 0, 'f' ) )
695 .append( QString( ", m_nFrameOffsetTempo: %1" ).arg( m_nFrameOffsetTempo ) )
696 .append( QString( ", m_fTickOffsetQueuing: %1" ).arg( m_fTickOffsetQueuing, 0, 'f' ) )
697 .append( QString( ", m_fTickOffsetSongSize: %1" ).arg( m_fTickOffsetSongSize, 0, 'f' ) );
698 if ( m_pPlayingPatterns != nullptr ) {
699 sOutput.append( QString( ", m_pPlayingPatterns: %1" ).arg( m_pPlayingPatterns->toQString( sPrefix + s ), bShort ) );
700 }
701 if ( m_pNextPatterns != nullptr ) {
702 sOutput.append( QString( ", m_pNextPatterns: %1" ).arg( m_pNextPatterns->toQString( sPrefix + s ), bShort ) );
703 }
704 sOutput.append( QString( ", m_nPatternSize: %1" ).arg( m_nPatternSize ) )
705 .append( QString( ", m_nLastLeadLagFactor: %1" ).arg( m_nLastLeadLagFactor ) )
706 .append( QString( ", m_nBar: %1" ).arg( m_nBar ) )
707 .append( QString( ", m_nBeat: %1" ).arg( m_nBeat ) );
708
709 }
710
711 return sOutput;
712}
713
714};
#define ERRORLOG(x)
Definition Object.h:239
void setNeedsLock(bool bNeedsLock)
The audio processing thread can modify some PatternLists.
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:83
void recalculateRubberband(float fBpm)
Recalculates all Samples using RubberBand for a specific tempo fBpm.
PatternList is a collection of patterns.
Definition PatternList.h:43
void add(Pattern *pattern, bool bAddVirtuals=false)
add a pattern to the list
QString toQString(const QString &sPrefix="", bool bShort=true) const override
Formatted string version for debugging purposes.
void clear()
empty the pattern list
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
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.
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