tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_LoopInfo.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
15 : engine (e), state (IDs::LOOPINFO)
16{
17 initialiseMissingProps();
18}
19
21 : engine (e), state (IDs::LOOPINFO)
22{
23 auto& formatManager = engine.getAudioFileFormatManager();
24
25 if (auto af = formatManager.getFormatFromFileName (f))
26 {
27 if (auto fin = f.createInputStream())
28 {
29 const std::unique_ptr<juce::AudioFormatReader> afr (af->createReaderFor (fin.release(), true));
30 init (afr.get(), af, f);
31 }
32 }
33}
34
36 : engine (e), state (IDs::LOOPINFO)
37{
38 init (afr, af, f);
39}
40
42 : engine (e), state (v), um (u), maintainParent (v.getParent().isValid())
43{
44 initialiseMissingProps();
45}
46
48 : engine (other.engine)
49{
50 *this = other;
51}
52
54{
55 return copyFrom (o.state);
56}
57
58//==============================================================================
59double LoopInfo::getBpm (const AudioFileInfo& wi) const
60{
61 return getBeatsPerSecond (wi) * 60.0;
62}
63
64void LoopInfo::setBpm (double newBpm, double currentBpm)
65{
66 if (newBpm != currentBpm)
67 {
68 jassert (getNumBeats() > 0);
69 const double ratio = newBpm / currentBpm;
70 setNumBeats (juce::jmax (1.0, getNumBeats() * ratio));
71 }
72}
73
74void LoopInfo::setBpm (double newBpm, const AudioFileInfo& wi)
75{
76 // this is a bit of a round about way of calculating the number of beats which
77 // will in turn update the BPM but should work until we determine a proper fix
78
79 jassert (wi.sampleRate != 0.0);
80
81 if (wi.sampleRate == 0.0)
82 return;
83
84 if (newBpm < 30.0 || newBpm > 1000.0)
85 return;
86
87 const double currentBpm = getBpm (wi);
88
89 // Set a dummy number of beats otherwise the ratio will be 0
90 if (currentBpm == 0)
91 {
92 const auto lengthMins = wi.getLengthInSeconds() / 60.0;
93 const auto numBeats = newBpm * lengthMins;
94 setNumBeats (numBeats);
95 }
96 else
97 {
98 setBpm (newBpm, currentBpm);
99 }
100}
101
103{
105
106 if (wi.sampleRate == 0.0)
107 return 2.0;
108
109 auto in = getInMarker();
110 auto out = getOutMarker();
111
112 if (out == -1 || out > wi.lengthInSamples)
113 out = wi.lengthInSamples;
114
115 if (out <= 0)
116 return 2.0;
117
118 auto length = (out - in) / wi.sampleRate;
119
120 if (length <= 0)
121 return 2.0;
122
123 return getNumBeats() / length;
124}
125
126//==============================================================================
127int LoopInfo::getDenominator() const { return getProp<int> (IDs::denominator); }
128int LoopInfo::getNumerator() const { return getProp<int> (IDs::numerator); }
129void LoopInfo::setDenominator (int dem) { setProp (IDs::denominator, dem); }
130void LoopInfo::setNumerator (int num) { setProp (IDs::numerator, num); }
131double LoopInfo::getNumBeats() const { return getProp<double> (IDs::numBeats); }
132void LoopInfo::setNumBeats (double b) { setProp (IDs::numBeats, b); }
133
134//==============================================================================
135bool LoopInfo::isLoopable() const { const juce::ScopedLock sl (lock); return ! isOneShot() && getNumBeats() > 0.0 && getDenominator() > 0 && getNumerator() > 0; }
136bool LoopInfo::isOneShot() const { return getProp<bool> (IDs::oneShot); }
137
138//==============================================================================
139int LoopInfo::getRootNote() const { return getProp<int> (IDs::rootNote); }
140void LoopInfo::setRootNote (int note) { setProp<int> (IDs::rootNote, note); }
141
142//==============================================================================
143SampleCount LoopInfo::getInMarker() const { return getProp<juce::int64> (IDs::inMarker); }
144SampleCount LoopInfo::getOutMarker() const { return getProp<juce::int64> (IDs::outMarker); }
145
146void LoopInfo::setInMarker (SampleCount in) { setProp<juce::int64> (IDs::inMarker, in); }
147void LoopInfo::setOutMarker (SampleCount out) { setProp<juce::int64> (IDs::outMarker, out); }
148
149//==============================================================================
151{
152 const juce::ScopedLock sl (lock);
153 return getLoopPoints().getNumChildren();
154}
155
157{
158 const juce::ScopedLock sl (lock);
159 auto lp = getLoopPoints().getChild (idx);
160
161 if (lp.isValid())
162 return { static_cast<juce::int64> (lp.getProperty (IDs::value)),
163 (LoopPointType) static_cast<int> (lp.getProperty (IDs::type)) };
164
165 return {};
166}
167
168void LoopInfo::addLoopPoint (SampleCount pos, LoopPointType type)
169{
170 const juce::ScopedLock sl (lock);
171 duplicateIfShared();
172
173 auto t = createValueTree (IDs::LOOPPOINT,
174 IDs::value, (juce::int64) pos,
175 IDs::type, (int) type);
176
177 getOrCreateLoopPoints().addChild (t, -1, nullptr);
178}
179
180void LoopInfo::changeLoopPoint (int idx, SampleCount pos, LoopPointType type)
181{
182 const juce::ScopedLock sl (lock);
183 duplicateIfShared();
184
185 auto lp = getLoopPoints().getChild (idx);
186 lp.setProperty (IDs::value, (juce::int64) pos, um);
187 lp.setProperty (IDs::type, (int) type, um);
188}
189
191{
192 const juce::ScopedLock sl (lock);
193 duplicateIfShared();
194 getLoopPoints().removeChild (idx, um);
195 removeChildIfEmpty (IDs::LOOPPOINTS);
196}
197
199{
200 const juce::ScopedLock sl (lock);
201 duplicateIfShared();
202 state.removeChild (getLoopPoints(), um);
203}
204
206{
207 const juce::ScopedLock sl (lock);
208 auto lps = getLoopPoints();
209
210 for (int i = lps.getNumChildren(); --i >= 0;)
211 if (((LoopPointType) int (lps.getChild (i).getProperty (IDs::type))) == type)
212 lps.removeChild (i, nullptr);
213}
214
215int LoopInfo::getNumTags() const { const juce::ScopedLock sl (lock); return getTags().getNumChildren(); }
216void LoopInfo::clearTags() { const juce::ScopedLock sl (lock); duplicateIfShared(); state.removeChild (getTags(), um); }
217juce::String LoopInfo::getTag (int idx) const { const juce::ScopedLock sl (lock); return getTags().getChild (idx).getProperty (IDs::name).toString(); }
218
220{
221 const juce::ScopedLock sl (lock);
222 duplicateIfShared();
223
224 juce::ValueTree t (IDs::TAG);
225 t.setProperty (IDs::name, tag, um);
226 getOrCreateTags().addChild (t, -1, um);
227}
228
230{
231 for (const auto& t : tags)
232 addTag (t);
233}
234
235void LoopInfo::initialiseMissingProps()
236{
237 const juce::ScopedLock sl (lock);
238 setPropertyIfMissing (state, IDs::numBeats, 0.0, um);
239 setPropertyIfMissing (state, IDs::denominator, 0, um);
240 setPropertyIfMissing (state, IDs::numerator, 0, um);
241 setPropertyIfMissing (state, IDs::oneShot, 0, um);
242 setPropertyIfMissing (state, IDs::bpm, 0, um);
243 setPropertyIfMissing (state, IDs::rootNote, -1, um);
244 setPropertyIfMissing (state, IDs::inMarker, 0, um);
245 setPropertyIfMissing (state, IDs::outMarker, -1, um);
246}
247
248LoopInfo& LoopInfo::copyFrom (const juce::ValueTree& o)
249{
250 const juce::ScopedLock sl (lock);
251 copyValueTree (state, o, um);
252 return *this;
253}
254
255void LoopInfo::removeChildIfEmpty (const juce::Identifier& i)
256{
257 auto v = state.getChildWithName (i);
258
259 if (v.getNumChildren() == 0)
260 state.removeChild (v, um);
261}
262
263juce::ValueTree LoopInfo::getLoopPoints() const { const juce::ScopedLock sl (lock); return state.getChildWithName (IDs::LOOPPOINTS); }
264juce::ValueTree LoopInfo::getOrCreateLoopPoints() { const juce::ScopedLock sl (lock); return state.getOrCreateChildWithName (IDs::LOOPPOINTS, um); }
265
266juce::ValueTree LoopInfo::getTags() const { const juce::ScopedLock sl (lock); return state.getChildWithName (IDs::TAGS); }
267juce::ValueTree LoopInfo::getOrCreateTags() { const juce::ScopedLock sl (lock); return state.getOrCreateChildWithName (IDs::TAGS, um); }
268
269void LoopInfo::duplicateIfShared()
270{
271 const juce::ScopedLock sl (lock);
272
273 if (state.getReferenceCount() <= 1)
274 return;
275
276 if (maintainParent)
277 {
278 auto parent = state.getParent();
279 int index = -1;
280
281 auto stateCopy = state.createCopy();
282
283 if (parent.isValid())
284 {
285 index = parent.indexOf (state);
286 parent.removeChild (index, um);
287 }
288
289 state = stateCopy;
290
291 if (parent.isValid())
292 parent.addChild (state, index, um);
293 }
294 else
295 {
296 state = state.createCopy();
297 }
298}
299
300static bool isSameFormat (const juce::AudioFormat* af1, const juce::AudioFormat* af2)
301{
302 return af1 == af2 || (af1 != nullptr && af2 != nullptr
303 && af1->getFormatName() == af2->getFormatName());
304}
305
306void LoopInfo::init (const juce::AudioFormatReader* afr, const juce::AudioFormat* af, const juce::File& file)
307{
308 if (afr == nullptr || af == nullptr)
309 return;
310
311 auto& formatManager = engine.getAudioFileFormatManager();
312 const juce::ScopedLock sl (lock);
313
314 #if TRACKTION_ENABLE_REX
315 if (isSameFormat (af, formatManager.getRexFormat()))
316 {
317 setDenominator (afr->metadataValues[RexAudioFormat::rexDenominator].getIntValue());
318 setNumerator (afr->metadataValues[RexAudioFormat::rexDenominator].getIntValue());
319 const double bpm = afr->metadataValues[RexAudioFormat::rexTempo].getDoubleValue();
320 setProp (IDs::bpm, bpm);
321 setProp (IDs::numBeats, bpm * ((afr->lengthInSamples / afr->sampleRate) / 60.0));
322
323 juce::StringArray beatPoints;
324 beatPoints.addTokens (afr->metadataValues[RexAudioFormat::rexBeatPoints], ";", {});
325 beatPoints.removeEmptyStrings();
326
327 for (int i = 0; i < beatPoints.size(); ++i)
328 addLoopPoint (beatPoints[i].getIntValue(), LoopInfo::LoopPointType::manual);
329 }
330 else
331 #endif
332 if (isSameFormat (af, formatManager.getAiffFormat()))
333 {
334 const int rootNote = afr->metadataValues[juce::AiffAudioFormat::appleRootSet] == "1"
335 ? afr->metadataValues[juce::AiffAudioFormat::appleRootNote].getIntValue() : -1;
336
337 setRootNote (rootNote);
338 setProp (IDs::oneShot, afr->metadataValues[juce::AiffAudioFormat::appleOneShot] == "1");
341 const double numBeats = afr->metadataValues[juce::AiffAudioFormat::appleBeats].getDoubleValue();
342 setProp (IDs::numBeats, numBeats);
343 setProp (IDs::bpm, (numBeats * 60.0) / (afr->lengthInSamples / afr->sampleRate));
344
346 t.addTokens (afr->metadataValues[juce::AiffAudioFormat::appleTag], ";", {});
348 t.removeEmptyStrings();
349
350 for (int i = 0; i < t.size(); ++i)
351 addTag (t[i]);
352 }
353 else if (isSameFormat (af, formatManager.getWavFormat()))
354 {
356
357 if (s.isNotEmpty())
358 {
359 if (auto n = juce::parseXML (s))
360 copyFrom (juce::ValueTree::fromXml (*n));
361 }
362 else
363 {
364 const int rootNote = afr->metadataValues[juce::WavAudioFormat::acidRootSet] == "1"
365 ? afr->metadataValues[juce::WavAudioFormat::acidRootNote].getIntValue() : -1;
366 const double numBeats = afr->metadataValues[juce::WavAudioFormat::acidBeats].getDoubleValue();
367
368 if (rootNote == -1)
369 if (auto smplNote = afr->metadataValues["MidiUnityNote"].getIntValue())
370 setRootNote (smplNote);
371
372 setRootNote (rootNote);
373 setNumBeats (numBeats);
374 setProp (IDs::oneShot, afr->metadataValues[juce::WavAudioFormat::acidOneShot] == "1");
378 setProp (IDs::bpm, (numBeats * 60.0) / (afr->lengthInSamples / afr->sampleRate));
379 }
380 }
381 else if (isSameFormat (af, formatManager.getNativeAudioFormat()))
382 {
384
385 if (s.isNotEmpty())
386 {
387 if (auto n = juce::parseXML (s))
388 copyFrom (juce::ValueTree::fromXml (*n));
389 }
390 else
391 {
392 const juce::String tempoString = afr->metadataValues["tempo"];
393 auto bpm = tempoString.getDoubleValue();
394
395 setProp (IDs::bpm, bpm);
396
397 if (tempoString.isNotEmpty())
398 {
399 const double fileDuration = afr->lengthInSamples / afr->sampleRate;
400 setNumBeats ((fileDuration / 60.0) * bpm);
401 }
402
403 const juce::String beatCount = afr->metadataValues["beat count"];
404
405 if (beatCount.isNotEmpty() && bpm == 0)
406 {
407 const double fileDuration = afr->lengthInSamples / afr->sampleRate;
408 const int beats = beatCount.getIntValue();
409
410 setProp (IDs::bpm, beats / fileDuration / 60.0);
411 setNumBeats (beats);
412 }
413
414 const juce::String timeSig (afr->metadataValues["time signature"]);
415
416 if (timeSig.isNotEmpty())
417 {
418 setDenominator (timeSig.upToFirstOccurrenceOf ("/", false, false).getIntValue());
419 setNumerator (timeSig.fromFirstOccurrenceOf ("/", false, false).getIntValue());
420 }
421
422 const juce::String keySig (afr->metadataValues["key signature"]);
423
424 if (keySig.isNotEmpty())
425 {
426 bool sharpOrFlat = keySig[1] == '#' || keySig[1] == 'b';
427 setRootNote (Pitch::getPitchFromString (engine, keySig.substring (0, sharpOrFlat ? 2 : 1)));
428 addTag (keySig.getLastCharacter() == 'm' ? "minor" : "major");
429 }
430 }
431 }
434 {
435 const int rootNote = afr->metadataValues[juce::WavAudioFormat::acidRootSet] == "1"
436 ? afr->metadataValues[juce::WavAudioFormat::acidRootNote].getIntValue() : -1;
437 const double numBeats = afr->metadataValues[juce::WavAudioFormat::acidBeats].getDoubleValue();
438
439 setRootNote (rootNote);
440 setNumBeats (numBeats);
441 setProp (IDs::oneShot, afr->metadataValues[juce::WavAudioFormat::acidOneShot] == "1");
445 setProp (IDs::bpm, (numBeats * 60.0) / (afr->lengthInSamples / afr->sampleRate));
446 }
447 else if (afr->metadataValues.containsKey ("MidiUnityNote"))
448 {
449 auto note = afr->metadataValues["MidiUnityNote"].getIntValue();
450 if (note > 0)
451 setRootNote (note);
452 }
453
454 if (file != juce::File() && float (state.getProperty (IDs::bpm)) < 0.001f)
455 deduceTempo (file, *afr);
456
457 initialiseMissingProps();
458}
459
460std::optional<float> LoopInfo::getCueTempo (const juce::StringPairArray& metadata)
461{
462 if (auto tempoStr = metadata["CueLabel0Text"]; tempoStr.isNotEmpty())
463 if (tempoStr.contains ("Tempo:"))
464 if (auto val = tempoStr.fromFirstOccurrenceOf ("Tempo: ", false, false).getFloatValue(); val >= 50 && val <= 250)
465 return val;
466
467 return {};
468}
469
470static juce::StringArray reverseTokens (juce::StringRef stringToTokenise, juce::StringRef breakCharacters, juce::StringRef quoteCharacters)
471{
472 auto tokens = juce::StringArray::fromTokens (stringToTokenise, breakCharacters, quoteCharacters);
473 std::reverse (tokens.strings.begin(), tokens.strings.end());
474 return tokens;
475}
476
477std::optional<float> LoopInfo::getFileNameTempo (const juce::String& rawName)
478{
479 auto name = rawName.replace (" ", "_").replace ("-", "_");
480
481 for (auto token : reverseTokens (name, "_", ""))
482 {
483 if (token.containsIgnoreCase ("bpm"))
484 {
485 token = token.replace ("bpm", "", true).trim();
486
487 while (token.startsWith ("0"))
488 token = token.substring (1);
489
490 auto val = token.getIntValue();
491
492 if (val > 50 && val < 250 && juce::String (val) == token)
493 return float (val);
494 }
495 }
496
497 for (auto token : reverseTokens (name, "_", ""))
498 {
499 auto val = token.getIntValue();
500
501 while (token.startsWith ("0"))
502 token = token.substring (1);
503
504 if (val > 50 && val < 250 && juce::String (val) == token)
505 return float (val);
506 }
507
508 return {};
509}
510
511std::optional<int> LoopInfo::getFileNameRootNote (const juce::String& rawName)
512{
513 auto name = rawName.replace (" ", "_").toLowerCase();
514
515 for (auto token : reverseTokens (name, "_", ""))
516 {
517 if (token.endsWith ("min")) token = token.dropLastCharacters (3);
518 else if (token.endsWith ("maj")) token = token.dropLastCharacters (3);
519 else if (token.endsWith ("m")) token = token.dropLastCharacters (1);
520
521 if (token == "a") return 57;
522 if (token == "a#") return 58;
523 if (token == "bb") return 58;
524 if (token == "b") return 59;
525 if (token == "c") return 60;
526 if (token == "c#") return 61;
527 if (token == "db") return 61;
528 if (token == "d") return 62;
529 if (token == "d#") return 63;
530 if (token == "eb") return 63;
531 if (token == "e") return 64;
532 if (token == "f") return 65;
533 if (token == "f#") return 66;
534 if (token == "gb") return 66;
535 if (token == "g") return 67;
536 if (token == "g#") return 68;
537 if (token == "ab") return 68;
538 }
539
540 return {};
541}
542
543bool LoopInfo::deduceTempo (const juce::File& file, const juce::AudioFormatReader& afr)
544{
545 auto len = afr.lengthInSamples / afr.sampleRate;
546 if (len <= 1.0 && len > 60.0)
547 return false;
548
549 auto fn = file.getFileNameWithoutExtension();
550
551 auto tempo = getCueTempo (afr.metadataValues);
552 if (! tempo.has_value())
553 tempo = getFileNameTempo (fn);
554
555 if (! tempo.has_value())
556 return false;
557
558 auto beats = *tempo / 60 * len;
559 auto rem = std::fmod (beats, 4.0f);
560 if (rem < 0.0f || (rem > 0.1f && rem < 3.9f) || rem > 4.0f)
561 return false;
562
563 setNumBeats (beats);
564 setProp (IDs::oneShot, false);
565 setDenominator (4);
566 setNumerator (4);
567 setProp (IDs::bpm, (beats * 60.0) / (afr.lengthInSamples / afr.sampleRate));
568
569 if (auto root = getFileNameRootNote (fn))
570 {
571 setRootNote (*root);
572 #if LOG_DEDUCED_TEMPO
573 DBG(fn + juce::String::formatted (": %.1f bpm %.1f beats ", *tempo, beats) + juce::MidiMessage::getMidiNoteName (*root, true, false, 4));
574 #endif
575 }
576 else
577 {
578 #if LOG_DEDUCED_TEMPO
579 DBG(fn + juce::String::formatted (": %.1f bpm %.1f beats", *tempo, beats));
580 #endif
581 }
582
583 return true;
584}
585
586}} // namespace tracktion { inline namespace engine
static const char *const appleDenominator
static const char *const appleOneShot
static const char *const appleNumerator
static const char *const appleTag
static const char *const appleBeats
static const char *const appleRootSet
static const char *const appleKey
static const char *const appleRootNote
StringPairArray metadataValues
const String & getFormatName() const
std::unique_ptr< FileInputStream > createInputStream() const
static String getMidiNoteName(int noteNumber, bool useSharps, bool includeOctaveNumber, int octaveNumForMiddleC)
void removeEmptyStrings(bool removeWhitespaceStrings=true)
static StringArray fromTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
int size() const noexcept
int addTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
bool containsKey(StringRef key) const noexcept
String trim() const
String dropLastCharacters(int numberToDrop) const
static String formatted(const String &formatStr, Args... args)
String toLowerCase() const
double getDoubleValue() const noexcept
String replace(StringRef stringToReplace, StringRef stringToInsertInstead, bool ignoreCase=false) const
String substring(int startIndex, int endIndex) const
int getIntValue() const noexcept
bool isNotEmpty() const noexcept
void removeChild(const ValueTree &child, UndoManager *undoManager)
ValueTree getChild(int index) const
int getNumChildren() const noexcept
int getReferenceCount() const noexcept
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
ValueTree getParent() const noexcept
const var & getProperty(const Identifier &name) const noexcept
ValueTree createCopy() const
ValueTree getChildWithName(const Identifier &type) const
ValueTree getOrCreateChildWithName(const Identifier &type, UndoManager *undoManager)
static const char *const acidRootSet
static const char *const acidOneShot
static const char *const acidDenominator
static const char *const acidBeats
static const char *const tracktionLoopInfo
static const char *const acidNumerator
static const char *const acidRootNote
The Engine is the central class for all tracktion sessions.
AudioFileFormatManager & getAudioFileFormatManager() const
Returns the AudioFileFormatManager that maintains a list of available audio file formats.
Holds tempo/beat information about an audio file.
bool isOneShot() const
Returns true if this can be is a one-shot e.g.
SampleCount getOutMarker() const
Returns the sample number used as the end point in the file.
void clearLoopPoints()
Removes all the loop points.
void setInMarker(SampleCount)
Sets the sample number to be used as the start point in the file.
void setNumerator(int newNumerator)
Sets the numerator of the object.
void setDenominator(int newDenominator)
Sets the denominator of the object.
void setBpm(double newBpm, const AudioFileInfo &)
Sets the tempo of the object.
void addLoopPoint(SampleCount, LoopPointType)
Adds a loop point at the given position.
LoopInfo(Engine &)
Creates an empty LoopInfo.
int getDenominator() const
Returns the denominator of the object.
int getNumTags() const
Returns the number of tags.
LoopInfo & operator=(const LoopInfo &)
Creates a copy of another LoopInfo.
void addTag(const juce::String &tag)
Adds a tag.
bool isLoopable() const
Returns true if this can be looped.
LoopPoint getLoopPoint(int index) const
Returns the loop points at the given index.
int getRootNote() const
Returns the root note of the object.
void changeLoopPoint(int index, SampleCount, LoopPointType)
Sets the loop point at the given index to a new position and type.
int getNumLoopPoints() const
Returns the number of loop points in the object.
LoopPointType
Enum to represet the type of loop point.
SampleCount getInMarker() const
Returns the sample number used as the start point in the file.
void addTags(const juce::StringArray &tags)
Adds multiple tags.
double getNumBeats() const
Returns the number of beats.
double getBpm(const AudioFileInfo &) const
Returns the tempo of the object.
void clearTags()
Removes all the tags.
void setOutMarker(SampleCount)
Sets the sample number to be used as the end point in the file.
void deleteLoopPoint(int index)
Removes the loop point at the given index.
int getNumerator() const
Returns the numerator of the object.
Engine & engine
The engine this belongs to.
juce::String getTag(int index) const
Returns the tag at a given index.
void setRootNote(int note)
Sets the root note of the object.
void setNumBeats(double newNumBeats)
Sets the number of beats.
double getBeatsPerSecond(const AudioFileInfo &) const
Returns the tempo of the object.
T fmod(T... args)
T get(T... args)
#define jassert(expression)
#define DBG(textToWrite)
typedef float
constexpr Type jmax(Type a, Type b)
std::unique_ptr< XmlElement > parseXML(const String &textToParse)
long long int64
T reverse(T... args)
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.