11namespace tracktion {
inline namespace engine
41QuantisationType::QuantisationType() : state (IDs::QUANTISATION)
43 initialiseCachedValues (
nullptr);
48 initialiseCachedValues (um);
51QuantisationType::~QuantisationType()
56QuantisationType::QuantisationType (
const QuantisationType& other)
57 : state (other.state.createCopy()),
58 typeIndex (other.typeIndex),
59 fractionOfBeat (other.fractionOfBeat)
61 initialiseCachedValues (
nullptr);
64QuantisationType& QuantisationType::operator= (
const QuantisationType& other)
66 proportion = other.proportion.
get();
67 typeName = other.typeName.
get();
68 typeIndex = other.typeIndex;
69 fractionOfBeat = other.fractionOfBeat;
74bool QuantisationType::operator== (
const QuantisationType& o)
const
76 return proportion == o.proportion
77 && typeIndex == o.typeIndex
78 && fractionOfBeat == o.fractionOfBeat
79 && quantiseNoteOffs == o.quantiseNoteOffs;
86 typeName.
referTo (state, IDs::type, um);
87 proportion.
referTo (state, IDs::amount, um, 1.0f);
88 quantiseNoteOffs.
referTo (state, IDs::quantiseNoteOffs, um,
true);
93void QuantisationType::updateType()
95 auto typeNameToIndex = [] (
const juce::String& newTypeName) ->
int
98 auto targetFraction = newTypeName.retainCharacters (
"0123456789/");
107 typeIndex = typeNameToIndex (typeName);
111void QuantisationType::updateFraction()
114 fractionOfBeat = quantisationTypes[typeIndex].beatFraction;
116 fractionOfBeat = 0.0;
119juce::String QuantisationType::getType (
bool translated)
const
122 return translated ?
TRANS(quantisationTypes[typeIndex].name)
123 : quantisationTypes[typeIndex].name;
138 isTriplet = quantisationTypes[typeIndex].isTriplets;
140 return quantisationTypes[typeIndex].snapLevel;
145 return typeIndex != 0 && proportion > 0.0f;
148void QuantisationType::setProportion (
float prop)
155 return roundTo (time, 0.5 - 1.0e-10, edit);
158TimePosition QuantisationType::roundUp (TimePosition time,
const Edit& edit)
const
160 return roundTo (time, 1.0 - 1.0e-10, edit);
163BeatPosition QuantisationType::roundToBeat (BeatPosition beatNumber,
double adjustment)
const
168 const double beats = fractionOfBeat *
floor (beatNumber.inBeats() / fractionOfBeat + adjustment);
170 return BeatPosition::fromBeats (proportion == 1.0 ? beats
171 : beatNumber.inBeats() + proportion * (beats - beatNumber.inBeats()));
174BeatPosition QuantisationType::roundBeatToNearest (BeatPosition beatNumber)
const
176 return roundToBeat (beatNumber, 0.5);
179BeatPosition QuantisationType::roundBeatUp (BeatPosition beatNumber)
const
181 return roundToBeat (beatNumber, 1.0 - 1.0e-10);
184BeatPosition QuantisationType::roundBeatToNearestNonZero (BeatPosition beatNumber)
const
186 auto t = roundBeatToNearest (beatNumber);
188 return t == BeatPosition() ? BeatPosition::fromBeats (fractionOfBeat) : t;
191TimePosition QuantisationType::roundTo (TimePosition time,
double adjustment,
const Edit& edit)
const
196 auto& s = edit.tempoSequence;
197 auto beats = s.toBeats (time);
199 beats = BeatPosition::fromBeats (fractionOfBeat *
std::floor (beats.inBeats() / fractionOfBeat + adjustment));
201 return proportion >= 1.0 ? s.toTime (beats)
210 s.
add (translated ?
TRANS(quantisationTypes[i].name)
211 : quantisationTypes[i].name);
216juce::String QuantisationType::getDefaultType (
bool translated)
218 return translated ?
TRANS(quantisationTypes[0].name)
219 : quantisationTypes[0].name;
231 else if (i == IDs::amount)
246 auto& m = e->message;
250 const auto noteOnTime = (roundToNearest (start + TimeDuration::fromSeconds (m.getTimeStamp()), ed) - start).inSeconds();
252 if (
auto noteOff = e->noteOffObject)
254 auto& mOff = noteOff->message;
256 if (quantiseNoteOffs)
258 auto noteOffTime = (roundUp (start + TimeDuration::fromSeconds (mOff.getTimeStamp()), ed) - start).inSeconds();
260 static constexpr double beatsToBumpUpBy = 1.0 / 512.0;
262 if (noteOffTime <= noteOnTime)
263 noteOffTime = roundUp (TimePosition::fromSeconds (noteOnTime + beatsToBumpUpBy), ed).
inSeconds();
265 mOff.setTimeStamp (noteOffTime);
269 mOff.setTimeStamp (noteOnTime + (mOff.getTimeStamp() - m.getTimeStamp()));
273 m.setTimeStamp (noteOnTime);
275 else if (m.isNoteOff() && quantiseNoteOffs)
277 m.setTimeStamp ((roundUp (start + TimeDuration::fromSeconds (m.getTimeStamp()), ed) - start).inSeconds());
282#if TRACKTION_UNIT_TESTS && ENGINE_UNIT_TESTS_QUANTISATION_TYPE
287 QuantisationTypeTests()
288 :
juce::UnitTest (
"QuantisationType",
"tracktion_engine")
292 void runTest()
override
294 auto& engine = *Engine::getEngines()[0];
295 auto edit = Edit::createSingleTrackEdit (engine);
298 c->setEnd (edit->tempoSequence.toTime (2_bp),
true);
300 MidiList originalList;
301 originalList.addNote (60, 0_bp, 0.25_bd, 127, 0,
nullptr);
302 originalList.addNote (61, 0.75_bp, 0.25_bd, 127, 0,
nullptr);
303 originalList.addNote (62, 1.25_bp, 0.25_bd, 127, 0,
nullptr);
304 originalList.addNote (63, 1.75_bp, 0.25_bd, 127, 0,
nullptr);
306 auto& list = c->getSequence();
307 list.copyFrom (originalList, {});
309 beginTest (
"No quantise");
311 auto& q = c->getQuantisation();
312 auto notes = list.getNotes();
313 expectEquals (q.roundBeatToNearest (notes[0]->getStartBeat()), 0_bp);
314 expectEquals (q.roundBeatToNearest (notes[1]->getStartBeat()), 0.75_bp);
315 expectEquals (q.roundBeatToNearest (notes[2]->getStartBeat()), 1.25_bp);
316 expectEquals (q.roundBeatToNearest (notes[3]->getStartBeat()), 1.75_bp);
319 beginTest (
"1/2 bar");
326 auto notes = list.getNotes();
327 expectEquals (q.roundBeatToNearest (notes[0]->getStartBeat()), 0_bp);
328 expectEquals (q.roundBeatToNearest (notes[1]->getStartBeat()), 1.0_bp);
329 expectEquals (q.roundBeatToNearest (notes[2]->getStartBeat()), 1.5_bp);
330 expectEquals (q.roundBeatToNearest (notes[3]->getStartBeat()), 2.0_bp);
334 for (
auto n : list.getNotes())
335 n->setStartAndLength (q.roundBeatToNearest (n->getStartBeat()),
336 n->getLengthBeats(), nullptr);
339 c->setLoopRangeBeats ({ 0_bp, 2_bd });
340 c->setEnd (edit->tempoSequence.toTime (4_bp),
true);
342 auto notes = c->getSequenceLooped().getNotes();
343 expectEquals (notes.size(), 6);
345 expectEquals (notes[0]->getStartBeat(), 0_bp);
346 expectEquals (notes[1]->getStartBeat(), 1.0_bp);
347 expectEquals (notes[2]->getStartBeat(), 1.5_bp);
348 expectEquals (notes[3]->getStartBeat(), 2.0_bp);
349 expectEquals (notes[4]->getStartBeat(), 3.0_bp);
350 expectEquals (notes[5]->getStartBeat(), 3.5_bp);
357 list.copyFrom (originalList, {});
364 for (
auto n : list.getNotes())
365 n->setStartAndLength (q.roundBeatToNearest (n->getStartBeat()),
366 n->getLengthBeats(), nullptr);
369 auto notes = c->getSequenceLooped().getNotes();
370 expectEquals (notes.size(), 6);
372 expectEquals (notes[0]->getStartBeat(), 0_bp);
373 expectEquals (notes[1]->getStartBeat(), 1.0_bp);
374 expectEquals (notes[2]->getStartBeat(), 1.0_bp);
375 expectEquals (notes[3]->getStartBeat(), 2.0_bp);
376 expectEquals (notes[4]->getStartBeat(), 3.0_bp);
377 expectEquals (notes[5]->getStartBeat(), 3.0_bp);
383static QuantisationTypeTests quantisationTypeTests;
void forceUpdateOfCachedValue()
void referTo(ValueTree &tree, const Identifier &property, UndoManager *um)
Type get() const noexcept
MidiEventHolder * getEventPointer(int index) const noexcept
int getNumEvents() const noexcept
void add(String stringToAdd)
String retainCharacters(StringRef charactersToRetain) const
void addListener(Listener *listener)
void removeListener(Listener *listener)
The Tracktion Edit class!
bool isEnabled() const
this type may represent "no quantising"
int getTimecodeSnapTypeLevel(bool &isTriplet) const noexcept
Returns the TimecodeSnapType level for the current quantisation type.
#define TRANS(stringLiteral)
#define NEEDS_TRANS(stringLiteral)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
constexpr int numElementsInArray(Type(&)[N]) noexcept
juce::Array< AudioTrack * > getAudioTracks(const Edit &edit)
Returns all the AudioTracks in an Edit.
MidiClip::Ptr insertMIDIClip(ClipOwner &parent, const juce::String &name, TimeRange position)
Inserts a new MidiClip into the ClipOwner's clip list.
constexpr TimeDuration toDuration(TimePosition)
Converts a TimePosition to a TimeDuration.
Represents a position in real-life time.
constexpr double inSeconds() const
Returns the TimePosition as a number of seconds.