11namespace tracktion {
inline namespace engine
17 DBG (
"******************************************");
18 for (
auto& s : segments)
24 text +=
"Transpose: " +
juce::String (s.transpose) +
"\n";
25 text +=
"===============================================";
32TimeRange AudioSegmentList::Segment::getRange()
const {
return { start, start + length }; }
33SampleRange AudioSegmentList::Segment::getSampleRange()
const {
return { startSample, startSample + lengthSample }; }
35float AudioSegmentList::Segment::getStretchRatio()
const {
return stretchRatio; }
36float AudioSegmentList::Segment::getTranspose()
const {
return transpose; }
38bool AudioSegmentList::Segment::hasFadeIn()
const {
return fadeIn; }
39bool AudioSegmentList::Segment::hasFadeOut()
const {
return fadeOut; }
41bool AudioSegmentList::Segment::isFollowedBySilence()
const {
return followedBySilence; }
43HashCode AudioSegmentList::Segment::getHashCode()
const
46 ^ (lengthSample * 127)
47 ^ (followedBySilence ? 1234 : 5432)
48 ^ static_cast<HashCode> (stretchRatio * 1003.0f)
49 ^ static_cast<HashCode> (transpose * 117.0f);
52bool AudioSegmentList::Segment::operator== (
const Segment& other)
const
54 return (start == other.start &&
55 length == other.length &&
56 startSample == other.startSample &&
57 lengthSample == other.lengthSample &&
58 stretchRatio == other.stretchRatio &&
59 transpose == other.transpose &&
60 fadeIn == other.fadeIn &&
61 fadeOut == other.fadeOut);
64bool AudioSegmentList::Segment::operator!= (
const Segment& other)
const
66 return ! operator== (other);
70AudioSegmentList::AudioSegmentList (AudioClipBase& acb) : clip (acb)
74AudioSegmentList::AudioSegmentList (AudioClipBase& acb,
bool relTime,
bool shouldCrossfade)
75 : clip (acb), relativeTime (relTime)
78 crossfadeTime = TimeDuration::fromSeconds (
static_cast<double> (clip.edit.engine.getPropertyStorage().getProperty (SettingID::crossfadeBlock, 12.0 / 1000.0)));
80 auto& pm = acb.edit.engine.getProjectManager();
82 auto anyTakesValid = [&]
84 for (ProjectItemID m : clip.getTakes())
85 if (pm.findSourceFile (m).existsAsFile())
92 auto f = pm.findSourceFile (clip.getSourceFileReference().getSourceProjectItemID());
96 if (clip.getCurrentSourceFile().existsAsFile() || anyTakesValid())
97 build (shouldCrossfade);
100static float calcStretchRatio (
const AudioSegmentList::Segment& seg,
double sampleRate)
102 double srcSamples = sampleRate * seg.getRange().getLength().inSeconds();
105 return (
float) (seg.getSampleRange().getLength() / srcSamples);
117 return create (acb, acb.getWarpTimeManager(), acb.getWaveInfo(), acb.getLoopInfo());
122 auto wi = af.getInfo();
123 return create (acb, wtm, wi, wi.loopInfo);
131 auto in = li.getInMarker();
132 auto out = (li.getOutMarker() == -1) ? wi.lengthInSamples : li.getOutMarker();
138 wtm.getWarpEndMarkerTime());
141 callBlocking ([&] { warpTimeRegions = wtm.getWarpTimeRegions (region); });
142 auto position = warpTimeRegions.
size() > 0 ? warpTimeRegions.
getUnchecked (0).getStart() : TimePosition();
144 for (
auto warpRegion : warpTimeRegions)
146 TimeRange sourceRegion (wtm.warpTimeToSourceTime (warpRegion.getStart()),
147 wtm.warpTimeToSourceTime (warpRegion.getEnd()));
152 seg.lengthSample =
tracktion::toSamples (sourceRegion.getEnd(), wi.sampleRate) + in - seg.startSample;
153 seg.start = position;
154 seg.length = warpRegion.getLength();
155 seg.stretchRatio = calcStretchRatio (seg, wi.sampleRate);
158 seg.transpose = 0.0f;
160 position = position + warpRegion.getLength();
161 jassert (seg.startSample >= in);
162 jassert (seg.startSample + seg.lengthSample <= out);
164 asl->segments.add (seg);
167 asl->crossfadeTime = 0.01s;
168 asl->crossFadeSegments();
174bool AudioSegmentList::operator== (
const AudioSegmentList& other)
const noexcept
176 return crossfadeTime == other.crossfadeTime
177 && relativeTime == other.relativeTime
178 && segments == other.segments;
181bool AudioSegmentList::operator!= (
const AudioSegmentList& other)
const noexcept
183 return ! operator== (other);
186void AudioSegmentList::build (
bool crossfade)
188 if (clip.getAutoPitch() && clip.getAutoPitchMode() == AudioClipBase::chordTrackMono)
189 if (
auto pg = clip.getPatternGenerator())
190 pg->getFlattenedChordProgression (progression,
true);
192 if (clip.getAutoTempo())
193 buildAutoTempo (crossfade);
195 buildNormal (crossfade);
201 for (
auto& s : segments)
202 s.start = s.start - offset;
206void AudioSegmentList::chopSegment (Segment& seg, TimePosition at,
int insertPos)
211 newSeg.length = seg.getRange().getEnd() - newSeg.getRange().getStart();
213 newSeg.transpose = getPitchAt (newSeg.start + 0.0001s);
214 newSeg.stretchRatio = (
float) clip.getSpeedRatio();
216 newSeg.fadeIn =
true;
217 newSeg.fadeOut = seg.fadeOut;
219 newSeg.lengthSample =
juce::roundToInt (seg.lengthSample * newSeg.length.inSeconds() / seg.length.inSeconds());
220 newSeg.startSample = seg.getSampleRange().getEnd() - newSeg.lengthSample;
222 seg.length = seg.length - newSeg.length;
223 seg.lengthSample = newSeg.startSample - seg.startSample;
226 seg.followedBySilence =
false;
228 jassert (newSeg.length > 0.01s);
231 segments.insert (insertPos, newSeg);
234void AudioSegmentList::buildNormal (
bool crossfade)
237 auto wi = clip.getWaveInfo();
239 if (wi.sampleRate == 0.0)
242 auto rate = clip.getSpeedRatio() * wi.sampleRate;
243 auto clipPos = clip.getPosition();
245 if (clip.isLooping())
247 auto clipLoopLen = clip.getLoopLength();
249 if (clipLoopLen <= 0s)
252 auto startSamp =
std::max ((SampleCount) 0, (SampleCount) (rate * clip.getLoopStart().inSeconds()));
253 auto lengthSamp =
std::max ((SampleCount) 0, (SampleCount) (rate * clipLoopLen.inSeconds()));
255 for (
int i = 0; ; ++i)
257 auto startTime = clipPos.getStart() + clipLoopLen * i - clipPos.getOffset();
259 if (startTime >= clipPos.getEnd())
262 auto end = startTime + clipLoopLen;
264 if (end < clipPos.getStart())
269 seg.startSample = startSamp;
270 seg.lengthSample = lengthSamp;
272 if (startTime < clipPos.getStart())
274 auto diff = (SampleCount) ((clipPos.getStart() - startTime).inSeconds() * rate);
276 seg.startSample += diff;
277 seg.lengthSample -= diff;
278 startTime = clipPos.getStart();
281 if (end > clipPos.getEnd())
283 auto diff = (SampleCount) ((end - clipPos.getEnd()).inSeconds() * rate);
284 seg.lengthSample -= diff;
285 end = clipPos.getEnd();
288 if (seg.lengthSample <= 0)
291 seg.start = startTime;
292 seg.length =
end - startTime;
294 seg.transpose = getPitchAt (startTime + 0.0001s);
295 seg.stretchRatio = (
float) clip.getSpeedRatio();
299 seg.followedBySilence =
true;
301 if (! segments.isEmpty())
303 auto&
prev = segments.getReference (segments.size() - 1);
305 if (tracktion::abs (
prev.getRange().getEnd() - seg.getRange().getStart()) < 0.01s)
306 prev.followedBySilence =
false;
312 if (! segments.isEmpty())
314 segments.getReference (0).fadeIn =
false;
315 segments.getReference (segments.size() - 1).fadeOut =
false;
323 seg.start = clipPos.getStart();
324 seg.length = clipPos.getLength();
326 seg.startSample =
juce::jlimit ((SampleCount) 0, wi.lengthInSamples, (SampleCount) (clipPos.getOffset().inSeconds() * rate));
327 seg.lengthSample =
juce::jlimit ((SampleCount) 0, wi.lengthInSamples, (SampleCount) (clipPos.getLength().inSeconds() * rate));
329 seg.transpose = getPitchAt (clipPos.getStart() + 0.0001s);
330 seg.stretchRatio = (
float) clip.getSpeedRatio();
335 seg.followedBySilence =
true;
342 if (clip.getAutoPitch())
344 auto& ps = clip.edit.pitchSequence;
346 for (
int i = 0; i < ps.getNumPitches(); ++i)
348 auto* pitch = ps.getPitch(i);
351 auto pitchTm = pitch->getPosition().getStart();
353 if (pitchTm > getStart() + 0.01s && pitchTm < getEnd() - 0.01s)
355 for (
int j = 0; j < segments.size(); ++j)
357 auto& seg = segments.getReference (j);
359 if (seg.getRange().reduced (0.01s).contains (pitchTm)
360 && std::abs (getPitchAt (pitchTm) - getPitchAt (seg.getRange().getStart())) > 0.0001)
362 chopSegment (seg, pitchTm, j + 1);
369 chopSegmentsForChords();
376void AudioSegmentList::chopSegmentsForChords()
378 if (clip.getAutoPitchMode() == AudioClipBase::chordTrackMono && progression.size() > 0)
380 auto& ts = clip.edit.tempoSequence;
384 for (
auto& p : progression)
386 auto chordTime = ts.toTime (pos);
388 if (chordTime > getStart() + 0.01s && chordTime < getEnd() - 0.01s)
390 for (
int j = 0; j < segments.size(); ++j)
392 auto& seg = segments.getReference (j);
394 if (seg.getRange().reduced (0.01s).contains (chordTime))
396 chopSegment (seg, chordTime, j + 1);
403 pos = pos + p->lengthInBeats;
411 auto numLoopPoints = loopInfo.getNumLoopPoints();
413 if (numLoopPoints == 0)
415 const auto numBeats = (
int)
std::ceil (loopInfo.getNumBeats());
418 for (
int i = 0; i < numBeats; ++i)
419 syncSamples.
add ((SampleCount) (range.getLength() / (
double) numBeats * i + range.getStart() + 0.5));
423 for (
int i = 0; i < numLoopPoints; ++i)
425 auto pos = loopInfo.getLoopPoint (i).pos;
427 if (range.contains (pos))
428 syncSamples.
add (pos);
432 if (! syncSamples.
contains (range.getStart()))
433 syncSamples.
add (range.getStart());
444 for (
auto& s : samples)
451void AudioSegmentList::initialiseSegment (Segment& seg, BeatPosition startBeat, BeatPosition endBeat,
double sampleRate)
453 auto& ts = clip.edit.tempoSequence;
454 seg.start = ts.toTime (startBeat);
455 seg.length = ts.toTime (endBeat) - seg.start;
456 seg.stretchRatio = calcStretchRatio (seg, sampleRate);
459 seg.transpose = getPitchAt (seg.start + 0.0001s);
462void AudioSegmentList::removeExtraSegments()
464 for (
int i = segments.size(); --i >= 0;)
466 auto& seg = segments.getReference (i);
467 auto segTime = seg.getRange();
468 auto clipTime = clip.getPosition().time;
470 if (! segTime.overlaps (clipTime))
474 else if (segTime.getStart() < clipTime.getEnd() && segTime.getEnd() > clipTime.getEnd())
476 auto oldLen = seg.length;
477 seg.length = getEnd() - seg.start;
478 auto ratio = oldLen / seg.length;
479 seg.lengthSample =
static_cast<SampleCount
> (seg.lengthSample / ratio + 0.5);
481 else if (segTime.getStart() < clipTime.getStart() && segTime.getEnd() > clipTime.getStart())
483 auto oldLen = seg.length;
484 auto delta = getStart() - segTime.getStart();
485 seg.start = seg.start + delta;
486 seg.length = seg.length - delta;
487 auto ratio = oldLen / segTime.getLength();
488 auto oldEndSamp = seg.getSampleRange().getEnd();
489 seg.lengthSample =
static_cast<SampleCount
> (seg.lengthSample / ratio + 0.5);
490 seg.startSample = oldEndSamp - seg.lengthSample;
495void AudioSegmentList::mergeSegments (
double sampleRate)
497 for (
int i = segments.size() - 1; i >= 1; --i)
499 auto& s1 = segments.getReference (i - 1);
500 auto& s2 = segments.getReference (i);
502 if (std::abs (s1.stretchRatio - s2.stretchRatio) < 0.0001
503 && std::abs (s1.transpose - s2.transpose) < 0.0001
505 && s1.startSample + s1.lengthSample == s2.startSample)
507 s1.length = s1.length + s2.length;
508 s1.lengthSample += s2.lengthSample;
509 s1.stretchRatio = calcStretchRatio (s1, sampleRate);
516void AudioSegmentList::crossFadeSegments()
518 for (
int i = 0; i < segments.size(); ++i)
520 auto& s = segments.getReference(i);
523 if (i < segments.size() - 1
524 && (tracktion::abs (s.getRange().getEnd() - segments.getReference (i + 1).start) < 0.0001s))
526 auto oldLen = s.length;
528 s.length = s.length + crossfadeTime;
529 auto ratio = oldLen / s.length;
530 s.lengthSample =
static_cast<SampleCount
> (s.lengthSample / ratio + 0.5);
531 s.followedBySilence =
false;
535 s.followedBySilence =
true;
539 if (i > 0 && segments.getReference (i - 1).fadeOut)
544void AudioSegmentList::buildAutoTempo (
bool crossfade)
547 auto wi = clip.getWaveInfo();
548 auto& li = clip.getLoopInfo();
550 SampleRange range (li.getInMarker(),
551 li.getOutMarker() == -1 ? wi.lengthInSamples
552 : li.getOutMarker());
557 auto& ts = clip.edit.tempoSequence;
558 auto syncSamples = findSyncSamples (li, range);
559 auto clipStartBeat = clip.getStartBeat();
561 if (clip.isLooping())
563 auto loopLengthBeats = clip.getLoopLengthBeats();
565 if (loopLengthBeats == BeatDuration())
568 auto offsetBeat = clip.getOffsetInBeats();
570 while (offsetBeat > loopLengthBeats)
571 offsetBeat = offsetBeat - loopLengthBeats;
573 if (tracktion::abs (offsetBeat).inBeats() < 0.00001)
574 offsetBeat = BeatDuration();
576 auto loopStartBeat = clip.getLoopStartBeats() + offsetBeat;
578 auto offsetTime = TimePosition::fromSeconds (loopStartBeat.inBeats() / li.getBeatsPerSecond (wi));
581 auto syncSamplesSubset = trimInitialSyncSamples (syncSamples, offsetSample);
583 BeatPosition beatPos;
584 BeatPosition loopEndBeat =
toPosition (loopLengthBeats) - offsetBeat;
586 for (
int i = 0; i < syncSamplesSubset.size(); ++i)
590 seg.startSample = syncSamplesSubset[i];
591 seg.lengthSample = ((i == syncSamplesSubset.size() - 1) ? (range.getEnd() - seg.startSample)
592 : (syncSamplesSubset[i + 1]) - seg.startSample);
594 auto startBeat = beatPos;
595 beatPos = beatPos + BeatDuration::fromBeats (TimeDuration::fromSamples (seg.lengthSample, wi.sampleRate).inSeconds() * li.getBeatsPerSecond (wi));
596 auto endBeat = beatPos;
598 initialiseSegment (seg, clipStartBeat + toDuration (startBeat), clipStartBeat + toDuration (endBeat), wi.sampleRate);
600 if (startBeat >= loopEndBeat)
603 if (endBeat > loopEndBeat)
605 auto oldLength = endBeat - startBeat;
606 auto newLength = loopEndBeat - startBeat;
608 seg.length = ts.toTime (clipStartBeat + toDuration (loopEndBeat)) - seg.start;
609 seg.lengthSample =
static_cast<SampleCount
> (seg.lengthSample * (newLength / oldLength) + 0.5);
611 jassert (seg.startSample >= range.getStart());
612 jassert (seg.startSample + seg.lengthSample <= range.getEnd());
617 jassert (seg.startSample >= range.getStart());
618 jassert (seg.startSample + seg.lengthSample <= range.getEnd());
622 loopStartBeat = clip.getLoopStartBeats();
624 offsetTime = TimePosition::fromSeconds (loopStartBeat.inBeats() / li.getBeatsPerSecond (wi));
627 syncSamplesSubset = trimInitialSyncSamples (syncSamples, offsetSample);
629 beatPos = loopEndBeat;
630 loopEndBeat = beatPos + loopLengthBeats;
632 while (beatPos < toPosition (clip.getLengthInBeats()))
634 for (
int i = 0; i < syncSamplesSubset.size(); ++i)
638 seg.startSample = syncSamplesSubset[i];
639 seg.lengthSample = ((i == syncSamplesSubset.size() - 1) ? (range.getEnd() - seg.startSample)
640 : (syncSamplesSubset[i + 1]) - seg.startSample);
642 auto startBeat = beatPos;
643 beatPos = beatPos + BeatDuration::fromBeats ((seg.lengthSample / wi.sampleRate) * li.getBeatsPerSecond (wi));
644 auto endBeat = beatPos;
646 initialiseSegment (seg, clipStartBeat + toDuration (startBeat), clipStartBeat + toDuration (endBeat), wi.sampleRate);
648 if (startBeat >= loopEndBeat)
651 if (endBeat > loopEndBeat)
653 auto oldLength = endBeat - startBeat;
654 auto newLength = loopEndBeat - startBeat;
656 seg.length = ts.toTime (clipStartBeat + toDuration (loopEndBeat)) - seg.start;
657 seg.lengthSample =
static_cast<SampleCount
> (seg.lengthSample * (newLength / oldLength) + 0.5);
659 jassert (seg.startSample >= range.getStart());
660 jassert (seg.startSample + seg.lengthSample <= range.getEnd());
665 jassert (seg.startSample >= range.getStart());
666 jassert (seg.startSample + seg.lengthSample <= range.getEnd());
670 beatPos = loopEndBeat;
671 loopEndBeat = beatPos + loopLengthBeats;
676 auto offsetTime = TimeDuration::fromSeconds (clip.getOffsetInBeats().inBeats() / li.getBeatsPerSecond (wi));
678 BeatPosition beatPos;
680 syncSamples = trimInitialSyncSamples (syncSamples, offsetSample);
682 for (
int i = 0; i < syncSamples.
size(); ++i)
686 seg.startSample = syncSamples[i];
687 seg.lengthSample = ((i == syncSamples.
size() - 1) ? (range.getEnd() - seg.startSample)
688 : (syncSamples[i + 1]) - seg.startSample);
690 auto startBeat = beatPos;
691 beatPos = beatPos + BeatDuration::fromBeats ((seg.lengthSample / wi.sampleRate) * li.getBeatsPerSecond (wi));
692 auto endBeat = beatPos;
694 initialiseSegment (seg, clipStartBeat + toDuration (startBeat), clipStartBeat + toDuration (endBeat), wi.sampleRate);
696 jassert (seg.startSample >= range.getStart());
697 jassert (seg.startSample + seg.lengthSample <= range.getEnd());
702 chopSegmentsForChords();
703 removeExtraSegments();
704 mergeSegments (wi.sampleRate);
710TimePosition AudioSegmentList::getStart()
const
712 if (! segments.isEmpty())
713 return segments.getReference (0).getRange().getStart();
718TimePosition AudioSegmentList::getEnd()
const
720 if (! segments.isEmpty())
721 return segments.getReference (segments.size() - 1).getRange().getEnd();
726float AudioSegmentList::getPitchAt (TimePosition t)
728 if (clip.getAutoPitch() && clip.getAutoPitchMode() == AudioClipBase::chordTrackMono && progression.size() > 0)
730 auto& ts = clip.edit.tempoSequence;
732 auto& ps = clip.edit.pitchSequence;
733 auto& pitchSetting = ps.getPitchAt (t);
735 auto beat = ts.toBeats (t);
738 for (
auto& p : progression)
740 if (beat >= pos && beat < pos + p->lengthInBeats)
742 int key = pitchSetting.getPitch() % 12;
744 auto scale = pitchSetting.getScale();
746 if (p->chordName.get().isNotEmpty())
749 int chordNote = p->getRootNote (key, scale);
751 int delta = chordNote - scaleNote;
753 int transposeBase = scaleNote - (clip.getLoopInfo().getRootNote() % 12);
755 while (transposeBase > 6) transposeBase -= 12;
756 while (transposeBase < -6) transposeBase += 12;
758 transposeBase += p->octave * 12;
760 return (
float) (transposeBase + delta + clip.getTransposeSemiTones (
false));
764 pos = pos + p->lengthInBeats.get();
768 if (clip.getAutoPitch())
770 auto& ps = clip.edit.pitchSequence;
771 auto& pitchSetting = ps.getPitchAt (t);
773 int pitch = pitchSetting.getPitch();
774 int transposeBase = pitch - clip.getLoopInfo().getRootNote();
776 while (transposeBase > 6) transposeBase -= 12;
777 while (transposeBase < -6) transposeBase += 12;
779 return (
float) (transposeBase + clip.getTransposeSemiTones (
false));
782 return clip.getPitchChange();
ElementType getUnchecked(int index) const
void ensureStorageAllocated(int minNumElements)
int size() const noexcept
ElementType * begin() noexcept
ElementType * end() noexcept
void add(const ElementType &newElement)
bool contains(ParameterType elementToLookFor) const
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
int roundToInt(const FloatType value) noexcept
constexpr int64_t toSamples(TimePosition, double sampleRate)
Converts a TimePosition to a number of samples.
constexpr TimePosition toPosition(TimeDuration)
Converts a TimeDuration to a TimePosition.
RangeType< TimePosition > TimeRange
A RangeType based on real time (i.e.
constexpr TimeDuration toDuration(TimePosition)
Converts a TimePosition to a TimeDuration.
TimePosition abs(TimePosition)
Returns the absolute of this TimePosition.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.