Anklang-0.3.0.dev886+g785567a1 anklang-0.3.0.dev886+g785567a1
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 "serialize.hh"
8#include "internal.hh"
9
10namespace te = tracktion::engine;
11
12namespace Ase {
13
14// == Clip ==
15Clip::Clip ()
16{}
17
18// == ClipNote ==
19bool
21{
22 return (tick == o.tick &&
23 id == o.id &&
24 channel == o.channel &&
25 key == o.key &&
26 selected == o.selected &&
27 duration == o.duration &&
28 velocity == o.velocity &&
29 fine_tune == o.fine_tune);
30}
31
32// == ClipStateListener ==
34 ClipImpl &aseclip_;
35 juce::ValueTree clip_state_;
36public:
37 ClipStateListener (ClipImpl &aseclip) :
38 aseclip_ (aseclip), clip_state_ (aseclip_.clip_->state)
39 {
40 clip_state_.addListener (this);
41 }
42 ~ClipStateListener() override
43 {
44 clip_state_.removeListener (this);
45 }
46 void
47 valueTreePropertyChanged (juce::ValueTree &tree, const juce::Identifier &property) override
48 {
49 assert_return (tree == clip_state_);
50 if (property == tracktion::engine::IDs::name)
51 aseclip_.emit_notify ("name");
52 else if (property == tracktion::engine::IDs::start ||
53 property == tracktion::engine::IDs::length ||
54 property == tracktion::engine::IDs::offset)
55 {
56 aseclip_.emit_notify ("start_tick");
57 aseclip_.emit_notify ("stop_tick");
58 aseclip_.emit_notify ("end_tick");
59 }
60 else if (property == tracktion::engine::IDs::mute)
61 aseclip_.emit_notify ("muted");
62 else if (property == tracktion::engine::IDs::volDb || property == tracktion::engine::IDs::gain)
63 aseclip_.emit_notify ("volume");
64 else if (property == tracktion::engine::IDs::pan)
65 aseclip_.emit_notify ("pan");
66 else
67 aseclip_.emit_notify ("notes"); // Simplistic change detection for notes within state
68 }
69 void valueTreeChildAdded (juce::ValueTree&, juce::ValueTree&) override {}
70 void valueTreeChildRemoved (juce::ValueTree&, juce::ValueTree&, int) override {}
71 void valueTreeParentChanged (juce::ValueTree&) override {}
72 void valueTreeChildOrderChanged (juce::ValueTree&, int, int) override {}
73};
74
75
76// == ClipImpl ==
77ClipImpl::ClipImpl (tracktion::Clip &clip) :
78 clip_ (&clip)
79{
80 register_ase_obj (this, clip);
81 state_listener_ = std::make_unique<ClipStateListener> (*this);
82}
83
84ClipImplP
85ClipImpl::from_trkn (tracktion::Clip &c)
86{
87 ClipImpl *clip = find_ase_obj<ClipImpl> (c);
88 if (clip)
89 return shared_ptr_cast<ClipImpl> (clip);
90 ClipImplP clipp = ClipImpl::make_shared (c);
91 return clipp;
92}
93
94ClipImpl::~ClipImpl()
95{
96 unregister_ase_obj (this, clip_.get());
97 state_listener_ = nullptr;
98}
99
100ProjectImpl*
101ClipImpl::project () const
102{
103 if (auto c = clip_.get())
104 if (auto timpl = find_ase_obj<TrackImpl> (c->getTrack()))
105 return timpl->project();
106 return nullptr;
107}
108
109bool
110ClipImpl::needs_serialize() const
111{
112 return false;
113}
114
115void
120
122ClipImpl::clip_index () const
123{
124 if (auto c = clip_.get())
125 if (auto timpl = find_ase_obj<TrackImpl> (c->getTrack()))
126 return timpl->clip_index (*this);
127 return -1;
128}
129
130void
131ClipImpl::assign_range (int64 starttick, int64 stoptick)
132{
133 assert_return (clip_.get());
134 auto &ts = clip_->edit.tempoSequence;
135 double start_beats = double (starttick) / TRANSPORT_PPQN;
136 double end_beats = double (stoptick) / TRANSPORT_PPQN;
137 double duration_beats = end_beats - start_beats;
138
139 if (duration_beats > 0)
140 {
141 auto start_time = ts.toTime (tracktion::BeatPosition::fromBeats (start_beats));
142 auto duration_time = ts.toTime (tracktion::BeatPosition::fromBeats (duration_beats));
143 // Note: duration in time might depend on tempo changes *during* the clip if we map strictly.
144 // But setLength expects TimeDuration.
145 // If we want to preserve BEAT duration, we might need setLength(BeatDuration)? No, setLength takes TimeDuration.
146 // But MidiClip is usually beat based.
147 // However, for assigning range on timeline, we convert beats to time.
148
149 clip_->setStart (start_time, false, true);
150 clip_->setLength (tracktion::TimeDuration::fromSeconds(duration_time.inSeconds()), true);
151 // Simplistic conversion. Proper way: find time of end beat - time of start beat.
152 auto end_time = ts.toTime (tracktion::BeatPosition::fromBeats (end_beats));
153 clip_->setLength (end_time - start_time, true);
154 }
155}
156
157ClipNoteS
159{
160 return all_notes();
161}
162
163void
164ClipImpl::all_notes (const ClipNoteS &notes)
165{
166 ClipNoteS current = all_notes();
167 // Mark all for deletion
168 for (auto &n : current) n.duration = 0;
169
170 ClipNoteS batch = current;
171 batch.insert (batch.end(), notes.begin(), notes.end());
172 change_batch (batch, "Set All Notes");
173}
174
175ClipNoteS
176ClipImpl::all_notes () const
177{
178 ClipNoteS notes;
179 if (!clip_.get()) return notes;
180 auto mclip = dynamic_cast<te::MidiClip*> (clip_.get());
181 if (!mclip) return notes;
182
183 // Assuming single channel clip
184 int channel = mclip->getMidiChannel().getChannelNumber() - 1;
185
186 for (auto n : mclip->getSequence().getNotes())
187 {
188 ClipNote cn;
189 cn.id = 1;
190 cn.channel = channel;
191 cn.key = n->getNoteNumber();
192 cn.velocity = n->getVelocity() / 127.0f;
193 cn.tick = n->getStartBeat().inBeats() * TRANSPORT_PPQN;
194 cn.duration = n->getLengthBeats().inBeats() * TRANSPORT_PPQN;
195 cn.selected = false;
196 cn.fine_tune = 0;
197 notes.push_back (cn);
198 }
199 return notes;
200}
201
202int64
203ClipImpl::end_tick () const
204{
205 if (!clip_.get()) return 0;
206 return stop_tick();
207}
208
209void
210ClipImpl::end_tick (int64 etick)
211{
212 if (!clip_.get()) return;
213 assign_range (start_tick(), etick);
214}
215
216int64
218{
219 if (!clip_.get()) return 0;
220 auto &ts = clip_->edit.tempoSequence;
221 return ts.toBeats (clip_->getPosition().getStart()).inBeats() * TRANSPORT_PPQN;
222}
223
224int64
226{
227 if (!clip_.get()) return 0;
228 auto &ts = clip_->edit.tempoSequence;
229 return ts.toBeats (clip_->getPosition().getEnd()).inBeats() * TRANSPORT_PPQN;
230}
231
232bool
234{
235 if (!clip_.get()) return false;
236 return clip_->isMuted();
237}
238
239void
241{
242 if (!clip_.get()) return;
243 auto &um = clip_->edit.getUndoManager();
244 um.beginNewTransaction ("Set Clip Muted");
245 clip_->setMuted (muted);
246}
247
248double
250{
251 if (!clip_.get()) return 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 if (!clip_.get()) return;
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 (float (db));
270 else
271 {
272 auto aclip = dynamic_cast<te::AudioClipBase*> (clip_.get());
273 if (aclip)
274 aclip->setGainDB (float (db));
275 }
276}
277
278double
280{
281 if (!clip_.get()) return 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 (double panval)
290{
291 if (!clip_.get()) return;
292 auto &um = clip_->edit.getUndoManager();
293 um.beginNewTransaction ("Set Clip Pan");
294 auto aclip = dynamic_cast<te::AudioClipBase*> (clip_.get());
295 if (aclip)
296 aclip->setPan (float (panval));
297}
298
299TelemetryFieldS
301{
302 TelemetryFieldS v;
303 return v;
304}
305
306void
307ClipImpl::update_telemetry ()
308{
309}
310
311void
313{
314 auto *clip = clip_.get();
315 if (clip) {
316 // Remove from te::Track's clip collection
317 clip->removeFromParent();
318 // Clear references
320 state_listener_ = nullptr;
321 }
323}
324
328{
329 ClipNoteS notes = all_notes();
331}
332
333int32
334ClipImpl::change_batch (const ClipNoteS &batch, const String &undogroup)
335{
336 if (!clip_.get()) return -1;
337 auto mclip = dynamic_cast<te::MidiClip*> (clip_.get());
338 assert_return (mclip, -1);
339
340 auto &um = clip_->edit.getUndoManager();
341 um.beginNewTransaction (juce::String (undogroup.empty() ? "Change Notes" : undogroup));
342
343 for (const auto &note : batch)
344 {
345 if (note.duration == 0) // Delete
346 {
347 auto &seq = mclip->getSequence();
348 for (auto n : seq.getNotes())
349 {
350 if (std::abs(n->getStartBeat().inBeats() * TRANSPORT_PPQN - note.tick) < 1 &&
351 n->getNoteNumber() == note.key)
352 {
353 seq.removeNote (*n, &um);
354 break;
355 }
356 }
357 }
358 else if (note.id <= 0) // Insert
359 {
360 mclip->getSequence().addNote (note.key,
361 tracktion::BeatPosition::fromBeats (double (note.tick) / TRANSPORT_PPQN),
362 tracktion::BeatDuration::fromBeats (double (note.duration) / TRANSPORT_PPQN),
363 note.velocity * 127,
364 note.channel,
365 &um);
366 }
367 else // Modify
368 {
369 auto &seq = mclip->getSequence();
370 for (auto n : seq.getNotes())
371 {
372 if (std::abs(n->getStartBeat().inBeats() * TRANSPORT_PPQN - note.tick) < 1 &&
373 n->getNoteNumber() == note.key)
374 {
375 n->setStartAndLength (tracktion::BeatPosition::fromBeats (double (note.tick) / TRANSPORT_PPQN),
376 tracktion::BeatDuration::fromBeats (double (note.duration) / TRANSPORT_PPQN),
377 &um);
378 n->setVelocity (note.velocity * 127, &um);
379 n->setNoteNumber (note.key, &um);
380 break;
381 }
382 }
383 }
384 }
385 emit_notify ("notes");
386 emit_notify ("all_notes");
387 return 0;
388}
389
390// == ClipImpl::Generator ==
392void
394{
395 ProjectImpl *p = clip.project();
396 int64_t bar_ticks = p ? p->bar_ticks() : 0;
397 events_ = clip.tick_events();
398 muted_ = false;
399 start_offset_ = 0;
400 loop_start_ = 0;
401 loop_end_ = bar_ticks * 2;
402 const int LOOPS = 2;
403 last_ = loop_end_ - start_offset_ + LOOPS * (loop_end_ - loop_start_);
404 if (true) // keep looping
405 last_ = M52MAX;
406}
407
409void
411{
412 // negative ticks indicate delay
413 if (target_tick < 0)
414 {
415 xtick_ = target_tick;
416 itick_ = xtick_;
417 return;
418 }
419 // external position
420 xtick_ = std::min (target_tick, play_length());
421 // advance internal position by externally observable ticks
422 itick_ = start_offset_;
423 return_unless (xtick_ > 0);
424 // beyond loop end
425 if (itick_ >= loop_end_)
426 {
427 itick_ = xtick_;
428 return;
429 }
430 // until loop end
431 int64 delta = xtick_;
432 const int64 frag = std::min (delta, loop_end_ - itick_);
433 delta -= frag;
434 itick_ += frag;
435 if (itick_ == loop_end_)
436 {
437 itick_ = loop_start_;
438 // within loop (loop count is discarded)
439 if (delta)
440 {
441 const int64 frac = delta % (loop_end_ - loop_start_);
442 itick_ += frac;
443 }
444 }
445}
446
448int64
449ClipImpl::Generator::generate (int64 target_tick, const Receiver &receiver)
450{
451 if (0)
452 printerr ("generate: %d < %d (%+d) && %d > %d (%+d) (loop: %d %d) i=%d\n", xtick_, last_, xtick_ < last_,
453 target_tick, xtick_, target_tick > xtick_,
454 loop_start_, loop_end_, itick_);
455 const int64 old_xtick = xtick_;
456 return_unless (xtick_ < last_ && target_tick > xtick_, xtick_ - old_xtick);
457 int64 ticks = std::min (target_tick, last_) - xtick_;
458 // consume delay
459 if (xtick_ < 0)
460 {
461 const int64 delta = std::min (ticks, -xtick_);
462 ticks -= delta;
463 xtick_ += delta;
464 itick_ += delta;
465 if (itick_ == 0)
466 itick_ = start_offset_;
467 }
468 // here: ticks == 0 || xtick_ >= 0
469 while (ticks > 0)
470 {
471 // advance
472 const int64 delta = itick_ < loop_end_ ? std::min (ticks, loop_end_ - itick_) : ticks;
473 ticks -= delta;
474 const int64 x = xtick_;
475 xtick_ += delta;
476 const int64 a = itick_;
477 itick_ += delta;
478 const int64 b = itick_;
479 if (itick_ == loop_end_)
480 itick_ = loop_start_;
481 // generate notes within [a,b)
482 if (receiver && !muted_)
483 {
484 ClipNote index = { .tick = a };
485 const ClipNote *event = events_->lookup_after (index);
486 while (event && event->tick < b)
487 {
488 MidiEvent midievent = make_note_on (event->channel, event->key, event->velocity, event->fine_tune, event->id);
489 const int64 noteon_tick = x + event->tick - a;
490 receiver (noteon_tick, midievent);
491 midievent.type = MidiEvent::NOTE_OFF;
492 receiver (noteon_tick + event->duration, midievent);
493 event++;
494 if (event == &*events_->end())
495 break;
496 }
497 }
498 }
499 return xtick_ - old_xtick;
500}
501
502String
503stringify_clip_note (const ClipNote &n)
504{
505 return string_format ("{%d,%d,%d,%s,%d,%d,%f,%f}",
506 n.id, n.channel, n.key,
507 n.selected ? "true" : "false",
508 n.tick, n.duration, n.velocity, n.fine_tune);
509}
510
511} // Ase
void setup(const ClipImpl &clip)
Create generator from clip.
Definition clip.cc:393
int64 generate(int64 target_tick, const Receiver &receiver)
Advance tick and call receiver for generated events.
Definition clip.cc:449
void jumpto(int64 target_tick)
Assign new play_position() (and clip_position()), preserves all other state.
Definition clip.cc:410
void set_muted(bool muted) override
Set clip muted state, emits notify:muted.
Definition clip.cc:240
void assign_range(int64 starttick, int64 stoptick) override
Change start_tick() and stop_tick(); emits notify:start_tick, notify:stop_tick.
Definition clip.cc:131
int64 start_tick() const override
Get the first tick intended for playback (this is >= 0), changes on notify:start_tick.
Definition clip.cc:217
ClipNoteS list_all_notes() override
List all notes of this Clip; changes on notify:notes.
Definition clip.cc:158
int32 change_batch(const ClipNoteS &notes, const String &undogroup) override
Change note id according to the arguments or add a new note if id < 0; emits notify:notes.
Definition clip.cc:334
double volume() const override
Get clip volume in dB.
Definition clip.cc:249
bool is_muted() const override
Check if clip is muted.
Definition clip.cc:233
int64 stop_tick() const override
Get the tick to stop playback, not events should be played after this, changes on notify:stop_tick.
Definition clip.cc:225
void serialize(WritNode &xs) override
Serialize members and childern.
Definition clip.cc:116
TelemetryFieldS telemetry() const override
Retrieve clip telemetry locations.
Definition clip.cc:300
double pan() const override
Get clip pan (-1.0 to 1.0).
Definition clip.cc:279
OrderedEventsP tick_events() const
Retrieve const vector with all notes ordered by tick.
Definition clip.cc:327
void remove_self() override
Remove self from parent container.
Definition clip.cc:312
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:167
void serialize(WritNode &xs) override
Serialize members and childern.
Definition gadget.cc:71
Mimick tracktion::engine::SafeSelectable<> for tracktion::Selectable descendants.
Definition trkn-utils.hh:27
One entry in a Writ serialization document.
Definition serialize.hh:24
void addListener(Listener *listener)
void removeListener(Listener *listener)
float getPan() const noexcept
float getGainDB() const noexcept
BeatPosition getStartBeat() const
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:29
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:73
typedef double
T min(T... args)
The Anklang C++ API namespace.
Definition api.hh:8
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
int32_t int32
A 32-bit signed integer.
Definition cxxaux.hh:28
int64_t int64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:29
void register_ase_obj(VirtualBase *ase_impl, tracktion::Selectable &selectable)
Helper: register AseImpl with a tracktion Selectable via ase_obj_.
Definition trkn-utils.cc:62
constexpr const int64 TRANSPORT_PPQN
Maximum number of sample frames to calculate in Processor::render().
Definition transport.hh:53
void unregister_ase_obj(VirtualBase *ase_impl, tracktion::Selectable *selectable)
Helper: unregister AseImpl from a tracktion Selectable (selectable may be nullptr)
Definition trkn-utils.cc:70
typedef int64_t
Part specific note event representation.
Definition api.hh:232
int64 tick
UI selection flag.
Definition api.hh:237
bool selected
Musical note as MIDI key, 0 .. 127.
Definition api.hh:236
float velocity
Duration in number of ticks.
Definition api.hh:239
bool operator==(const ClipNote &) const
Fine Tune, -100 .. +100.
Definition clip.cc:20
int64 duration
Position in ticks.
Definition api.hh:238
int8 channel
ID, > 0.
Definition api.hh:234
int8 key
MIDI Channel.
Definition api.hh:235
float fine_tune
Velocity, 0 .. +1.
Definition api.hh:240
MidiEvent data structure.
Definition midievent.hh:50
MidiEventType type
MidiEvent type, one of the MidiEventType members.
Definition midievent.hh:55
typedef ssize_t