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