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

« « « Anklang Documentation
Loading...
Searching...
No Matches
prjtests.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 <ase/project.hh>
3#include <ase/clip.hh>
4#include <ase/track.hh>
5#include <ase/testing.hh>
6
7namespace { // Anon
8using namespace Ase;
9
10static void
11project_creation()
12{
13 ProjectImplP project = ProjectImpl::create ("TestProject");
14 TASSERT (project);
15 TASSERT (project->name() == "TestProject");
16
17 const double initial_bpm = project->bpm();
18 TASSERT (initial_bpm >= 10.0 && initial_bpm <= 999.0);
19 uint64_t bpm_notifications = 0;
20 auto bpm_connection = project->on_event ("notify:bpm", [&bpm_notifications] (const Event &event) { bpm_notifications++; });
21
22 uint64_t name_notifications = 0;
23 auto name_connection = project->on_event ("notify:name", [&name_notifications] (const Event &event) { name_notifications++; });
24
25 // Test BPM notify/undo/redo
26 project->bpm (130.0);
27 TASSERT (std::abs (project->bpm() - 130.0) < 0.001);
28 uint64_t last_bpm_notifications = bpm_notifications;
29 project->bpm (123.5);
30 TASSERT (std::abs (project->bpm() - 123.5) < 0.001);
31 TASSERT (bpm_notifications > last_bpm_notifications);
32
33 // Test undo
34 TASSERT (project->can_undo());
35 TASSERT (!project->can_redo());
36 last_bpm_notifications = bpm_notifications;
37 project->undo();
38 TASSERT (!project->can_undo());
39 TASSERT (project->can_redo());
40 TASSERT (std::abs (project->bpm() - initial_bpm) < 0.001);
41 TASSERT (bpm_notifications > last_bpm_notifications);
42 // Test redo
43 last_bpm_notifications = bpm_notifications;
44 project->redo();
45 TASSERT (project->can_undo());
46 TASSERT (!project->can_redo());
47 TASSERT (std::abs (project->bpm() - 123.5) < 0.001);
48 TASSERT (bpm_notifications > last_bpm_notifications);
49
50 project->undo();
51 TASSERT (!project->can_undo());
52 TASSERT (project->can_redo());
53 TASSERT (std::abs (project->bpm() - initial_bpm) < 0.001);
54
55 // Test name notify/undo/redo
56 const String initial_name = project->name();
57 TASSERT (initial_name == "TestProject");
58 uint64_t last_name_notifications = name_notifications;
59 project->name ("NewName");
60 TASSERT (project->name() == "NewName");
61 TASSERT (name_notifications > last_name_notifications);
62
63 TASSERT (project->can_undo());
64 last_name_notifications = name_notifications;
65 project->undo();
66 TASSERT (project->name() == initial_name);
67 TASSERT (project->can_redo());
68 TASSERT (name_notifications > last_name_notifications);
69
70 last_name_notifications = name_notifications;
71 project->redo();
72 TASSERT (project->name() == "NewName");
73 TASSERT (name_notifications > last_name_notifications);
74
75 project->discard();
76
77 // Test create second project
78 ProjectImplP project2 = ProjectImpl::create ("TestProject2");
79 project2->name ("foo");
80 TASSERT (project2->name() == "foo");
81 project2->name ("bar");
82 TASSERT (project2->name() == "bar");
83 project2->discard();
84}
85TEST_ADD (project_creation);
86
87static void
88project_tempo_values()
89{
90 ProjectP project = ProjectImpl::create ("TempoTest");
91 TASSERT (project->bpm() == 120.0);
92 TASSERT (project->numerator() == 4);
93 TASSERT (project->denominator() == 4);
94
95 project->bpm (1.0);
96 TASSERT (project->bpm() == 20.0);
97 project->bpm (2000.0);
98 TASSERT (project->bpm() == 300.0);
99
100 for (bool numerator : { true, false })
101 {
102 const auto get = [&] { return numerator ? project->numerator() : project->denominator(); };
103 const auto set = [&] (int value) {
104 if (numerator)
105 project->numerator (value);
106 else
107 project->denominator (value);
108 };
109 uint notifications = 0;
110 auto connection = project->on_event (numerator ? "notify:numerator" : "notify:denominator",
111 [&] (const Event&) { notifications++; });
112 const int initial = get(), value = numerator ? 7 : 8;
113 project->ungroup_undo();
114 set (value);
115 TASSERT (get() == value);
116 TASSERT (notifications > 0);
117 TASSERT (project->can_undo());
118
119 notifications = 0;
120 set (value);
121 TASSERT (notifications == 0);
122 project->undo();
123 TASSERT (get() == initial);
124 TASSERT (notifications > 0);
125 TASSERT (project->can_redo());
126
127 notifications = 0;
128 project->redo();
129 TASSERT (get() == value);
130 TASSERT (notifications > 0);
131 }
132
133 project->discard();
134}
135TEST_ADD (project_tempo_values);
136
137static void
138project_length()
139{
140 ProjectP project = ProjectImpl::create ("LengthTest");
141 TrackP track = project->create_track();
142 TCMP (project->length(), ==, 0);
143 uint notifications = 0;
144 auto connection = project->on_event ("notify:length", [&] (const Event&) { notifications++; });
145 auto expect_length = [&] (double expected) {
146 TCMP (project->length(), ==, expected);
147 TASSERT (notifications > 0);
148 notifications = 0;
149 };
150 auto check_change = [&] (auto change, double expected) {
151 const double before = project->length();
152 project->ungroup_undo();
153 change();
154 expect_length (expected);
155 project->undo();
156 expect_length (before);
157 project->redo();
158 expect_length (expected);
159 };
160
161 project->ungroup_undo();
162 ClipP midi = track->create_midi_clip ("LengthClip", 1, 3);
163 expect_length (4);
164 check_change ([&] { project->bpm (60); }, 8);
165 check_change ([&] { midi->position ({ { 2, 12 }, 0 }); }, 12);
166 check_change ([&] { midi->remove_self(); }, 0);
167 check_change ([&] { track->create_audio_clip ("LastClip", 20, 4); }, 24);
168 check_change ([&] { track->remove_self(); }, 0);
169
170 project->discard();
171}
172TEST_ADD (project_length);
173
174static void
175project_track_management()
176{
177 ProjectImplP project = ProjectImpl::create ("TrackTest");
178 TASSERT (project);
179
180 TrackS tracks = project->all_tracks();
181 const size_t initial_count = tracks.size();
182 TASSERT (initial_count >= 1);
183
184 TrackP new_track = project->create_track();
185 TASSERT (new_track);
186
187 tracks = project->all_tracks();
188 TASSERT (tracks.size() == initial_count + 1);
189
190 uint64_t track_notifications = 0;
191 auto track_connection = project->on_event ("notify:all_tracks", [&track_notifications] (const Event &event) { track_notifications++; });
192 new_track->remove_self();
193 TASSERT (project->all_tracks().size() == initial_count);
194 TASSERT (track_notifications > 0);
195
196 project->discard();
197}
198TEST_ADD (project_track_management);
199
200static void
201track_outlives_project()
202{
203 TrackS tracks;
204 // one project passes even without the fix, freed meters must get reused
205 for (size_t i = 0; i < 40; i++)
206 {
207 auto project = ProjectImpl::create ("TrackLifetimeTest");
208 tracks.push_back (project->create_track());
209 TASSERT (tracks.back());
210 project->discard();
211 }
212 for (const auto &track : tracks)
213 TASSERT (track->name().empty());
214}
215TEST_ADD (track_outlives_project);
216
217static void
218project_playback_state()
219{
220 ProjectImplP project = ProjectImpl::create ("PlaybackTest");
221 TASSERT (project);
222
223 // Initially not playing
224 TASSERT (!project->is_playing());
225
226 project->discard();
227}
228TEST_ADD (project_playback_state);
229
230static void
231track_mute_solo()
232{
233 ProjectImplP project = ProjectImpl::create ("TrackMuteSoloTest");
234 TASSERT (project);
235
236 TrackP track = project->create_track();
237 TASSERT (track);
238
239 // Test initial mute/solo state
240 TASSERT (!track->muted());
241 TASSERT (!track->solo());
242
243 // Test mute notifications
244 uint64_t muted_notifications = 0;
245 auto muted_connection = track->on_event ("notify:muted", [&muted_notifications] (const Event &event) { muted_notifications++; });
246
247 uint64_t solo_notifications = 0;
248 auto solo_connection = track->on_event ("notify:solo", [&solo_notifications] (const Event &event) { solo_notifications++; });
249
250 // Test mute with notification
251 uint64_t last_muted_notifications = muted_notifications;
252 track->muted (true);
253 TASSERT (track->muted());
254 TASSERT (muted_notifications > last_muted_notifications);
255
256 // Reset mute
257 last_muted_notifications = muted_notifications;
258 track->muted (false);
259 TASSERT (!track->muted());
260 TASSERT (muted_notifications > last_muted_notifications);
261
262 // Test solo with notification
263 uint64_t last_solo_notifications = solo_notifications;
264 track->solo (true);
265 TASSERT (track->solo());
266 TASSERT (solo_notifications > last_solo_notifications);
267
268 // Reset solo
269 last_solo_notifications = solo_notifications;
270 track->solo (false);
271 TASSERT (!track->solo());
272 TASSERT (solo_notifications > last_solo_notifications);
273
274 project->discard();
275}
276TEST_ADD (track_mute_solo);
277
278static void
279track_control_classification()
280{
281 ProjectImplP project = ProjectImpl::create ("ControlTrackTest");
282 TASSERT (project);
283
284 // Fresh projects have the default control tracks plus one audio track
285 TrackS tracks = project->all_tracks();
286 bool have_master = false, have_control = false, have_editable = false;
287 for (auto &t : tracks)
288 {
289 have_master |= t->is_master();
290 have_control |= t->is_control_track();
291 have_editable |= !t->is_control_track();
292 }
293 TASSERT (have_master);
294 TASSERT (have_control);
295 TASSERT (have_editable); // default audio track is editable
296
297 // New tracks are editable, not control tracks
298 TrackP new_track = project->create_track();
299 TASSERT (new_track);
300 TASSERT (!new_track->is_control_track());
301 TASSERT (!new_track->is_master());
302
303 project->discard();
304}
305TEST_ADD (track_control_classification);
306
307static void
308track_hidden()
309{
310 ProjectImplP project = ProjectImpl::create ("TrackHiddenTest");
311 TASSERT (project);
312
313 TrackP track = project->create_track();
314 TASSERT (track);
315
316 // Test initial hidden state
317 TASSERT (!track->hidden());
318
319 // Test hidden notifications
320 uint64_t hidden_notifications = 0;
321 auto hidden_connection = track->on_event ("notify:hidden", [&hidden_notifications] (const Event &event) { hidden_notifications++; });
322
323 // Test hide with notification
324 uint64_t last_hidden_notifications = hidden_notifications;
325 project->ungroup_undo();
326 track->hidden (true);
327 TASSERT (track->hidden());
328 TASSERT (hidden_notifications > last_hidden_notifications);
329
330 TASSERT (project->can_undo());
331 last_hidden_notifications = hidden_notifications;
332 project->undo();
333 TASSERT (!track->hidden());
334 TASSERT (hidden_notifications > last_hidden_notifications);
335
336 TASSERT (project->can_redo());
337 last_hidden_notifications = hidden_notifications;
338 project->redo();
339 TASSERT (track->hidden());
340 TASSERT (hidden_notifications > last_hidden_notifications);
341
342 project->discard();
343}
344TEST_ADD (track_hidden);
345
346static void
347check_track_parameter_values (bool master)
348{
349 struct ParameterTest {
350 const char *name;
351 float (Track::*get)() const;
352 void (Track::*set) (float);
353 float value;
354 };
355 const std::array<ParameterTest,3> parameters = {{
356 { "volume_db", &Track::volume_db, &Track::volume_db, -6.0f },
357 { "slider_pos", &Track::slider_pos, &Track::slider_pos, 0.25f },
358 { "pan", &Track::pan, &Track::pan, 0.5f },
359 }};
360 for (size_t property = 0; property < parameters.size(); property++) {
361 ProjectP project = ProjectImpl::create ("TrackParameterTest");
362 TASSERT (project);
363 TrackP track = master ? project->master_track() : project->create_track();
364 TrackP other_track = master ? project->create_track() : project->master_track();
365 TASSERT (track);
366 TASSERT (other_track);
367 using Values = std::array<float,3>;
368 auto read_values = [&parameters] (const TrackP &t) {
369 Values values;
370 for (size_t i = 0; i < parameters.size(); i++)
371 values[i] = (t.get()->*parameters[i].get)();
372 return values;
373 };
374 const Values other_values = read_values (other_track);
375 std::array<uint64_t,3> notifications = {};
376 Values notified_values = {};
378 for (size_t i = 0; i < parameters.size(); i++)
379 connections[i] = track->on_event (String ("notify:") + parameters[i].name, [&, i] (const Event&) {
380 notifications[i]++;
381 notified_values[i] = (track.get()->*parameters[i].get)();
382 });
383 auto verify = [&] (const Values &expected, const Values &before, const auto &previous_notifications) {
384 const Values actual = read_values (track);
385 for (size_t i = 0; i < parameters.size(); i++) {
386 TASSERT (std::abs (actual[i] - expected[i]) < 0.001f);
387 if (expected[i] != before[i]) {
388 TASSERT (notifications[i] > previous_notifications[i]);
389 TASSERT (std::abs (notified_values[i] - expected[i]) < 0.001f);
390 }
391 }
392 TASSERT (read_values (other_track) == other_values);
393 };
394 const auto &parameter = parameters[property];
395 const Values initial = read_values (track);
396 auto previous_notifications = notifications;
397 project->group_undo (parameter.name);
398 (track.get()->*parameter.set) (parameter.value);
399 project->ungroup_undo();
400 const Values changed = read_values (track);
401 TASSERT (std::abs (changed[property] - parameter.value) < 0.001f);
402 TASSERT (changed[property] != initial[property]);
403 if (property < 2) {
404 TASSERT (changed[0] != initial[0]);
405 TASSERT (changed[1] != initial[1]);
406 }
407 verify (changed, initial, previous_notifications);
408 TASSERT (project->can_undo());
409 TASSERT (!project->can_redo());
410
411 for (int repeat = 0; repeat < 2; repeat++) {
412 previous_notifications = notifications;
413 project->undo();
414 verify (initial, changed, previous_notifications);
415 TASSERT (project->can_redo());
416
417 previous_notifications = notifications;
418 project->redo();
419 verify (changed, initial, previous_notifications);
420 TASSERT (!project->can_redo());
421 }
422
423 project->discard();
424 }
425}
426
427static void
428track_parameter_values()
429{
430 check_track_parameter_values (false);
431}
432TEST_ADD (track_parameter_values);
433
434static void
435master_parameter_values()
436{
437 check_track_parameter_values (true);
438}
439TEST_ADD (master_parameter_values);
440
441static void
442track_volume_pan()
443{
444 ProjectImplP project = ProjectImpl::create ("TrackVolumePanTest");
445 TASSERT (project);
446
447 TrackP track = project->create_track();
448 TASSERT (track);
449
450 // Test initial volume
451 double initial_vol = track->volume_db();
452 TASSERT (initial_vol >= -100.0 && initial_vol <= 20.0);
453
454 // Test volume notifications
455 uint64_t volume_notifications = 0;
456 auto volume_connection = track->on_event ("notify:volume_db", [&volume_notifications] (const Event &event) { volume_notifications++; });
457
458 uint64_t pan_notifications = 0;
459 auto pan_connection = track->on_event ("notify:pan", [&pan_notifications] (const Event &event) { pan_notifications++; });
460
461 // Test setting volume with notification
462 uint64_t last_volume_notifications = volume_notifications;
463 track->volume_db (-6.0);
464 TASSERT (std::abs (track->volume_db() - (-6.0)) < 0.01);
465 TASSERT (volume_notifications > last_volume_notifications);
466
467 // Reset volume
468 last_volume_notifications = volume_notifications;
469 track->volume_db (0.0);
470 TASSERT (std::abs (track->volume_db()) < 0.01);
471 TASSERT (volume_notifications > last_volume_notifications);
472
473 // Test pan
474 double initial_pan = track->pan();
475 TASSERT (initial_pan >= -1.0 && initial_pan <= 1.0);
476
477 // Test setting pan with notification
478 uint64_t last_pan_notifications = pan_notifications;
479 track->pan (0.5);
480 TASSERT (std::abs (track->pan() - 0.5) < 0.01);
481 TASSERT (pan_notifications > last_pan_notifications);
482
483 // Reset pan
484 last_pan_notifications = pan_notifications;
485 track->pan (-0.5);
486 TASSERT (std::abs (track->pan() - (-0.5)) < 0.01);
487 TASSERT (pan_notifications > last_pan_notifications);
488
489 project->discard();
490}
491TEST_ADD (track_volume_pan);
492
493static void
494track_name()
495{
496 ProjectImplP project = ProjectImpl::create ("TrackNameTest");
497 TASSERT (project);
498
499 TrackP first = project->all_tracks().back();
500 TASSERT (first->name() == "Track 1" && project->master_track()->name() == "Master");
501 TrackP track = project->create_track();
502 TASSERT (track && track->name() == "Track 2");
503
504 // Set and get track name
505 track->name ("MyTrack");
506 TASSERT (track->name() == "MyTrack");
507
508 track->name ("AnotherName");
509 TASSERT (track->name() == "AnotherName");
510
511 // Empty names show Tracktion's default
512 track->name ("");
513 TASSERT (track->name() == "Track 1");
514 track->name ("Track 2");
515
516 // New tracks get a free number, in the same undo step as their creation
517 first->remove_self();
518 TASSERT (track->name() == "Track 2");
519 project->ungroup_undo();
520 const size_t ntracks = project->all_tracks().size();
521 TrackP third = project->create_track();
522 TASSERT (third->name() == "Track 3");
523 project->undo();
524 TASSERT (project->all_tracks().size() == ntracks);
525
526 project->discard();
527}
528TEST_ADD (track_name);
529
530static void
531track_fixed_names()
532{
533 ProjectP project = Server::instance().create_project ("FixedNamesTest");
534 TrackS tracks = project->all_tracks();
535 tracks.push_back (project->master_track());
536 for (const TrackP &track : tracks)
537 if (track->is_control_track() || track->is_master())
538 {
539 const String initial = track->name();
540 if (initial == "Chord")
541 continue;
542 const StringS read_only = track->read_only_properties();
543 TASSERT (std::count (read_only.begin(), read_only.end(), "name"));
544 uint notifications = 0;
545 auto connection = track->on_event ("notify:name", [&] (const Event&) { notifications++; });
546 track->name ("Ignored Name");
547 TASSERT (track->name() == initial && notifications == 0);
548 }
549 project->discard();
550}
551TEST_ADD (track_fixed_names);
552
553static void
554plugin_name()
555{
556 ProjectP project = Server::instance().create_project ("PluginNameTest");
557 TrackP track = project->create_track();
558 for (const String &type : { "text", "insert" })
559 {
560 PluginP plugin = track->create_plugin (type);
561 TASSERT (plugin);
562 const String initial = plugin->name();
563 const StringS read_only = plugin->read_only_properties();
564 TASSERT (!std::count (read_only.begin(), read_only.end(), "name"));
565 uint notifications = 0;
566 auto connection = plugin->on_event ("notify:name", [&] (const Event&) { notifications++; });
567 const String project_name = project->name();
568 project->ungroup_undo();
569 project->name (project_name + " marker");
570 plugin->name ("");
571 TASSERT (plugin->name() == initial && notifications == 0);
572 project->undo();
573 TASSERT (project->name() == project_name);
574 project->redo();
575 TASSERT (project->name() == project_name + " marker");
576 plugin->name ("New Name");
577 TASSERT (plugin->name() == "New Name" && notifications > 0);
578 notifications = 0;
579 plugin->name ("");
580 TASSERT (plugin->name() == initial && notifications > 0);
581 }
582 PluginP fixed = track->create_plugin ("4osc");
583 TASSERT (fixed);
584 const String initial = fixed->name();
585 const StringS read_only = fixed->read_only_properties();
586 TASSERT (std::count (read_only.begin(), read_only.end(), "name"));
587 fixed->name ("Ignored Name");
588 TASSERT (fixed->name() == initial);
589 project->discard();
590}
591TEST_ADD (plugin_name);
592
593static void
594project_name_notifications()
595{
596 ProjectP project = Server::instance().create_project ("ProjectNameNotifyTest");
597 TrackP track = project->create_track();
598 GadgetS children = {
599 track,
600 track->create_midi_clip ("Midi", 0, 1),
601 track->create_audio_clip ("Audio", 2, 1),
602 track->create_plugin ("text"),
603 track->create_plugin ("insert"),
604 };
605 uint notifications = 0;
606 auto connection = project->on_event ("notify:name", [&] (const Event&) { notifications++; });
607 for (const auto &child : children)
608 {
609 TASSERT (child);
610 project->ungroup_undo();
611 child->name ("Child Name");
612 TASSERT (project->name() == "ProjectNameNotifyTest" && notifications == 0);
613 project->undo();
614 TASSERT (notifications == 0);
615 project->redo();
616 TASSERT (notifications == 0);
617 }
618 project->name ("Project Name");
619 TASSERT (notifications > 0);
620 project->discard();
621}
622TEST_ADD (project_name_notifications);
623
624static void
625name_undo_redo()
626{
627 ProjectP project = Server::instance().create_project ("NameUndoTest");
628 TrackP track = project->create_track();
629 TrackP chord;
630 for (const TrackP &control : project->all_tracks())
631 if (control->is_control_track() && control->name() == "Chord")
632 chord = control;
633 TASSERT (chord);
634 const StringS read_only = chord->read_only_properties();
635 TASSERT (!std::count (read_only.begin(), read_only.end(), "name"));
636 const std::vector<std::pair<GadgetP,String>> writable = {
637 { project, "" },
638 { track, "Track 1" },
639 { chord, "" },
640 { track->create_midi_clip ("Midi", 0, 1), "" },
641 { track->create_audio_clip ("Audio", 2, 1), "" },
642 { track->create_plugin ("text"), "Text Plugin" },
643 { track->create_plugin ("insert"), "Insert Plugin" },
644 };
645 for (const auto &[gadget, default_name] : writable)
646 {
647 TASSERT (gadget);
648 const String initial = gadget->name();
649 uint notifications = 0;
650 auto connection = gadget->on_event ("notify:name", [&] (const Event&) { notifications++; });
651 auto check = [&] (const String &expected) {
652 TASSERT (gadget->name() == expected && notifications > 0);
653 notifications = 0;
654 };
655 gadget->name ("First Name");
656 check ("First Name");
657 gadget->name ("Second Name");
658 check ("Second Name");
659 gadget->name ("Second Name");
660 TASSERT (notifications == 0);
661 project->undo();
662 check ("First Name");
663 project->undo();
664 check (initial);
665 project->redo();
666 check ("First Name");
667 project->redo();
668 check ("Second Name");
669 gadget->name ("");
670 check (default_name);
671 project->undo();
672 check ("Second Name");
673 project->redo();
674 check (default_name);
675 }
676 project->discard();
677}
678TEST_ADD (name_undo_redo);
679
680static void
681server_name()
682{
683 Server &server = Server::instance();
684 TASSERT (server.name().empty() && server.read_only_properties() == StringS { "name" });
685 uint notifications = 0;
686 auto connection = server.on_event ("notify:name", [&] (const Event&) { notifications++; });
687 for (const String &name : { "Renamed Server", "" })
688 {
689 server.name (name);
690 TASSERT (server.name().empty() && notifications == 0);
691 }
692}
693TEST_ADD (server_name);
694
695static void
696clip_creation()
697{
698 ProjectImplP project = ProjectImpl::create ("ClipTest");
699 TASSERT (project);
700
701 TrackP track = project->create_track();
702 TASSERT (track);
703
704 TrackImplP trackimpl = std::dynamic_pointer_cast<TrackImpl> (track);
705 TASSERT (trackimpl);
706
707 ClipP clip = trackimpl->create_midi_clip ("TestClip", 0.0, 4.0);
708 TASSERT (clip && clip->name() == "TestClip");
709 TASSERT (track->create_audio_clip ("TestAudio", 4.0, 4.0)->name() == "TestAudio");
710
711 project->discard();
712}
713TEST_ADD (clip_creation);
714
715static void
716clip_audio_notes_read_only()
717{
718 ProjectImplP project = ProjectImpl::create ("AudioNotesReadOnly");
719 ClipP clip = project->create_track()->create_audio_clip ("Audio", 0, 4);
720 const StringS read_only = clip->read_only_properties();
721 TASSERT (std::count (read_only.begin(), read_only.end(), "all_notes") == 1);
722 uint notifications = 0;
723 auto connection = clip->on_event ("notify", [&] (const Event&) { notifications++; });
724 project->clear_undo();
725 ClipNote note { .note_number = 60, .length_beats = 1, .velocity = 90 };
726 clip->all_notes ({ note });
727 TASSERT (clip->all_notes().empty());
728 TASSERT (!project->can_undo() && notifications == 0);
729 project->discard();
730}
731TEST_ADD (clip_audio_notes_read_only);
732
733static void
734clip_notes()
735{
736 ProjectP project = ProjectImpl::create ("ClipNotesTest");
737 TrackP track = project->create_track();
738 project->denominator (8);
739 ClipP clip = track->create_midi_clip ("NotesClip", 0.0, 4.0);
740 TASSERT (clip->all_notes().empty());
741
742 ClipNote first;
743 first.note_number = 60;
744 first.start_beat = 0.125;
745 first.length_beats = 0.375;
746 first.velocity = 0;
747 first.colour = 127;
748 first.mute = true;
749 ClipNote second = first;
750 second.velocity = 127;
751 second.colour = 3;
752 second.mute = false;
753 ClipNoteS added = clip->change_batch ({ first, second }, "Add Notes");
754 TASSERT (added.size() == 2);
755 TASSERT (added[0].id > 0 && added[1].id > 0 && added[0].id != added[1].id);
756 first.id = added[0].id;
757 second.id = added[1].id;
758 TASSERT (added[0] == first && added[1] == second);
759 TASSERT (clip->list_all_notes() == added);
760 project->undo();
761 TASSERT (clip->all_notes().empty());
762 project->redo();
763 TASSERT (clip->all_notes() == added);
764
765 ClipNote moved = second;
766 moved.note_number = 67;
767 moved.start_beat = 1.0 / 3;
768 moved.length_beats = 2.0 / 3;
769 moved.velocity = 64;
770 moved.colour = 12;
771 moved.mute = true;
772 TASSERT (clip->change_batch ({ moved }, "Move Note") == ClipNoteS { moved });
773 TASSERT ((clip->all_notes() == ClipNoteS { first, moved }));
774 project->undo();
775 TASSERT (clip->all_notes() == added);
776 project->redo();
777 TASSERT ((clip->all_notes() == ClipNoteS { first, moved }));
778
779 ClipNote removed = first;
780 removed.length_beats = 0;
781 clip->change_batch ({ removed }, "Delete Note");
782 TASSERT (clip->all_notes() == ClipNoteS { moved });
783 project->undo();
784 TASSERT ((clip->all_notes() == ClipNoteS { first, moved }));
785 project->redo();
786 TASSERT (clip->all_notes() == ClipNoteS { moved });
787
788 clip->all_notes ({ second });
789 const auto replaced = clip->all_notes();
790 TASSERT (replaced.size() == 1 && replaced[0].id != second.id);
791 second.id = replaced[0].id;
792 TASSERT (replaced == ClipNoteS { second });
793 project->undo();
794 TASSERT (clip->all_notes() == ClipNoteS { moved });
795 project->redo();
796 TASSERT (clip->all_notes() == replaced);
797
798 ClipNote fresh = second;
799 fresh.id = 0;
800 const auto mixed = clip->change_batch ({ first, fresh }, "Skip Deleted Handle");
801 TASSERT (mixed.size() == 2 && mixed[0].id == 0 && mixed[1].id > 0);
802 TASSERT (clip->all_notes().size() == 2);
803 const auto before_batch = clip->all_notes();
804 ClipNote modified = before_batch[0], deleted = before_batch[0];
805 modified.velocity = 42;
806 deleted.length_beats = 0;
807 const auto ordered = clip->change_batch ({ modified, fresh, deleted }, "Delete Before Modify");
808 TASSERT (ordered.size() == 3 && ordered[0].id == 0 && ordered[1].id > 0 && ordered[2].id == deleted.id);
809 TASSERT ((clip->all_notes() == ClipNoteS { before_batch[1], ordered[1] }));
810 project->undo();
811 TASSERT (clip->all_notes() == before_batch);
812 project->redo();
813 TASSERT ((clip->all_notes() == ClipNoteS { before_batch[1], ordered[1] }));
814 clip->remove_self();
815 TASSERT (clip->change_batch ({ fresh }, "Edit Removed Clip").empty());
816 TASSERT (clip->position() == ClipPosition{});
817 project->discard();
818}
819TEST_ADD (clip_notes);
820
821static void
822clip_note_notifications()
823{
824 ProjectImplP project = ProjectImpl::create ("NoteNotifications");
825 ClipP clip = project->create_track()->create_midi_clip ("Notes", 0, 4);
827 auto connection = clip->on_event ("notify", [&] (const Event &event) {
828 if (event.detail() == "notes" || event.detail() == "all_notes")
829 notified[event.detail()] = clip->all_notes();
830 });
831 auto require_notifies = [&] (const ClipNoteS &notes) {
832 TASSERT (notified.count ("notes") && notified.at ("notes") == notes);
833 TASSERT (notified.count ("all_notes") && notified.at ("all_notes") == notes);
834 notified.clear();
835 };
836 for (int step = 0; step < 3; step++)
837 {
838 const ClipNoteS before = clip->all_notes();
839 project->clear_undo();
840 ClipNote note { .note_number = 60, .length_beats = 1, .velocity = 90 };
841 if (step > 0)
842 {
843 note = before.at (0);
844 if (step == 1)
845 note.velocity = 45;
846 else
847 note.length_beats = 0;
848 }
849 clip->change_batch ({ note }, "Change Note");
850 const ClipNoteS after = clip->all_notes();
851 TASSERT (before != after);
852 require_notifies (after);
853 project->undo();
854 TASSERT (clip->all_notes() == before);
855 require_notifies (before);
856 project->redo();
857 TASSERT (clip->all_notes() == after);
858 require_notifies (after);
859 }
860 project->discard();
861}
862TEST_ADD (clip_note_notifications);
863
864static void
865clip_range()
866{
867 ProjectP project = ProjectImpl::create ("ClipRangeTest");
868 TrackP track = project->create_track();
869 project->bpm (120);
870 project->numerator (6);
871 for (int denominator : { 4, 8, 16 })
872 {
873 project->denominator (denominator);
874 for (ClipP clip : { track->create_midi_clip ("MidiRange", 0.5, 1.25),
875 track->create_audio_clip ("AudioRange", 0.5, 1.25) })
876 {
877 const ClipPosition initial { { 0.5, 1.75 }, 0 };
878 const double beats_per_second = denominator / 2.0;
879 TASSERT (clip->position() == initial);
880 TASSERT (clip->start_beat() == initial.time.start * beats_per_second);
881 TASSERT (clip->end_beat() == initial.time.end * beats_per_second);
882 uint position_notifications = 0, start_notifications = 0, end_notifications = 0;
883 auto position_connection = clip->on_event ("notify:position", [&] (const Event&) { position_notifications++; });
884 auto start_connection = clip->on_event ("notify:start_beat", [&] (const Event&) { start_notifications++; });
885 auto end_connection = clip->on_event ("notify:end_beat", [&] (const Event&) { end_notifications++; });
886 auto check_position = [&] (const ClipPosition &expected)
887 {
888 TASSERT (clip->position() == expected);
889 TASSERT (clip->start_beat() == expected.time.start * beats_per_second);
890 TASSERT (clip->end_beat() == expected.time.end * beats_per_second);
891 TASSERT (position_notifications > 0 && start_notifications > 0 && end_notifications > 0);
892 position_notifications = start_notifications = end_notifications = 0;
893 };
894 const ClipPosition changed { { 0.625, 2.125 }, 0.375 };
895 clip->position (changed);
896 check_position (changed);
897 project->undo();
898 check_position (initial);
899 project->redo();
900 check_position (changed);
901 const ClipPosition offset_changed { changed.time, 0.875 };
902 clip->position (offset_changed);
903 check_position (offset_changed);
904 project->undo();
905 check_position (changed);
906 project->redo();
907 check_position (offset_changed);
908 }
909 }
910 project->discard();
911}
912TEST_ADD (clip_range);
913
914static void
915clip_mute_volume_pan()
916{
917 ProjectImplP project = ProjectImpl::create ("ClipMuteVolPanTest");
918 TASSERT (project);
919
920 TrackP track = project->create_track();
921 TASSERT (track);
922
923 TrackImplP trackimpl = std::dynamic_pointer_cast<TrackImpl> (track);
924 TASSERT (trackimpl);
925
926 // Test MIDI clip
927 ClipP mclip = trackimpl->create_midi_clip ("MidiClip", 0.0, 4.0);
928 TASSERT (mclip);
929
930 // Test initial state
931 TASSERT (!mclip->muted());
932
933 // Test mute notifications
934 uint64_t muted_notifications = 0;
935 auto muted_connection = mclip->on_event ("notify:muted", [&muted_notifications] (const Event &event) { muted_notifications++; });
936
937 // Test mute with notification
938 uint64_t last_muted_notifications = muted_notifications;
939 mclip->muted (true);
940 TASSERT (mclip->muted());
941 TASSERT (muted_notifications > last_muted_notifications);
942
943 last_muted_notifications = muted_notifications;
944 project->undo();
945 TASSERT (!mclip->muted() && muted_notifications > last_muted_notifications);
946 last_muted_notifications = muted_notifications;
947 project->redo();
948 TASSERT (mclip->muted() && muted_notifications > last_muted_notifications);
949
950 // Reset mute
951 last_muted_notifications = muted_notifications;
952 mclip->muted (false);
953 TASSERT (!mclip->muted());
954 TASSERT (muted_notifications > last_muted_notifications);
955
956 // Test volume notifications
957 uint64_t volume_notifications = 0;
958 auto volume_connection = mclip->on_event ("notify:volume", [&volume_notifications] (const Event &event) { volume_notifications++; });
959
960 // Test setting volume with notification
961 uint64_t last_volume_notifications = volume_notifications;
962 mclip->volume (-6.0);
963 TASSERT (std::abs (mclip->volume() - (-6.0)) < 0.01);
964 TASSERT (volume_notifications > last_volume_notifications);
965
966 // Reset volume
967 last_volume_notifications = volume_notifications;
968 mclip->volume (0.0);
969 TASSERT (std::abs (mclip->volume()) < 0.01);
970 TASSERT (volume_notifications > last_volume_notifications);
971
972 // Test audio clip (pan is only available on audio clips)
973 ClipP aclip = trackimpl->create_audio_clip ("AudioClip", 0.0, 4.0);
974 TASSERT (aclip);
975
976 // Test pan notifications for audio clip
977 uint64_t pan_notifications = 0;
978 auto pan_connection = aclip->on_event ("notify:pan", [&pan_notifications] (const Event &event) { pan_notifications++; });
979
980 // Initial pan should be 0
981 TASSERT (std::abs (aclip->pan()) < 0.01);
982
983 // Test setting pan with notification
984 uint64_t last_pan_notifications = pan_notifications;
985 aclip->pan (0.5);
986 TASSERT (std::abs (aclip->pan() - 0.5) < 0.01);
987 TASSERT (pan_notifications > last_pan_notifications);
988
989 // Reset pan
990 last_pan_notifications = pan_notifications;
991 aclip->pan (-0.5);
992 TASSERT (std::abs (aclip->pan() - (-0.5)) < 0.01);
993 TASSERT (pan_notifications > last_pan_notifications);
994
995 // Test audio clip volume
996 uint64_t audio_volume_notifications = 0;
997 auto audio_volume_connection = aclip->on_event ("notify:volume", [&audio_volume_notifications] (const Event &event) { audio_volume_notifications++; });
998
999 last_volume_notifications = audio_volume_notifications;
1000 aclip->volume (-3.0);
1001 TASSERT (std::abs (aclip->volume() - (-3.0)) < 0.01);
1002 TASSERT (audio_volume_notifications > last_volume_notifications);
1003
1004 project->discard();
1005}
1006TEST_ADD (clip_mute_volume_pan);
1007
1008static void
1009clip_undo_redo()
1010{
1011 ProjectImplP project = ProjectImpl::create ("ClipUndoRedoTest");
1012 TASSERT (project);
1013
1014 TrackP track = project->create_track();
1015 TASSERT (track);
1016
1017 TrackImplP trackimpl = std::dynamic_pointer_cast<TrackImpl> (track);
1018 TASSERT (trackimpl);
1019
1020 // Test MIDI clip mute undo/redo
1021 ClipP mclip = trackimpl->create_midi_clip ("UndoMidiClip", 0.0, 4.0);
1022 TASSERT (mclip);
1023
1024 uint64_t muted_notifications = 0;
1025 auto muted_connection = mclip->on_event ("notify:muted", [&muted_notifications] (const Event &event) { muted_notifications++; });
1026
1027 // Set muted
1028 uint64_t last_muted_notifications = muted_notifications;
1029 mclip->muted (true);
1030 TASSERT (mclip->muted());
1031 TASSERT (muted_notifications > last_muted_notifications);
1032
1033 // Undo mute
1034 TASSERT (project->can_undo());
1035 project->undo();
1036 TASSERT (!mclip->muted());
1037
1038 // Redo mute
1039 TASSERT (project->can_redo());
1040 project->redo();
1041 TASSERT (mclip->muted());
1042
1043 // Test MIDI clip volume undo/redo
1044 uint64_t volume_notifications = 0;
1045 auto volume_connection = mclip->on_event ("notify:volume", [&volume_notifications] (const Event &event) { volume_notifications++; });
1046
1047 // Set volume
1048 double initial_vol = mclip->volume();
1049 uint64_t last_volume_notifications = volume_notifications;
1050 mclip->volume (-12.0);
1051 TASSERT (std::abs (mclip->volume() - (-12.0)) < 0.01);
1052 TASSERT (volume_notifications > last_volume_notifications);
1053
1054 // Undo volume
1055 TASSERT (project->can_undo());
1056 project->undo();
1057 TASSERT (std::abs (mclip->volume() - initial_vol) < 0.01);
1058
1059 // Redo volume
1060 TASSERT (project->can_redo());
1061 project->redo();
1062 TASSERT (std::abs (mclip->volume() - (-12.0)) < 0.01);
1063
1064 // Test audio clip pan undo/redo
1065 ClipP aclip = trackimpl->create_audio_clip ("UndoAudioClip", 0.0, 4.0);
1066 TASSERT (aclip);
1067
1068 uint64_t pan_notifications = 0;
1069 auto pan_connection = aclip->on_event ("notify:pan", [&pan_notifications] (const Event &event) { pan_notifications++; });
1070
1071 // Set pan
1072 double initial_pan = aclip->pan();
1073 uint64_t last_pan_notifications = pan_notifications;
1074 aclip->pan (0.8);
1075 TASSERT (std::abs (aclip->pan() - 0.8) < 0.01);
1076 TASSERT (pan_notifications > last_pan_notifications);
1077
1078 // Undo pan
1079 TASSERT (project->can_undo());
1080 project->undo();
1081 TASSERT (std::abs (aclip->pan() - initial_pan) < 0.01);
1082
1083 // Redo pan
1084 TASSERT (project->can_redo());
1085 project->redo();
1086 TASSERT (std::abs (aclip->pan() - 0.8) < 0.01);
1087
1088 // Test MIDI clip notes undo/redo
1089 ClipNoteS batch;
1090 ClipNote note;
1091 note.id = -1;
1092 note.note_number = 60;
1093 note.start_beat = 0;
1094 note.length_beats = 0.375;
1095 note.velocity = 102;
1096 batch.push_back (note);
1097
1098 uint64_t notes_notifications = 0;
1099 auto notes_connection = mclip->on_event ("notify:notes", [&notes_notifications] (const Event &event) { notes_notifications++; });
1100
1101 // Add notes
1102 uint64_t last_notes_notifications = notes_notifications;
1103 mclip->change_batch (batch, "Add Note");
1104 TASSERT (mclip->list_all_notes().size() == 1);
1105 TASSERT (notes_notifications > last_notes_notifications);
1106
1107 // Undo notes
1108 TASSERT (project->can_undo());
1109 project->undo();
1110 TASSERT (mclip->list_all_notes().empty());
1111
1112 // Redo notes
1113 TASSERT (project->can_redo());
1114 project->redo();
1115 TASSERT (mclip->list_all_notes().size() == 1);
1116
1117 // Test MIDI clip range undo/redo
1118 const ClipPosition initial_position = mclip->position();
1119 ClipPosition changed_position = initial_position;
1120 changed_position.time.start += 0.375;
1121 changed_position.time.end += 0.375;
1122
1123 // Assign new range
1124 uint64_t range_notifications = 0;
1125 auto range_connection = mclip->on_event ("notify:position", [&range_notifications] (const Event &event) { range_notifications++; });
1126
1127 uint64_t last_range_notifications = range_notifications;
1128 mclip->position (changed_position);
1129 TASSERT (mclip->position() == changed_position);
1130 TASSERT (range_notifications > last_range_notifications);
1131
1132 // Undo range
1133 TASSERT (project->can_undo());
1134 project->undo();
1135 TASSERT (mclip->position() == initial_position);
1136
1137 // Redo range
1138 TASSERT (project->can_redo());
1139 project->redo();
1140 TASSERT (mclip->position() == changed_position);
1141
1142 project->discard();
1143}
1144TEST_ADD (clip_undo_redo);
1145
1146static void
1147plugin_creation()
1148{
1149 ProjectP project = ProjectImpl::create ("PluginTest");
1150 TrackP track = project->create_track();
1151 TASSERT (track);
1152 PluginS plugins = track->list_plugins();
1153 TASSERT (!plugins.empty());
1154 PluginP plugin = plugins.front();
1155 TASSERT (plugin);
1156 TASSERT (!plugin->name().empty());
1157 TASSERT (!plugin->plugin_type().empty());
1158
1159 for (bool check_enabled : { true, false })
1160 {
1161 const auto get = [&] { return check_enabled ? plugin->enabled() : plugin->frozen(); };
1162 const auto set = [&] (bool value) {
1163 if (check_enabled)
1164 plugin->enabled (value);
1165 else
1166 plugin->frozen (value);
1167 };
1168 uint notifications = 0;
1169 auto connection = plugin->on_event (check_enabled ? "notify:enabled" : "notify:frozen",
1170 [&] (const Event&) { notifications++; });
1171 const bool initial = get();
1172 project->ungroup_undo();
1173 set (!initial);
1174 TASSERT (get() != initial);
1175 TASSERT (notifications > 0);
1176 TASSERT (project->can_undo());
1177
1178 notifications = 0;
1179 set (!initial);
1180 TASSERT (notifications == 0);
1181 project->undo();
1182 TASSERT (get() == initial);
1183 TASSERT (notifications > 0);
1184 TASSERT (project->can_redo());
1185
1186 notifications = 0;
1187 project->redo();
1188 TASSERT (get() != initial);
1189 TASSERT (notifications > 0);
1190 }
1191
1192 uint removed_count = 0;
1193 auto removed_connection = plugin->on_event ("object:removed", [&] (const Event&) { removed_count++; });
1194 plugin->remove_self();
1195 TASSERT (removed_count > 0);
1196 project->discard();
1197}
1198TEST_ADD (plugin_creation);
1199
1200} // Anon
T begin(T... args)
virtual StringS read_only_properties() const
List x()/x (v) properties that this object can't change, their setters ignore the value or warn.
Definition gadget.cc:103
Central singleton, serves as API entry point.
Definition api.hh:412
virtual ProjectP create_project(String projectname)=0
Create a new project (name is modified to be unique if necessary.
static Server & instance()
Retrieve global Server instance.
Definition server.cc:210
Container for Clip objects and sequencing information.
Definition api.hh:278
virtual float volume_db() const =0
Get track volume in dB.
virtual float pan() const =0
Get track pan (-1.0 to 1.0).
virtual float slider_pos() const =0
Get track volume fader position.
T count(T... args)
T empty(T... args)
T end(T... args)
T fixed(T... args)
typedef float
bool check(const String &file, const String &mode)
Definition path.cc:625
The Anklang C++ API namespace.
Definition api.hh:8
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:17
typedef uint64_t
A Tracktion MIDI note with a session handle for edits.
Definition api.hh:229
bool mute
Tracktion note colour index.
Definition api.hh:236
int colour
Velocity, 0 .. 127.
Definition api.hh:235
double start_beat
Musical note as MIDI key, 0 .. 127.
Definition api.hh:232
int velocity
Duration in Tracktion beats.
Definition api.hh:234
int note_number
Session handle, > 0 for existing notes.
Definition api.hh:231
double length_beats
Position in Tracktion beats from the start of the clip.
Definition api.hh:233
Tracktion clip position in seconds.
Definition api.hh:248
Structure for callback based notifications.
Definition value.hh:112
#define TASSERT(cond)
Unconditional test assertion, enters breakpoint if not fullfilled.
Definition testing.hh:23
#define TCMP(a, cmp, b)
Compare a and b according to operator cmp, verbose on failiure.
Definition testing.hh:22
#define TEST_ADD(fun)
Register a function to run as part of the unit test suite.
Definition testing.hh:30