Anklang-0.3.0.dev797+g4e3241f3 anklang-0.3.0.dev797+g4e3241f3
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 state_listener_ = std::make_unique<ClipStateListener> (*this);
81}
82
83ClipImplP
84ClipImpl::from_trkn (tracktion::Clip &c)
85{
86 ClipImpl *clip = SelectableHandle::find_selectable_handle<ClipImpl> (c);
87 if (clip)
88 return shared_ptr_cast<ClipImpl> (clip);
89 ClipImplP clipp = ClipImpl::make_shared (c);
90 return clipp;
91}
92
93ClipImpl::~ClipImpl()
94{
95 state_listener_ = nullptr;
96}
97
98ProjectImpl*
99ClipImpl::project () const
100{
101 if (auto c = clip_.get())
102 if (auto t = c->getTrack())
103 if (auto timpl = SelectableHandle::find_selectable_handle<TrackImpl> (*t))
104 return timpl->project();
105 return nullptr;
106}
107
108bool
109ClipImpl::needs_serialize() const
110{
111 return false;
112}
113
114void
119
121ClipImpl::clip_index () const
122{
123 if (auto c = clip_.get())
124 if (auto t = c->getTrack())
125 if (auto timpl = SelectableHandle::find_selectable_handle<TrackImpl> (*t))
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
312ClipImpl::OrderedEventsP
314{
315 ClipNoteS notes = all_notes();
317}
318
319int32
320ClipImpl::change_batch (const ClipNoteS &batch, const String &undogroup)
321{
322 if (!clip_.get()) return -1;
323 auto mclip = dynamic_cast<te::MidiClip*> (clip_.get());
324 assert_return (mclip, -1);
325
326 auto &um = clip_->edit.getUndoManager();
327 um.beginNewTransaction (juce::String (undogroup.empty() ? "Change Notes" : undogroup));
328
329 for (const auto &note : batch)
330 {
331 if (note.duration == 0) // Delete
332 {
333 auto &seq = mclip->getSequence();
334 for (auto n : seq.getNotes())
335 {
336 if (std::abs(n->getStartBeat().inBeats() * TRANSPORT_PPQN - note.tick) < 1 &&
337 n->getNoteNumber() == note.key)
338 {
339 seq.removeNote (*n, &um);
340 break;
341 }
342 }
343 }
344 else if (note.id <= 0) // Insert
345 {
346 mclip->getSequence().addNote (note.key,
347 tracktion::BeatPosition::fromBeats (double (note.tick) / TRANSPORT_PPQN),
348 tracktion::BeatDuration::fromBeats (double (note.duration) / TRANSPORT_PPQN),
349 note.velocity * 127,
350 note.channel,
351 &um);
352 }
353 else // Modify
354 {
355 auto &seq = mclip->getSequence();
356 for (auto n : seq.getNotes())
357 {
358 if (std::abs(n->getStartBeat().inBeats() * TRANSPORT_PPQN - note.tick) < 1 &&
359 n->getNoteNumber() == note.key)
360 {
361 n->setStartAndLength (tracktion::BeatPosition::fromBeats (double (note.tick) / TRANSPORT_PPQN),
362 tracktion::BeatDuration::fromBeats (double (note.duration) / TRANSPORT_PPQN),
363 &um);
364 n->setVelocity (note.velocity * 127, &um);
365 n->setNoteNumber (note.key, &um);
366 break;
367 }
368 }
369 }
370 }
371 emit_notify ("notes");
372 emit_notify ("all_notes");
373 return 0;
374}
375
376// == ClipImpl::Generator ==
378void
380{
381 ProjectImpl *p = clip.project();
382 int64_t bar_ticks = p ? p->bar_ticks() : 0;
383 events_ = clip.tick_events();
384 muted_ = false;
385 start_offset_ = 0;
386 loop_start_ = 0;
387 loop_end_ = bar_ticks * 2;
388 const int LOOPS = 2;
389 last_ = loop_end_ - start_offset_ + LOOPS * (loop_end_ - loop_start_);
390 if (true) // keep looping
391 last_ = M52MAX;
392}
393
395void
397{
398 // negative ticks indicate delay
399 if (target_tick < 0)
400 {
401 xtick_ = target_tick;
402 itick_ = xtick_;
403 return;
404 }
405 // external position
406 xtick_ = std::min (target_tick, play_length());
407 // advance internal position by externally observable ticks
408 itick_ = start_offset_;
409 return_unless (xtick_ > 0);
410 // beyond loop end
411 if (itick_ >= loop_end_)
412 {
413 itick_ = xtick_;
414 return;
415 }
416 // until loop end
417 int64 delta = xtick_;
418 const int64 frag = std::min (delta, loop_end_ - itick_);
419 delta -= frag;
420 itick_ += frag;
421 if (itick_ == loop_end_)
422 {
423 itick_ = loop_start_;
424 // within loop (loop count is discarded)
425 if (delta)
426 {
427 const int64 frac = delta % (loop_end_ - loop_start_);
428 itick_ += frac;
429 }
430 }
431}
432
434int64
435ClipImpl::Generator::generate (int64 target_tick, const Receiver &receiver)
436{
437 if (0)
438 printerr ("generate: %d < %d (%+d) && %d > %d (%+d) (loop: %d %d) i=%d\n", xtick_, last_, xtick_ < last_,
439 target_tick, xtick_, target_tick > xtick_,
440 loop_start_, loop_end_, itick_);
441 const int64 old_xtick = xtick_;
442 return_unless (xtick_ < last_ && target_tick > xtick_, xtick_ - old_xtick);
443 int64 ticks = std::min (target_tick, last_) - xtick_;
444 // consume delay
445 if (xtick_ < 0)
446 {
447 const int64 delta = std::min (ticks, -xtick_);
448 ticks -= delta;
449 xtick_ += delta;
450 itick_ += delta;
451 if (itick_ == 0)
452 itick_ = start_offset_;
453 }
454 // here: ticks == 0 || xtick_ >= 0
455 while (ticks > 0)
456 {
457 // advance
458 const int64 delta = itick_ < loop_end_ ? std::min (ticks, loop_end_ - itick_) : ticks;
459 ticks -= delta;
460 const int64 x = xtick_;
461 xtick_ += delta;
462 const int64 a = itick_;
463 itick_ += delta;
464 const int64 b = itick_;
465 if (itick_ == loop_end_)
466 itick_ = loop_start_;
467 // generate notes within [a,b)
468 if (receiver && !muted_)
469 {
470 ClipNote index = { .tick = a };
471 const ClipNote *event = events_->lookup_after (index);
472 while (event && event->tick < b)
473 {
474 MidiEvent midievent = make_note_on (event->channel, event->key, event->velocity, event->fine_tune, event->id);
475 const int64 noteon_tick = x + event->tick - a;
476 receiver (noteon_tick, midievent);
477 midievent.type = MidiEvent::NOTE_OFF;
478 receiver (noteon_tick + event->duration, midievent);
479 event++;
480 if (event == &*events_->end())
481 break;
482 }
483 }
484 }
485 return xtick_ - old_xtick;
486}
487
488String
489stringify_clip_note (const ClipNote &n)
490{
491 return string_format ("{%d,%d,%d,%s,%d,%d,%f,%f}",
492 n.id, n.channel, n.key,
493 n.selected ? "true" : "false",
494 n.tick, n.duration, n.velocity, n.fine_tune);
495}
496
497} // Ase
void setup(const ClipImpl &clip)
Create generator from clip.
Definition clip.cc:379
int64 generate(int64 target_tick, const Receiver &receiver)
Advance tick and call receiver for generated events.
Definition clip.cc:435
void jumpto(int64 target_tick)
Assign new play_position() (and clip_position()), preserves all other state.
Definition clip.cc:396
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:320
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:115
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:313
void emit_notify(const String &detail) override
Emit notify:detail, multiple notifications maybe coalesced if a CoalesceNotifies instance exists.
Definition object.cc:164
void serialize(WritNode &xs) override
Serialize members and childern.
Definition gadget.cc:71
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:9
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
constexpr const int64 TRANSPORT_PPQN
Maximum number of sample frames to calculate in Processor::render().
Definition transport.hh:53
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