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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_MelodyneFileReader.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
11#if TRACKTION_ENABLE_ARA
12
13//==============================================================================
14#if JUCE_MSVC
15 #pragma warning (push, 0)
16#elif JUCE_CLANG
17 #pragma clang diagnostic push
18 #pragma clang diagnostic ignored "-Wnon-virtual-dtor"
19 #pragma clang diagnostic ignored "-Wreorder"
20 #pragma clang diagnostic ignored "-Wunsequenced"
21 #pragma clang diagnostic ignored "-Wint-to-pointer-cast"
22 #pragma clang diagnostic ignored "-Wunused-parameter"
23 #pragma clang diagnostic ignored "-Wconversion"
24 #pragma clang diagnostic ignored "-Woverloaded-virtual"
25 #pragma clang diagnostic ignored "-Wshadow"
26 #pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
27 #if __clang_major__ >= 10
28 #pragma clang diagnostic ignored "-Wpragma-pack"
29 #endif
30#endif
31
32#undef PRAGMA_ALIGN_SUPPORTED
33#undef VST_FORCE_DEPRECATED
34#define VST_FORCE_DEPRECATED 0
35
36#ifndef JUCE_MSVC
37 #define __cdecl
38#endif
39
40// If you get an error here, in order to build with ARA support you'll need
41// to include the SDK in your header search paths!
42#include "ARA_API/ARAVST3.h"
43#include "ARA_Library/Dispatch/ARAHostDispatch.h"
44
47
48namespace ARA
49{
50 DEF_CLASS_IID (IMainFactory)
51 DEF_CLASS_IID (IPlugInEntryPoint)
52 DEF_CLASS_IID (IPlugInEntryPoint2)
53}
54
55#if JUCE_MSVC
56 #pragma warning (pop)
57#elif JUCE_CLANG
58 #pragma clang diagnostic pop
59#endif
60
61namespace tracktion { inline namespace engine
62{
63
64using namespace ARA;
65
66struct ARAClipPlayer : private Selectable::Listener
67{
68 #include "tracktion_MelodyneInstanceFactory.h"
69 #include "tracktion_ARAWrapperFunctions.h"
70 #include "tracktion_ARAWrapperInterfaces.h"
71
72 //==============================================================================
73 ARAClipPlayer (Edit& ed, MelodyneFileReader& o, AudioClipBase& c)
74 : Selectable::Listener (ed.tempoSequence), owner (o),
75 clip (c),
76 file (c.getAudioFile()),
77 edit (ed)
78 {
79 TRACKTION_ASSERT_MESSAGE_THREAD
80 jassert (file.getFile().existsAsFile());
81 }
82
83 ~ARAClipPlayer()
84 {
86 TRACKTION_ASSERT_MESSAGE_THREAD
87
88 contentAnalyserChecker = nullptr;
89 modelUpdater = nullptr;
90 contentUpdater = nullptr;
91
92 // Needs to happen before killing off ARA stuff
93 if (auto p = getPlugin())
94 {
95 p->hideWindowForShutdown();
96
97 if (auto pi = p->getAudioPluginInstance())
98 pi->releaseResources();
99 }
100
101 if (auto doc = getDocument())
102 {
103 if (doc->dci != nullptr)
104 {
105 {
106 const ScopedDocumentEditor sde (*this, false);
107 playbackRegionAndSource = nullptr;
108 }
109
110 melodyneInstance = nullptr;
111 }
112 }
113 }
114
115 //==============================================================================
116 Edit& getEdit() { return edit; }
117 AudioClipBase& getClip() { return clip; }
118 ExternalPlugin* getPlugin() { return melodyneInstance != nullptr ? melodyneInstance->plugin.get() : nullptr; }
119 const ARAFactory* getARAFactory() const { return melodyneInstance != nullptr ? melodyneInstance->factory : nullptr; }
120
121 //==============================================================================
122 bool initialise (ARAClipPlayer* clipToClone)
123 {
124 TRACKTION_ASSERT_MESSAGE_THREAD
126
127 if (auto doc = getDocument())
128 {
129 ExternalPlugin::Ptr p = MelodyneInstanceFactory::getInstance (edit.engine).createPlugin (edit);
130
131 if (p == nullptr || getDocument() == nullptr)
132 return false;
133
134 melodyneInstance.reset (MelodyneInstanceFactory::getInstance (edit.engine).createInstance (*p, doc->dcRef));
135
136 if (melodyneInstance == nullptr)
137 return false;
138
139 updateContent (clipToClone);
140
141 return playbackRegionAndSource != nullptr
142 && playbackRegionAndSource->playbackRegion != nullptr;
143 }
144
145 return false;
146 }
147
148 void contentHasChanged()
149 {
151 updateContent (nullptr);
152 owner.sendChangeMessage();
153 }
154
155 void selectableObjectChanged (Selectable*) override
156 {
157 if (auto doc = getDocument())
158 {
159 if (doc->musicalContext != nullptr)
160 {
161 doc->beginEditing (true);
162 doc->musicalContext->update();
163 doc->endEditing (true);
164 }
165 }
166 }
167
168 void selectableObjectAboutToBeDeleted (Selectable*) override {}
169
170 //==============================================================================
171 void updateContent (ARAClipPlayer* clipToClone)
172 {
174 TRACKTION_ASSERT_MESSAGE_THREAD
175
176 if (juce::MessageManager::getInstance()->isThisTheMessageThread()
177 && getEdit().getTransport().isAllowedToReallocate())
178 {
179 contentUpdater = nullptr;
180 internalUpdateContent (clipToClone);
181 }
182 else
183 {
184 if (contentUpdater == nullptr)
185 {
186 contentUpdater = std::make_unique<ContentUpdater> (*this);
187 }
188 else
189 {
190 if (! contentUpdater->isTimerRunning()) //To avoid resetting it
191 contentUpdater->startTimer (100);
192 }
193 }
194 }
195
196 //==============================================================================
197 juce::MidiMessageSequence getAnalysedMIDISequence()
198 {
200
201 const int midiChannel = 1;
203
204 if (auto doc = getDocument())
205 {
206 const ARADocumentControllerInterface* dci = doc->dci;
207 ARADocumentControllerRef dcRef = doc->dcRef;
208 ARAAudioSourceRef audioSourceRef = playbackRegionAndSource->audioSource->audioSourceRef;
209
210 if (dci->isAudioSourceContentAvailable (dcRef, audioSourceRef, kARAContentTypeNotes))
211 {
212 ARAContentReaderRef contentReaderRef = dci->createAudioSourceContentReader (dcRef, audioSourceRef, kARAContentTypeNotes, nullptr);
213 int numEvents = (int) dci->getContentReaderEventCount (dcRef, contentReaderRef);
214
215 for (int i = 0; i < numEvents; ++i)
216 {
217 if (auto note = static_cast<const ARAContentNote*> (dci->getContentReaderDataForEvent (dcRef, contentReaderRef, i)))
218 {
219 if (note->pitchNumber != kARAInvalidPitchNumber)
220 {
221 result.addEvent (juce::MidiMessage::noteOn (midiChannel, note->pitchNumber, static_cast<float> (note->volume)),
222 note->startPosition);
223
224 result.addEvent (juce::MidiMessage::noteOff (midiChannel, note->pitchNumber),
225 note->startPosition + note->noteDuration);
226 }
227 }
228 }
229
230 dci->destroyContentReader (dcRef, contentReaderRef);
231 }
232
233 result.updateMatchedPairs();
234 }
235
236 return result;
237 }
238
239 //==============================================================================
240 void setViewSelection()
241 {
242 if (playbackRegionAndSource != nullptr)
243 playbackRegionAndSource->setViewSelection();
244 }
245
246 //==============================================================================
247 void startProcessing() { TRACKTION_ASSERT_MESSAGE_THREAD if (playbackRegionAndSource != nullptr) playbackRegionAndSource->enable(); }
248 void stopProcessing() { TRACKTION_ASSERT_MESSAGE_THREAD if (playbackRegionAndSource != nullptr) playbackRegionAndSource->disable(); }
249
250 class ContentAnalyser
251 {
252 public:
253 ContentAnalyser (const ARAClipPlayer& p) : pimpl (p)
254 {
255 }
256
257 bool isAnalysing()
258 {
259 callBlocking ([this] { updateAnalysingContent(); });
260
261 return analysingContent;
262 }
263
264 void updateAnalysingContent()
265 {
267
268 auto doc = pimpl.getDocument();
269
270 if (doc == nullptr)
271 {
272 analysingContent = false;
273 return;
274 }
275
276 const ARADocumentControllerInterface* dci = doc->dci;
277 ARADocumentControllerRef dcRef = doc->dcRef;
278 ARAAudioSourceRef audioSourceRef = nullptr;
279
280 if (pimpl.playbackRegionAndSource != nullptr)
281 if (pimpl.playbackRegionAndSource->audioSource != nullptr)
282 audioSourceRef = pimpl.playbackRegionAndSource->audioSource->audioSourceRef;
283
284 if (dci != nullptr && dcRef != nullptr && audioSourceRef != nullptr)
285 {
286 if (firstCall)
287 {
288 auto araFactory = pimpl.getARAFactory();
289 for (ARAContentType contentType : { kARAContentTypeBarSignatures, kARAContentTypeTempoEntries })
290 {
291 for (int i = 0; i < (int) araFactory->analyzeableContentTypesCount; i++)
292 {
293 if (araFactory->analyzeableContentTypes[i] == contentType)
294 {
295 typesBeingAnalyzed.push_back (contentType);
296 break;
297 }
298 }
299 }
300
301 if (!typesBeingAnalyzed.empty())
302 dci->requestAudioSourceContentAnalysis (dcRef, audioSourceRef, (ARASize)typesBeingAnalyzed.size(), typesBeingAnalyzed.data());
303
304 firstCall = false;
305 }
306
307 analysingContent = false;
308 for (ARAContentType contentType : typesBeingAnalyzed)
309 {
310 analysingContent = (dci->isAudioSourceContentAnalysisIncomplete (dcRef, audioSourceRef, contentType) != kARAFalse);
311 if (analysingContent)
312 break;
313 }
314 }
315 else
316 {
317 analysingContent = false;
318 }
319 }
320
321 private:
322 const ARAClipPlayer& pimpl;
323 std::vector<ARAContentType> typesBeingAnalyzed;
324 volatile bool analysingContent = false;
325 bool firstCall = true;
326
327 ContentAnalyser() = delete;
329 };
330
331 friend class ContentAnalyser;
332
333 std::unique_ptr<ContentAnalyser> contentAnalyserChecker;
334
335 bool isAnalysingContent() const
336 {
337 return contentAnalyserChecker->isAnalysing();
338 }
339
340 ARADocument* getDocument() const;
341
342private:
343 //==============================================================================
344 MelodyneFileReader& owner;
345 AudioClipBase& clip;
346 const AudioFile file;
347 Edit& edit;
348
349 std::unique_ptr<MelodyneInstance> melodyneInstance;
350 std::unique_ptr<PlaybackRegionAndSource> playbackRegionAndSource;
351 HashCode currentHashCode = 0;
352
353 //==============================================================================
354 struct ScopedDocumentEditor
355 {
356 ScopedDocumentEditor (ARAClipPlayer& o, bool restartModelUpdaterLater)
357 : owner (o), restartTimerLater (restartModelUpdaterLater)
358 {
359 if (restartTimerLater)
360 owner.modelUpdater = nullptr;
361
362 owner.getDocument()->beginEditing (false);
363 }
364
365 ~ScopedDocumentEditor()
366 {
367 if (auto doc = owner.getDocument())
368 {
369 doc->endEditing (false);
370
371 if (restartTimerLater)
372 owner.modelUpdater = std::make_unique<ModelUpdater> (*doc);
373 }
374 }
375
376 private:
377 ARAClipPlayer& owner;
378 const bool restartTimerLater;
379
380 JUCE_DECLARE_NON_COPYABLE (ScopedDocumentEditor)
381 };
382
383 //==============================================================================
388 void recreateTrack (ARAClipPlayer* clipToClone)
389 {
391 TRACKTION_ASSERT_MESSAGE_THREAD
392
393 jassert (melodyneInstance != nullptr);
394 jassert (melodyneInstance->factory != nullptr);
395 jassert (melodyneInstance->extensionInstance != nullptr);
396
397 auto oldTrack = std::move (playbackRegionAndSource);
398
399 playbackRegionAndSource = std::make_unique<PlaybackRegionAndSource> (*getDocument(), clip, *melodyneInstance->factory,
400 *melodyneInstance->extensionInstance,
401 juce::String::toHexString (currentHashCode),
402 clipToClone != nullptr ? clipToClone->playbackRegionAndSource.get() : nullptr);
403
404 if (oldTrack != nullptr)
405 {
406 const ScopedDocumentEditor sde (*this, false);
407 oldTrack = nullptr;
408 }
409 }
410
411 void internalUpdateContent (ARAClipPlayer* clipToClone)
412 {
414 TRACKTION_ASSERT_MESSAGE_THREAD
415
416 if (auto doc = getDocument())
417 {
418 jassert (doc->dci != nullptr);
419
420 contentAnalyserChecker = nullptr;
421 modelUpdater = nullptr; // Can't be editing the document in any way while restoring
422
423 HashCode newHashCode = file.getHash()
424 ^ file.getFile().getLastModificationTime().toMilliseconds()
425 ^ static_cast<HashCode> (clip.itemID.getRawID());
426
427 if (currentHashCode != newHashCode)
428 {
429 currentHashCode = newHashCode;
430 const ScopedDocumentEditor sde (*this, true);
431
432 recreateTrack (clipToClone);
433 }
434 else
435 {
436 if (playbackRegionAndSource != nullptr
437 && playbackRegionAndSource->playbackRegion != nullptr)
438 {
439 const ScopedDocumentEditor sde (*this, true);
440 playbackRegionAndSource->playbackRegion->updateRange();
441 }
442 }
443
444 modelUpdater = std::make_unique<ModelUpdater> (*doc);
445
446 if (contentAnalyserChecker == nullptr)
447 contentAnalyserChecker = std::make_unique<ContentAnalyser> (*this);
448 }
449 }
450
451 //==============================================================================
452 struct ContentUpdater : public juce::Timer
453 {
454 ContentUpdater (ARAClipPlayer& p) : owner (p) { startTimer (100); }
455
456 ARAClipPlayer& owner;
457
458 void timerCallback() override
459 {
461
462 if (owner.getEdit().getTransport().isAllowedToReallocate())
463 {
464 owner.internalUpdateContent (nullptr);
465 stopTimer();
466 }
467 }
468
470 };
471
472 std::unique_ptr<ContentUpdater> contentUpdater;
473
474 //==============================================================================
475 struct ModelUpdater : private juce::Timer
476 {
477 ModelUpdater (ARADocument& d) : document (d) { startTimer (3000); }
478
479 ARADocument& document;
480
481 void timerCallback() override
482 {
484 if (document.dci != nullptr && document.dcRef != nullptr)
485 document.dci->notifyModelUpdates (document.dcRef);
486 }
487
489 };
490
492
493 //==============================================================================
494 ARAClipPlayer() = delete;
496};
497
498//==============================================================================
499MelodyneFileReader::MelodyneFileReader (Edit& ed, AudioClipBase& clip)
500{
501 TRACKTION_ASSERT_MESSAGE_THREAD
503
504 player = std::make_unique<ARAClipPlayer> (ed, *this, clip);
505
506 if (! player->initialise (nullptr))
507 player = nullptr;
508}
509
510MelodyneFileReader::MelodyneFileReader (Edit& ed, AudioClipBase& clip, MelodyneFileReader& other)
511{
512 TRACKTION_ASSERT_MESSAGE_THREAD
514
515 if (other.player != nullptr)
516 {
517 player = std::make_unique<ARAClipPlayer> (ed, *this, clip);
518
519 if (! player->initialise (other.player.get()))
520 player = nullptr;
521 }
522
523 jassert (player != nullptr);
524}
525
526MelodyneFileReader::~MelodyneFileReader()
527{
528 TRACKTION_ASSERT_MESSAGE_THREAD
530
531 if (player != nullptr)
532 if (auto plugin = player->getPlugin())
533 if (auto pi = plugin->getAudioPluginInstance())
534 pi->setPlayHead (nullptr);
535
536 auto toDestroy = std::move (player);
537}
538
539//==============================================================================
540void MelodyneFileReader::showPluginWindow()
541{
542 if (player != nullptr)
543 player->setViewSelection();
544
545 if (auto p = getPlugin())
546 p->showWindowExplicitly();
547}
548
549void MelodyneFileReader::hidePluginWindow()
550{
551 if (auto p = getPlugin())
552 p->hideWindowForShutdown();
553}
554
555ExternalPlugin* MelodyneFileReader::getPlugin()
556{
557 if (isValid())
558 return player->getPlugin();
559
560 return {};
561}
562
563//==============================================================================
564bool MelodyneFileReader::isAnalysingContent()
565{
566 return player != nullptr && player->isAnalysingContent();
567}
568
569void MelodyneFileReader::sourceClipChanged()
570{
571 if (player != nullptr)
572 player->updateContent (nullptr);
573}
574
575//==============================================================================
576juce::MidiMessageSequence MelodyneFileReader::getAnalysedMIDISequence()
577{
578 if (player != nullptr)
579 return player->getAnalysedMIDISequence();
580
581 return {};
582}
583
584void MelodyneFileReader::cleanUpOnShutdown()
585{
586 ARAClipPlayer::MelodyneInstanceFactory::shutdown();
587}
588
589//==============================================================================
590struct ARADocumentHolder::Pimpl
591{
592 Pimpl (Edit& e) : edit (e) {}
593
594 void initialise()
595 {
596 TRACKTION_ASSERT_MESSAGE_THREAD
597 araDocument.reset (ARAClipPlayer::createDocument (edit));
598
599 if (araDocument != nullptr)
600 {
601 araDocument->beginRestoringState (edit.getARADocument().lastState);
602
603 visitAllTrackItems (edit, [] (TrackItem& i)
604 {
605 if (auto c = dynamic_cast<AudioClipBase*> (&i))
606 c->loadMelodyneState();
607
608 return true;
609 });
610
611 araDocument->endRestoringState();
612 }
613 }
614
615 Edit& edit;
617
619};
620
621ARADocumentHolder::ARADocumentHolder (Edit& e, const juce::ValueTree& v)
622 : edit (e), lastState (v)
623{
624}
625
626ARADocumentHolder::~ARADocumentHolder()
627{
628 TRACKTION_ASSERT_MESSAGE_THREAD
630 pimpl = nullptr;
631}
632
633ARADocumentHolder::Pimpl* ARADocumentHolder::getPimpl()
634{
635 if (pimpl == nullptr)
636 {
638 pimpl = std::make_unique<Pimpl> (edit);
639 callBlocking ([this]() { pimpl->initialise(); });
640 }
641
642 return pimpl.get();
643}
644
645void ARADocumentHolder::flushStateToValueTree()
646{
647 TRACKTION_ASSERT_MESSAGE_THREAD
648
649 if (pimpl != nullptr)
650 if (pimpl->araDocument != nullptr)
651 pimpl->araDocument->flushStateToValueTree (lastState);
652}
653
654ARAClipPlayer::ARADocument* ARAClipPlayer::getDocument() const
655{
656 if (auto p = edit.getARADocument().getPimpl())
657 return p->araDocument.get();
658
659 return {};
660}
661
662}} // namespace tracktion { inline namespace engine
663
664#else
665
666//==============================================================================
667namespace tracktion { inline namespace engine
668{
669
672
673MelodyneFileReader::MelodyneFileReader (Edit&, AudioClipBase&) {}
674MelodyneFileReader::MelodyneFileReader (Edit&, AudioClipBase&, MelodyneFileReader&) {}
675MelodyneFileReader::~MelodyneFileReader() {}
676
677void MelodyneFileReader::cleanUpOnShutdown() {}
678ExternalPlugin* MelodyneFileReader::getPlugin() { return {}; }
679void MelodyneFileReader::showPluginWindow() {}
680void MelodyneFileReader::hidePluginWindow() {}
681bool MelodyneFileReader::isAnalysingContent() { return false; }
682juce::MidiMessageSequence MelodyneFileReader::getAnalysedMIDISequence() { return {}; }
683void MelodyneFileReader::sourceClipChanged() {}
684
685ARADocumentHolder::ARADocumentHolder (Edit& e, const juce::ValueTree&) : edit (e) { juce::ignoreUnused (edit); }
686ARADocumentHolder::~ARADocumentHolder() {}
687ARADocumentHolder::Pimpl* ARADocumentHolder::getPimpl() { return {}; }
688void ARADocumentHolder::flushStateToValueTree() {}
689
690}} // namespace tracktion { inline namespace engine
691
692#endif
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
void updateMatchedPairs() noexcept
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
static String toHexString(IntegerType number)
Base class for Clips that produce some kind of audio e.g.
The Tracktion Edit class!
T is_pointer_v
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE(className)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
typedef int
void ignoreUnused(Types &&...) noexcept
void visitAllTrackItems(const Edit &edit, std::function< bool(TrackItem &)> f)
Calls a function for all TrackItems in an Edit.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.