Anklang-0.3.0-1416-gfa8ce8d2 anklang-0.3.0-1416-gfa8ce8d2
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
clip.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "trkn/tracktion.hh" // PCH include must come first
3
4#include "clip.hh"
5#include "track.hh"
6#include "project.hh"
7#include "internal.hh"
8#include <map>
9
10namespace te = tracktion::engine;
11
12namespace Ase {
13
14// == ClipStateListener ==
16 ClipImpl &aseclip_;
17 juce::ValueTree clip_state_, tempo_state_;
18 // Handles refer to the Edit note trees, including notes held by undo.
20 int32 next_note_id_ = 0;
21public:
22 ClipStateListener (ClipImpl &aseclip) :
23 aseclip_ (aseclip), clip_state_ (aseclip_.clip_->state), tempo_state_ (aseclip_.clip_->edit.tempoSequence.getState())
24 {
25 clip_state_.addListener (this);
26 tempo_state_.addListener (this);
27 }
28 ~ClipStateListener() override
29 {
30 tempo_state_.removeListener (this);
31 clip_state_.removeListener (this);
32 }
33 void
34 prune_notes ()
35 {
36 std::erase_if (note_states_, [] (const auto &entry) { return entry.second.getReferenceCount() == 1; });
37 }
39 describe_note (const te::MidiNote &note)
40 {
41 int32 id = 0;
42 for (const auto &entry : note_states_)
43 if (entry.second == note.state)
44 {
45 id = entry.first;
46 break;
47 }
48 if (!id)
49 {
50 id = ++next_note_id_;
51 note_states_.emplace (id, note.state);
52 }
53 return { id, note.getNoteNumber(), note.getStartBeat().inBeats(), note.getLengthBeats().inBeats(),
54 note.getVelocity(), note.getColour(), note.isMute() };
55 }
57 find_note (te::MidiList &sequence, int32 id)
58 {
59 auto entry = note_states_.find (id);
60 return entry == note_states_.end() ? nullptr : sequence.getNoteFor (entry->second);
61 }
62 void
63 notify_notes (const juce::ValueTree &tree)
64 {
65 if (tree == clip_state_ || tree.isAChildOf (clip_state_))
66 {
67 aseclip_.emit_notify ("notes");
68 aseclip_.emit_notify ("all_notes");
69 }
70 }
71 void
72 valueTreePropertyChanged (juce::ValueTree &tree, const juce::Identifier &property) override
73 {
74 if (tree.hasType (te::IDs::NOTE))
75 notify_notes (tree);
76 if (tree == tempo_state_ || tree.isAChildOf (tempo_state_))
77 {
78 // Tempo and time signature changes can move clip beats
79 aseclip_.emit_notify ("start_beat");
80 aseclip_.emit_notify ("end_beat");
81 }
82 return_unless (tree == clip_state_);
83 if (property == tracktion::engine::IDs::name)
84 aseclip_.emit_notify ("name");
85 else if (property == tracktion::engine::IDs::start ||
86 property == tracktion::engine::IDs::length ||
87 property == tracktion::engine::IDs::offset)
88 {
89 aseclip_.emit_notify ("position");
90 aseclip_.emit_notify ("start_beat");
91 aseclip_.emit_notify ("end_beat");
92 }
93 else if (property == tracktion::engine::IDs::mute)
94 aseclip_.emit_notify ("muted");
95 else if (property == tracktion::engine::IDs::volDb || property == tracktion::engine::IDs::gain)
96 aseclip_.emit_notify ("volume");
97 else if (property == tracktion::engine::IDs::pan)
98 aseclip_.emit_notify ("pan");
99 else
100 aseclip_.emit_notify ("notes"); // Simplistic change detection for notes within state
101 }
102 void valueTreeChildAdded (juce::ValueTree &parent, juce::ValueTree&) override { notify_notes (parent); }
103 void valueTreeChildRemoved (juce::ValueTree &parent, juce::ValueTree&, int) override { notify_notes (parent); }
104 void valueTreeParentChanged (juce::ValueTree&) override {}
105 void valueTreeChildOrderChanged (juce::ValueTree&, int, int) override {}
106};
107
108
109// == ClipImpl ==
110ClipImpl::ClipImpl (tracktion::Clip &clip) :
111 clip_ (&clip)
112{
113 register_ase_obj (this, clip);
114 state_listener_ = std::make_unique<ClipStateListener> (*this);
115}
116
117ClipImplP
118ClipImpl::from_trkn (tracktion::Clip &c)
119{
120 ClipImpl *clip = find_ase_obj<ClipImpl> (c);
121 if (clip)
122 return shared_ptr_cast<ClipImpl> (clip);
123 ClipImplP clipp = ClipImpl::make_shared (c);
124 return clipp;
125}
126
127ClipImpl::~ClipImpl()
128{
129 unregister_ase_obj (this, clip_.get());
130 state_listener_ = nullptr;
131}
132
134ClipImpl::clip_index () const
135{
136 if (auto c = clip_.get())
137 if (auto timpl = find_ase_obj<TrackImpl> (c->getTrack()))
138 return timpl->clip_index (*this);
139 return -1;
140}
141
142String
143ClipImpl::name () const
144{
145 return clip_.get() ? clip_->getName().toStdString() : "";
146}
147
148void
149ClipImpl::name (const std::string &n)
150{
151 return_unless (clip_.get());
152 clip_->edit.getUndoManager().beginNewTransaction ("Set Clip Name");
153 clip_->setName (juce::String (n));
154}
155
156ClipPosition
157ClipImpl::position () const
158{
159 return_unless (clip_.get(), {});
160 const auto p = clip_->getPosition();
161 return { { p.time.getStart().inSeconds(), p.time.getEnd().inSeconds() }, p.offset.inSeconds() };
162}
163
164void
165ClipImpl::position (const ClipPosition &position)
166{
167 assert_return (clip_.get());
168 clip_->edit.getUndoManager().beginNewTransaction ("Set Clip Position");
169 clip_->setPosition ({ { tracktion::TimePosition::fromSeconds (position.time.start),
170 tracktion::TimePosition::fromSeconds (position.time.end) },
171 tracktion::TimeDuration::fromSeconds (position.offset) });
172}
173
174ClipNoteS
176{
177 return all_notes();
178}
179
180void
181ClipImpl::all_notes (const ClipNoteS &notes)
182{
183 return_unless (dynamic_cast<te::MidiClip*> (clip_.get()));
184 ClipNoteS batch = all_notes();
185 // Mark all for deletion.
186 for (auto &note : batch)
187 note.length_beats = 0;
188 for (auto note : notes)
189 {
190 note.id = 0;
191 batch.push_back (note);
192 }
193 change_batch (batch, "Set All Notes");
194}
195
196ClipNoteS
197ClipImpl::all_notes () const
198{
199 ClipNoteS notes;
200 auto mclip = dynamic_cast<te::MidiClip*> (clip_.get());
201 return_unless (mclip, notes);
202 state_listener_->prune_notes();
203 for (auto note : mclip->getSequence().getNotes())
204 notes.push_back (state_listener_->describe_note (*note));
205 return notes;
206}
207
208double
210{
211 return clip_.get() ? clip_->getStartBeat().inBeats() : 0;
212}
213
214void
216{
217 warning ("Clip.start_beat is read-only; assign Clip.position");
218}
219
220double
222{
223 return clip_.get() ? clip_->getEndBeat().inBeats() : 0;
224}
225
226void
228{
229 warning ("Clip.end_beat is read-only; assign Clip.position");
230}
231
232bool
234{
235 return_unless (clip_.get(), false);
236 return clip_->isMuted();
237}
238
239void
241{
242 return_unless (clip_.get());
243 auto &um = clip_->edit.getUndoManager();
244 um.beginNewTransaction ("Set Clip Muted");
245 clip_->setMuted (muted);
246}
247
248float
250{
251 return_unless (clip_.get(), 0.0);
252 auto mclip = dynamic_cast<te::MidiClip*> (clip_.get());
253 if (mclip)
254 return mclip->getVolumeDb();
255 auto aclip = dynamic_cast<te::AudioClipBase*> (clip_.get());
256 if (aclip)
257 return aclip->getGainDB();
258 return 0.0;
259}
260
261void
263{
264 return_unless (clip_.get());
265 auto &um = clip_->edit.getUndoManager();
266 um.beginNewTransaction ("Set Clip Volume");
267 auto mclip = dynamic_cast<te::MidiClip*> (clip_.get());
268 if (mclip)
269 mclip->setVolumeDb (db);
270 else
271 {
272 auto aclip = dynamic_cast<te::AudioClipBase*> (clip_.get());
273 if (aclip)
274 aclip->setGainDB (db);
275 }
276}
277
278float
280{
281 return_unless (clip_.get(), 0.0);
282 auto aclip = dynamic_cast<te::AudioClipBase*> (clip_.get());
283 if (aclip)
284 return aclip->getPan();
285 return 0.0;
286}
287
288void
289ClipImpl::pan (float panval)
290{
291 auto aclip = dynamic_cast<te::AudioClipBase*> (clip_.get());
292 return_unless (aclip); // MIDI clips have no pan
293 clip_->edit.getUndoManager().beginNewTransaction ("Set Clip Pan");
294 aclip->setPan (panval);
295}
296
299{
300 StringS props = { "end_beat", "start_beat" };
301 if (!dynamic_cast<te::AudioClipBase*> (clip_.get()))
302 props.push_back ("pan");
303 if (!dynamic_cast<te::MidiClip*> (clip_.get()))
304 props.push_back ("all_notes");
305 return props;
306}
307
308TelemetryFieldS
310{
311 TelemetryFieldS v;
312 return v;
313}
314
315void
316ClipImpl::update_telemetry ()
317{
318}
319
320void
322{
323 auto *clip = clip_.get();
324 if (clip) {
325 // Remove from te::Track's clip collection
326 clip->removeFromParent();
327 // Clear references
329 state_listener_ = nullptr;
330 }
332}
333
334ClipNoteS
335ClipImpl::change_batch (const ClipNoteS &batch, const String &undogroup)
336{
337 return_unless (clip_.get(), {});
338 auto mclip = dynamic_cast<te::MidiClip*> (clip_.get());
339 assert_return (mclip, {});
340 auto &sequence = mclip->getSequence();
341 auto &um = clip_->edit.getUndoManager();
342 // Group deletions, changes and insertions into one undo step.
343 um.beginNewTransaction (juce::String (undogroup.empty() ? "Change Notes" : undogroup));
344 ClipNoteS changed (batch.size());
345 state_listener_->prune_notes();
346 // Delete existing notes before modifications and insertions.
347 for (size_t i = 0; i < batch.size(); i++)
348 {
349 const auto &note = batch[i];
350 if (note.id > 0 && note.length_beats == 0)
351 if (auto n = state_listener_->find_note (sequence, note.id))
352 {
353 changed[i] = note;
354 sequence.removeNote (*n, &um);
355 }
356 }
357 // Modify existing notes.
358 for (size_t i = 0; i < batch.size(); i++)
359 {
360 const auto &note = batch[i];
361 if (note.id > 0 && note.length_beats != 0)
362 if (auto n = state_listener_->find_note (sequence, note.id))
363 {
364 n->setStartAndLength (tracktion::BeatPosition::fromBeats (note.start_beat),
365 tracktion::BeatDuration::fromBeats (note.length_beats), &um);
366 n->setNoteNumber (note.note_number, &um);
367 n->setVelocity (note.velocity, &um);
368 n->setColour (note.colour, &um);
369 n->setMute (note.mute, &um);
370 changed[i] = state_listener_->describe_note (*n);
371 }
372 }
373 // Insert new notes.
374 for (size_t i = 0; i < batch.size(); i++)
375 {
376 const auto &note = batch[i];
377 if (note.id <= 0 && note.length_beats != 0)
378 if (auto n = sequence.addNote (note.note_number, tracktion::BeatPosition::fromBeats (note.start_beat),
379 tracktion::BeatDuration::fromBeats (note.length_beats), note.velocity, note.colour, &um))
380 {
381 n->setMute (note.mute, &um);
382 changed[i] = state_listener_->describe_note (*n);
383 }
384 }
385 emit_notify ("notes");
386 emit_notify ("all_notes");
387 return changed;
388}
389
390} // Ase
ClipNoteS list_all_notes() override
List all notes of this Clip, changes on notify:notes.
Definition clip.cc:175
float pan() const override
Get audio clip pan (-1.0 to 1.0), 0 for other clips.
Definition clip.cc:279
StringS read_only_properties() const override
List x()/x (v) properties that this object can't change, their setters ignore the value or warn.
Definition clip.cc:298
bool muted() const override
Check if clip is muted.
Definition clip.cc:233
TelemetryFieldS telemetry() const override
Retrieve clip telemetry locations.
Definition clip.cc:309
ClipNoteS change_batch(const ClipNoteS &notes, const String &undogroup) override
Insert id <= 0, delete length_beats == 0; emits notify:notes and notify:all_notes.
Definition clip.cc:335
double end_beat() const override
Clip end in Tracktion beats, changes on notify:end_beat.
Definition clip.cc:221
float volume() const override
Get clip volume in dB.
Definition clip.cc:249
double start_beat() const override
Clip start in Tracktion beats, changes on notify:start_beat.
Definition clip.cc:209
void remove_self() override
Remove self from parent container.
Definition clip.cc:321
void emit_notify(const String &detail) override
Emit notify:detail, multiple notifications maybe coalesced if a CoalesceNotifies instance exists.
Definition object.cc:164
void remove_self() override
Remove self from parent container.
Definition gadget.cc:86
Mimick tracktion::engine::SafeSelectable<> for tracktion::Selectable descendants.
Definition trkn-utils.hh:35
bool hasType(const Identifier &typeName) const noexcept
bool isAChildOf(const ValueTree &possibleParent) const noexcept
void addListener(Listener *listener)
void removeListener(Listener *listener)
float getPan() const noexcept
float getGainDB() const noexcept
T empty(T... args)
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
Definition internal.hh:28
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:72
The Anklang C++ API namespace.
Definition api.hh:8
int32_t int32
A 32-bit signed integer.
Definition cxxaux.hh:27
void register_ase_obj(VirtualBase *ase_impl, tracktion::Selectable &selectable)
Helper: register AseImpl with a tracktion Selectable via ase_obj_.
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:34
void unregister_ase_obj(VirtualBase *ase_impl, tracktion::Selectable *selectable)
Helper: unregister AseImpl from a tracktion Selectable (selectable may be nullptr)
T push_back(T... args)
A Tracktion MIDI note with a session handle for edits.
Definition api.hh:229
typedef ssize_t