Anklang-0.3.0.dev712+gdc4e642f anklang-0.3.0.dev712+gdc4e642f
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
project.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 "project.hh"
3#include "jsonipc/jsonipc.hh"
4#include "main.hh"
5#include "processor.hh"
6#include "compress.hh"
7#include "path.hh"
8#include "unicode.hh"
9#include "serialize.hh"
10#include "storage.hh"
11#include "server.hh"
12#include "internal.hh"
13
14#define UDEBUG(...) Ase::debug ("undo", __VA_ARGS__)
15
16using namespace std::literals;
17
18namespace Ase {
19
20static Preference synth_latency_pref =
21 Preference ({
22 "project.default_license", _("Default License"), "",
23 "CC-BY-SA-4.0 - https://creativecommons.org/licenses/by-sa/4.0/legalcode",
24 "",
25 {}, STANDARD, {
26 String ("descr=") + _("Default LICENSE to apply in the project properties."), } });
27
28static std::vector<ProjectImplP> &all_projects = *new std::vector<ProjectImplP>();
29
30// == Project ==
31Project::Project() :
32 bpm (this, "bpm", MinMaxStep { 10., 999., 0 }, { "label="s + _("Beats Per Minute"), "nick=BPM" }),
33 numerator (this, "numerator", MinMaxStep { 1., 63., 0 }, { "label="s + _("Signature Numerator"), "nick=Num" }),
34 denominator (this, "denominator", MinMaxStep { 1, 16, 0 }, { "label="s + _("Signature Denominator"), "nick=Den" })
35{}
36
38Project::last_project()
39{
40 return all_projects.empty() ? nullptr : all_projects.back();
41}
42
43// == ProjectImpl ==
44using StringPairS = std::vector<std::tuple<String,String>>;
45
47 String loading_file;
48 String writer_cachedir;
49 String anklang_dir;
50 StringPairS writer_files;
51 StringPairS asset_hashes;
53 ptrp_ (ptrp)
54 {
55 *ptrp_ = this;
56 }
57 ~PStorage()
58 {
59 *ptrp_ = nullptr;
60 }
61private:
62 PStorage **const ptrp_ = nullptr;
63};
64
65ProjectImpl::ProjectImpl()
66{
67 if (tracks_.empty())
68 create_track (); // ensure Master track
69 bpm = 120;
70 numerator = 4;
71 denominator = 4;
72
73 if (0)
74 autoplay_timer_ = main_loop->add ([this] () {
75 return_unless (autoplay_timer_ != LoopID::INVALID, false);
76 autoplay_timer_ = LoopID::INVALID;
77 if (!is_playing())
78 start_playback();
79 return false;
81
82 /* TODO: MusicalTuning
83 * group = _("Tuning");
84 * Prop ("musical_tuning", _("Musical Tuning"), _("Tuning"), MusicalTuning::OD_12_TET, {
85 * "descr="s + _("The tuning system which specifies the tones or pitches to be used. "
86 * "Due to the psychoacoustic properties of tones, various pitch combinations can "
87 * "sound \"natural\" or \"pleasing\" when used in combination, the musical "
88 * "tuning system defines the number and spacing of frequency values applied."), "" },
89 * enum_lister<MusicalTuning>);
90 */
91}
92
93ProjectImpl::~ProjectImpl()
94{
95 main_loop->cancel (&autoplay_timer_);
96}
97
98
99void
100ProjectImpl::force_shutdown_all ()
101{
102}
103
104ProjectImplP
105ProjectImpl::create (const String &projectname)
106{
107 ProjectImplP project = ProjectImpl::make_shared();
108 all_projects.push_back (project);
109 project->name (projectname);
110 return project;
111}
112
113void
114ProjectImpl::discard ()
115{
116 return_unless (!discarded_);
117 stop_playback();
118 const size_t nerased = Aux::erase_first (all_projects, [this] (auto ptr) { return ptr.get() == this; });
119 if (nerased)
120 {} // resource cleanups...
121 discarded_ = true;
122}
123
124void
125ProjectImpl::_activate ()
126{
127 assert_return (!is_active());
128 DeviceImpl::_activate();
129 for (auto &track : tracks_)
130 track->_activate();
131}
132
133void
134ProjectImpl::_deactivate ()
135{
136 assert_return (is_active());
137 for (auto trackit = tracks_.rbegin(); trackit != tracks_.rend(); ++trackit)
138 (*trackit)->_deactivate();
139 DeviceImpl::_deactivate();
140}
141
142static bool
143is_anklang_dir (const String &path)
144{
145 return Path::check (Path::join (path, ".anklang.project"), "r");
146}
147
148static String
149find_anklang_parent_dir (const String &path)
150{
151 for (String p = path; !p.empty() && !Path::isroot (p); p = Path::dirname (p))
152 if (is_anklang_dir (p))
153 return p;
154 return "";
155}
156
157static bool
158make_anklang_dir (const String &path)
159{
160 String mime = Path::join (path, ".anklang.project");
161 return Path::stringwrite (mime, "# ANKLANG(1) project directory\n");
162}
163
164Error
165ProjectImpl::save_project (const String &utf8filename, bool collect)
166{
168 assert_return (storage_ == nullptr, Error::OPERATION_BUSY);
169 PStorage storage (&storage_); // storage_ = &storage;
170 const String dotanklang = ".anklang";
171 String projectfile, path = Path::normalize (Path::abspath (savepath));
172 // check path is a file
173 if (path.back() == '/' ||
174 Path::check (path, "d")) // need file not directory
175 return Error::FILE_IS_DIR;
176 // force .anklang extension
177 if (!string_endswith (path, dotanklang))
178 path += dotanklang;
179 // existing files need proper project directories
180 if (Path::check (path, "e")) // existing file
181 {
182 const String dir = Path::dirname (path);
183 if (!is_anklang_dir (dir))
184 return Error::NO_PROJECT_DIR;
185 projectfile = Path::basename (path);
186 path = dir; // file inside project dir
187 }
188 else // new file name
189 {
190 projectfile = Path::basename (path);
191 const String parentdir = Path::dirname (path);
192 if (is_anklang_dir (parentdir))
193 path = parentdir;
194 else { // use projectfile stem as dir
195 assert_return (string_endswith (path, dotanklang), Error::INTERNAL);
196 path.resize (path.size() - dotanklang.size());
197 }
198 }
199 // create parent directory
200 if (!Path::mkdirs (path))
201 return ase_error_from_errno (errno);
202 // ensure path is_anklang_dir
203 if (!make_anklang_dir (path))
204 return ase_error_from_errno (errno);
205 storage_->anklang_dir = path;
206 const String abs_projectfile = Path::join (path, projectfile);
207 // create backups
208 if (Path::check (abs_projectfile, "e"))
209 {
210 const String backupdir = Path::join (path, "backup");
211 if (!Path::mkdirs (backupdir))
212 return ase_error_from_errno (errno ? errno : EPERM);
213 const StringPair parts = Path::split_extension (projectfile, true);
214 const String backupname = Path::join (backupdir, parts.first + now_strftime (" (%y%m%dT%H%M%S)") + parts.second);
215 const String backupglob = Path::join (backupdir, parts.first + " ([0-9]*[0-9]T[0-9]*[0-9])" + parts.second);
216 if (!Path::rename (abs_projectfile, backupname))
217 ASE_SERVER.user_note (string_format ("## Backup failed\n%s: \\\nFailed to create backup: \\\n%s",
218 backupname, ase_error_blurb (ase_error_from_errno (errno))));
219 else // successful backup, now prune
220 {
222 Path::glob (backupglob, backups);
223 strings_version_sort (&backups, true);
224 const int bmax = 24;
225 while (backups.size() > bmax)
226 {
227 const String bfile = backups.back();
228 backups.pop_back();
229 Path::rmrf (bfile);
230 }
231 }
232 }
233 // start writing
235 storage_->writer_cachedir = anklang_cachedir_create();
236 storage_->asset_hashes.clear();
237 StorageWriter ws (Storage::AUTO_ZSTD);
238 Error error = ws.open_with_mimetype (abs_projectfile, "application/x-anklang");
239 if (!error)
240 {
241 // serialize Project
242 String jsd = json_stringify (*this, Writ::RELAXED);
243 jsd += '\n';
244 error = ws.store_file_data ("project.json", jsd, true);
245 }
246 if (!error)
247 for (const auto &[path, dest] : storage_->writer_files) {
248 error = ws.store_file (dest, path);
249 if (!!error) {
250 printerr ("%s: %s: %s: %s\n", program_alias(), __func__, path, ase_error_blurb (error));
251 break;
252 }
253 }
254 storage_->writer_files.clear();
255 if (!error)
256 error = ws.close();
257 if (!error)
258 saved_filename_ = abs_projectfile;
259 if (!!error)
260 ws.remove_opened();
261 anklang_cachedir_cleanup (storage_->writer_cachedir);
262 return error;
263}
264
265Error
266ProjectImpl::snapshot_project (String &json)
267{
268 assert_return (storage_ == nullptr, Error::OPERATION_BUSY);
269 // writer setup
270 PStorage storage (&storage_); // storage_ = &storage;
271 storage_->writer_cachedir = anklang_cachedir_create();
272 if (storage_->writer_cachedir.empty() || !Path::check (storage_->writer_cachedir, "d"))
273 return Error::NO_PROJECT_DIR;
274 storage_->anklang_dir = storage_->writer_cachedir;
275 storage_->asset_hashes.clear();
276 // serialize Project
277 json = json_stringify (*this, Writ::RELAXED) + '\n';
278 // cleanup
279 anklang_cachedir_cleanup (storage_->writer_cachedir);
280 return Error::NONE;
281}
282
283String
284ProjectImpl::writer_file_name (const String &fspath) const
285{
286 assert_return (storage_ != nullptr, "");
287 assert_return (!storage_->writer_cachedir.empty(), "");
288 return Path::join (storage_->writer_cachedir, fspath);
289}
290
291Error
292ProjectImpl::writer_add_file (const String &fspath)
293{
294 assert_return (storage_ != nullptr, Error::INTERNAL);
295 assert_return (!storage_->writer_cachedir.empty(), Error::INTERNAL);
296 if (!Path::check (fspath, "frw"))
297 return Error::FILE_NOT_FOUND;
298 if (!string_startswith (fspath, storage_->writer_cachedir))
299 return Error::FILE_OPEN_FAILED;
300 storage_->writer_files.push_back ({ fspath, Path::basename (fspath) });
301 return Error::NONE;
302}
303
304Error
305ProjectImpl::writer_collect (const String &fspath, String *hexhashp)
306{
307 assert_return (storage_ != nullptr, Error::INTERNAL);
308 assert_return (!storage_->anklang_dir.empty(), Error::INTERNAL);
309 if (!Path::check (fspath, "fr"))
310 return Error::FILE_NOT_FOUND;
311 // determine hash of file to collect
312 const String hexhash = string_to_hex (blake3_hash_file (fspath));
313 if (hexhash.empty())
314 return ase_error_from_errno (errno ? errno : EIO);
315 // resolve against existing hashes
316 for (const auto &hf : storage_->asset_hashes)
317 if (std::get<0> (hf) == hexhash)
318 {
319 *hexhashp = hexhash;
320 return Error::NONE;
321 }
322 // file may be within project directory
324 if (Path::dircontains (storage_->anklang_dir, fspath, &relpath))
325 {
326 storage_->asset_hashes.push_back ({ hexhash, relpath });
327 *hexhashp = hexhash;
328 return Error::NONE;
329 }
330 // determine unique path name
331 const size_t file_size = Path::file_size (fspath);
332 const String basedir = storage_->anklang_dir;
333 relpath = Path::join ("samples", Path::basename (fspath));
334 String dest = Path::join (basedir, relpath);
335 size_t i = 0;
336 while (Path::check (dest, "e"))
337 {
338 if (file_size == Path::file_size (dest))
339 {
340 const String althash = string_to_hex (blake3_hash_file (dest));
341 if (althash == hexhash)
342 {
343 // found file with same hash within project directory
344 storage_->asset_hashes.push_back ({ hexhash, relpath });
345 *hexhashp = hexhash;
346 return Error::NONE;
347 }
348 }
349 // add counter to create unique name
350 const StringPair parts = Path::split_extension (relpath, true);
351 dest = Path::join (basedir, string_format ("%s(%u)%s", parts.first, ++i, parts.second));
352 }
353 // create parent dir
354 if (!Path::mkdirs (Path::dirname (dest)))
355 return ase_error_from_errno (errno);
356 // copy into project dir
357 const bool copied = Path::copy_file (fspath, dest);
358 if (!copied)
359 return ase_error_from_errno (errno);
360 // success
361 storage_->asset_hashes.push_back ({ hexhash, relpath });
362 *hexhashp = hexhash;
363 return Error::NONE;
364}
365
366String
367ProjectImpl::saved_filename ()
368{
369 return encodefs (saved_filename_);
370}
371
372Error
373ProjectImpl::load_project (const String &utf8filename)
374{
375 const String filename = decodefs (utf8filename);
376 assert_return (storage_ == nullptr, Error::OPERATION_BUSY);
377 PStorage storage (&storage_); // storage_ = &storage;
378 String fname = filename;
379 // turn /dir/.anklang.project -> /dir/
380 if (Path::basename (fname) == ".anklang.project" && is_anklang_dir (Path::dirname (fname)))
381 fname = Path::dirname (fname);
382 // turn /dir/ -> /dir/dir.anklang
383 if (Path::check (fname, "d"))
384 fname = Path::join (fname, Path::basename (Path::strip_slashes (Path::normalize (fname)))) + ".anklang";
385 // add missing '.anklang' extension
386 if (!Path::check (fname, "e"))
387 fname += ".anklang";
388 // check for readable file
389 if (!Path::check (fname, "e"))
390 return ase_error_from_errno (errno);
391 // try reading .anklang container
392 StorageReader rs (Storage::AUTO_ZSTD);
393 Error error = rs.open_for_reading (fname);
394 if (!!error)
395 return error;
396 if (rs.stringread ("mimetype") != "application/x-anklang")
397 return Error::BAD_PROJECT;
398 // find project.json *inside* container
399 String jsd = rs.stringread ("project.json");
400 if (jsd.empty() && errno)
401 return Error::FORMAT_INVALID;
402 storage_->loading_file = fname;
403 storage_->anklang_dir = find_anklang_parent_dir (storage_->loading_file);
404#if 0 // unimplemented
405 String dirname = Path::dirname (fname);
406 // search in dirname or dirname/..
407 if (is_anklang_dir (dirname))
408 rs.search_dir (dirname);
409 else
410 {
411 dirname = Path::dirname (dirname);
412 if (is_anklang_dir (dirname))
413 rs.search_dir (dirname);
414 }
415#endif
416 // parse project
417 if (!json_parse (jsd, *this))
418 return Error::PARSE_ERROR;
419 saved_filename_ = storage_->loading_file;
420 return Error::NONE;
421}
422
423StreamReaderP
424ProjectImpl::load_blob (const String &fspath)
425{
426 assert_return (storage_ != nullptr, nullptr);
427 assert_return (!storage_->loading_file.empty(), nullptr);
428 return stream_reader_zip_member (storage_->loading_file, fspath);
429}
430
432String
433ProjectImpl::loader_resolve (const String &hexhash)
434{
435 return_unless (storage_ && storage_->asset_hashes.size(), "");
436 return_unless (!storage_->anklang_dir.empty(), "");
437 for (const auto& [hash,relpath] : storage_->asset_hashes)
438 if (hexhash == hash)
439 return Path::join (storage_->anklang_dir, relpath);
440 return "";
441}
442
443void
444ProjectImpl::serialize (WritNode &xs)
445{
446 // provide asset_hashes early on
447 if (xs.in_load() && storage_ && storage_->asset_hashes.empty())
448 xs["filehashes"] & storage_->asset_hashes;
449 // serrialize children
450 DeviceImpl::serialize (xs);
451 // load tracks
452 if (xs.in_load())
453 for (auto &xc : xs["tracks"].to_nodes())
454 {
455 TrackImplP trackp = tracks_.back(); // master_track
456 if (!xc["mastertrack"].as_int())
457 trackp = shared_ptr_cast<TrackImpl> (create_track());
458 xc & *trackp;
459 }
460 // save tracks
461 if (xs.in_save())
462 {
463 for (auto &trackp : tracks_)
464 {
465 WritNode xc = xs["tracks"].push();
466 xc & *trackp;
467 if (trackp == tracks_.back()) // master_track
468 xc.front ("mastertrack") << true;
469 }
470 // store external reference hashes *after* all other objects
471 if (storage_ && storage_->asset_hashes.size())
472 xs["filehashes"] & storage_->asset_hashes;
473 }
474}
475
476String
477ProjectImpl::match_serialized (const String &regex, int group)
478{
479 String json;
480 Error error = snapshot_project (json);
481 if (!!error) {
482 warning ("Project: failed to serialize project: %s\n", ase_error_blurb (error));
483 return "";
484 }
485 return Re::grep (regex, json, group);
486}
487
488UndoScope::UndoScope (ProjectImplP projectp) :
489 projectp_ (projectp)
490{
492 projectp->undo_scopes_open_++;
493}
494
495UndoScope::~UndoScope()
496{
497 assert_return (projectp_->undo_scopes_open_);
498 projectp_->undo_scopes_open_--;
499}
500
501void
502UndoScope::operator+= (const VoidF &func)
503{
504 projectp_->push_undo (func);
505}
506
507UndoScope
508ProjectImpl::undo_scope (const String &scopename)
509{
510 assert_warn (scopename != "");
511 const size_t old_undo = undostack_.size();
512 const size_t old_redo = redostack_.size();
513 UndoScope undoscope = add_undo_scope (scopename);
514 if (undostack_.size() > old_undo && redostack_.size())
515 redostack_.clear();
516 if ((!old_undo ^ !undostack_.size()) || (!old_redo ^ !redostack_.size()))
517 emit_notify ("dirty");
518 return undoscope;
519}
520
521UndoScope
522ProjectImpl::add_undo_scope (const String &scopename)
523{
524 UndoScope undoscope (shared_ptr_cast<ProjectImpl> (this)); // undo_scopes_open_ += 1
526 if (undo_scopes_open_ == 1 && (undo_groups_open_ == 0 || undo_group_name_.size()))
527 {
528 undostack_.push_back ({ nullptr, undo_group_name_.empty() ? scopename : undo_group_name_ });
529 undo_group_name_ = "";
530 }
531 return undoscope;
532}
533
534void
535ProjectImpl::push_undo (const VoidF &func)
536{
537 undostack_.push_back ({ func, "" });
538 if (undostack_.size() == 1)
539 emit_notify ("dirty");
540}
541
542void
544{
545 assert_return (undo_scopes_open_ == 0 && undo_groups_open_ == 0);
546 return_unless (!undostack_.empty());
548 while (!undostack_.empty() && undostack_.back().func)
549 {
550 funcs.push_back (undostack_.back().func);
551 undostack_.pop_back();
552 }
553 assert_return (!undostack_.empty() && undostack_.back().func == nullptr); // must contain scope name
554 const String scopename = undostack_.back().name;
555 UDEBUG ("Undo: steps=%d scope: %s\n", funcs.size(), scopename);
556 undostack_.pop_back(); // pop scope name
557 // swap undo/redo stacks, run undo steps and scope redo
558 const bool redostack_was_empty = redostack_.empty();
559 undostack_.swap (redostack_);
560 {
561 auto undoscope = add_undo_scope (scopename); // preserves redostack_
562 for (const auto &func : funcs)
563 func();
564 }
565 undostack_.swap (redostack_);
566 if (redostack_was_empty || undostack_.empty())
567 emit_notify ("dirty");
568}
569
570bool
572{
573 return undostack_.size() > 0;
574}
575
576void
578{
579 assert_return (undo_scopes_open_ == 0 && undo_groups_open_ == 0);
580 return_unless (!redostack_.empty());
582 while (!redostack_.empty() && redostack_.back().func)
583 {
584 funcs.push_back (redostack_.back().func);
585 redostack_.pop_back();
586 }
587 assert_return (!redostack_.empty() && redostack_.back().func == nullptr); // must contain scope name
588 const String scopename = redostack_.back().name;
589 UDEBUG ("Undo: steps=%d scope: %s\n", funcs.size(), scopename);
590 redostack_.pop_back(); // pop scope name
591 // run redo steps with undo scope
592 const bool undostack_was_empty = undostack_.empty();
593 {
594 auto undoscope = add_undo_scope (scopename); // preserves redostack_
595 for (const auto &func : funcs)
596 func();
597 }
598 if (undostack_was_empty || redostack_.empty())
599 emit_notify ("dirty");
600}
601
602bool
604{
605 return redostack_.size() > 0;
606}
607
608void
610{
611 assert_return (undoname != "");
612 undo_groups_open_++;
613 if (undo_groups_open_ == 1)
614 undo_group_name_ = undoname;
615 /* Opened undo groups cause:
616 * a) rename of the first opened undo scope
617 * b) merging of undo scopes
618 * c) block undo(), redo() calls
619 * We avoid group state tracking through IPC boundaries.
620 */
621}
622
623void
625{
626 assert_return (undo_groups_open_ > 0);
627 undo_groups_open_--;
628 if (!undo_groups_open_)
629 undo_group_name_ = "";
630}
631
632void
633ProjectImpl::clear_undo ()
634{
635 assert_warn (undo_scopes_open_ == 0 && undo_groups_open_ == 0);
636 undostack_.clear();
637 redostack_.clear();
638 emit_notify ("dirty");
639}
640
641size_t ProjectImpl::undo_mem_counter = 0;
642
643size_t
644ProjectImpl::undo_size_guess () const
645{
646 size_t count = undostack_.size();
647 count += redostack_.size();
648 size_t item = sizeof (UndoFunc);
649 item += sizeof (std::shared_ptr<void>); // undofunc selfp
650 item += 4 * sizeof (uint64); // undofunc arguments: double ClipNote struct
651 return count * item + undo_mem_counter;
652}
653
656{
658 AudioProcessorP proc = master_processor ();
659 assert_return (proc, v);
660 const AudioTransport &transport = proc->transport();
661 v.push_back (telemetry_field ("current_tick", &transport.current_tick_d));
662 v.push_back (telemetry_field ("current_bar", &transport.current_bar));
663 v.push_back (telemetry_field ("current_beat", &transport.current_beat));
664 v.push_back (telemetry_field ("current_sixteenth", &transport.current_semiquaver));
665 v.push_back (telemetry_field ("current_bpm", &transport.current_bpm));
666 v.push_back (telemetry_field ("current_minutes", &transport.current_minutes));
667 v.push_back (telemetry_field ("current_seconds", &transport.current_seconds));
668 return v;
669}
670
671AudioProcessorP
672ProjectImpl::master_processor () const
673{
674 return_unless (!tracks_.empty(), nullptr);
675 TrackP master = const_cast<ProjectImpl*> (this)->master_track();
676 return_unless (master, nullptr);
677 DeviceP device = master->access_device();
678 return_unless (device, nullptr);
679 AudioProcessorP proc = device->_audio_processor();
680 return_unless (proc, nullptr);
681 return proc;
682}
683
684void
685ProjectImpl::set_bpm (double newbpm)
686{
687 const double nbpm = CLAMP (newbpm, MIN_BPM, MAX_BPM);
688 if (tick_sig_.bpm() != nbpm) {
689 tick_sig_.set_bpm (nbpm);
690 update_tempo();
691 }
692 if (newbpm != tick_sig_.bpm())
693 bpm.notify();
694}
695
696double
697ProjectImpl::get_bpm () const
698{
699 return tick_sig_.bpm();
700}
701
702void
703ProjectImpl::set_numerator (double num)
704{
705 const bool changed = tick_sig_.set_signature (num, tick_sig_.beat_unit());
706 if (changed) {
707 update_tempo();
708 numerator.notify();
709 }
710}
711
712double
713ProjectImpl::get_numerator () const
714{
715 return tick_sig_.beats_per_bar();
716}
717
718void
719ProjectImpl::set_denominator (double den)
720{
721 const bool changed = tick_sig_.set_signature (tick_sig_.beats_per_bar(), den);
722 if (changed) {
723 update_tempo();
724 denominator.notify();
725 }
726}
727
728double
729ProjectImpl::get_denominator () const
730{
731 return tick_sig_.beat_unit();
732}
733
734void
735ProjectImpl::update_tempo ()
736{
737 AudioProcessorP proc = master_processor();
738 return_unless (proc);
739 const TickSignature tsig (tick_sig_);
740 auto job = [proc, tsig] () {
741 AudioTransport &transport = const_cast<AudioTransport&> (proc->engine().transport());
742 transport.tempo (tsig);
743 };
744 proc->engine().async_jobs += job;
745}
746
747void
749{
750 assert_return (!discarded_);
751 assert_return (App.engine);
752 AudioEngine &engine = *App.engine;
753 ProjectImplP selfp = shared_ptr_cast<ProjectImpl> (this); // keep alive while engine changes refcounts
754 ProjectImplP oldp = engine.get_project(); // keep alive while engine changes refs
755 if (oldp != selfp)
756 {
757 if (oldp)
758 {
759 oldp->stop_playback();
760 engine.set_project (nullptr);
761 }
762 engine.set_project (selfp);
763 }
764 assert_return (this == &*engine.get_project());
765
766 main_loop->cancel (&autoplay_timer_);
767 AudioProcessorP proc = master_processor();
768 return_unless (proc);
770 for (auto track : tracks_)
771 track->queue_cmd (*queuep, track->START);
772 const TickSignature tsig (tick_sig_);
773 auto job = [proc, queuep, tsig, autostop] () {
774 AudioEngine &engine = proc->engine();
775 const double udmax = 18446744073709549568.0; // max double exactly matching an uint64_t
776 const uint64_t s = autostop > udmax ? udmax : autostop * engine.sample_rate();
777 engine.set_autostop (s);
778 AudioTransport &transport = const_cast<AudioTransport&> (engine.transport());
779 transport.tempo (tsig);
780 transport.running (true);
781 for (const auto &cmd : *queuep)
782 cmd();
783 };
784 proc->engine().async_jobs += job;
785}
786
787void
789{
790 main_loop->cancel (&autoplay_timer_);
791 AudioProcessorP proc = master_processor();
792 return_unless (proc);
794 for (auto track : tracks_)
795 track->queue_cmd (*stop_queuep, track->STOP);
796 auto job = [proc, stop_queuep] () {
797 AudioTransport &transport = const_cast<AudioTransport&> (proc->engine().transport());
798 const bool wasrunning = transport.running();
799 transport.running (false);
800 if (!wasrunning)
801 transport.set_tick (-AUDIO_BLOCK_MAX_RENDER_SIZE / 2 * transport.tick_sig.ticks_per_sample());
802 for (const auto &stop : *stop_queuep)
803 stop (!wasrunning); // restart = !wasrunning
804 if (!wasrunning)
805 transport.set_tick (0); // adjust transport and track positions
806 };
807 proc->engine().async_jobs += job;
808}
809
810bool
812{
813 AudioProcessorP proc = master_processor();
814 return_unless (proc, false);
815 return proc->engine().transport().current_bpm > 0.0;
816}
817
818TrackP
820{
821 assert_return (!discarded_, nullptr);
822 const bool havemaster = tracks_.size() != 0;
823 TrackImplP track = TrackImpl::make_shared (*this, !havemaster);
824 tracks_.insert (tracks_.end() - int (havemaster), track);
825 emit_event ("track", "insert", { { "track", track }, });
826 track->_set_parent (this);
827 emit_notify ("all_tracks");
828 return track;
829}
830
831bool
833{
834 assert_return (child._parent() == this, false);
836 return_unless (track && !track->is_master(), false);
837 clear_undo(); // TODO: implement undo for remove_track
838 if (!Aux::erase_first (tracks_, [track] (TrackP t) { return t == track; }))
839 return false;
840 // destroy Track
841 track->_set_parent (nullptr);
842 emit_event ("track", "remove");
843 emit_notify ("all_tracks");
844 return true;
845}
846
847TrackS
849{
850 TrackS tracks (tracks_.size());
851 std::copy (tracks_.begin(), tracks_.end(), tracks.begin());
852 return tracks;
853}
854
856ProjectImpl::track_index (const Track &child) const
857{
858 for (size_t i = 0; i < tracks_.size(); i++)
859 if (&child == tracks_[i].get())
860 return i;
861 return -1;
862}
863
864TrackP
866{
867 assert_return (!tracks_.empty(), nullptr);
868 return tracks_.back();
869}
870
873{
874 return {}; // TODO: DeviceInfo
875}
876
877AudioProcessorP
879{
880 return {}; // TODO: AudioProcessorP
881}
882
883void
884ProjectImpl::_set_event_source (AudioProcessorP esource)
885{
886 // TODO: _set_event_source
887}
888
889} // Ase
#define EPERM
T back(T... args)
T begin(T... args)
void emit_notify(const String &detail) override
Emit notify:detail, multiple notifications maybe coalesced if a CoalesceNotifies instance exists.
Definition object.cc:164
bool remove_track(Track &child) override
Remove a track owned by this Project.
Definition project.cc:832
void group_undo(const String &undoname) override
Merge upcoming undo steps.
Definition project.cc:609
void redo() override
Redo the last undo modification.
Definition project.cc:577
bool can_redo() override
Check if any redo steps have been recorded.
Definition project.cc:603
TrackP master_track() override
Retrieve the master track.
Definition project.cc:865
bool is_playing() override
Check whether a project is currently playing (song sequencing).
Definition project.cc:811
void undo() override
Undo the last project modification.
Definition project.cc:543
bool can_undo() override
Check if any undo steps have been recorded.
Definition project.cc:571
TelemetryFieldS telemetry() const override
Retrieve project telemetry locations.
Definition project.cc:655
TrackS all_tracks() override
List all tracks of the project.
Definition project.cc:848
void ungroup_undo() override
Stop merging undo steps.
Definition project.cc:624
TrackP create_track() override
Create and append a new Track.
Definition project.cc:819
AudioProcessorP _audio_processor() const override
Retrieve the corresponding AudioProcessor.
Definition project.cc:878
void start_playback() override
Start playback of a project, requires active sound engine.
Definition project.hh:71
DeviceInfo device_info() override
Describe this Device type.
Definition project.cc:872
void stop_playback() override
Stop project playback.
Definition project.cc:788
Container for Clip objects and sequencing information.
Definition api.hh:285
One entry in a Writ serialization document.
Definition serialize.hh:24
T clear(T... args)
T copy(T... args)
dirname
T empty(T... args)
T end(T... args)
errno
T insert(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
#define CLAMP(v, mi, ma)
Yield v clamped to [mi … ma].
Definition internal.hh:60
#define assert_warn(expr)
Issue an assertion warning if expr evaluates to false.
Definition internal.hh:33
#define _(...)
Retrieve the translation of a C or C++ string.
Definition internal.hh:18
typedef int
size_t erase_first(C &container, const std::function< bool(typename C::value_type const &value)> &pred)
Erase first element for which pred() is true in vector or list.
Definition utils.hh:268
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...
String anklang_cachedir_create()
Create exclusive cache directory for this process' runtime.
Definition storage.cc:106
uint64_t uint64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:25
String string_to_hex(const String &input)
Convert bytes in string input to hexadecimal numbers.
Definition strings.cc:1171
bool json_parse(const String &jsonstring, T &target)
Parse a well formed JSON string and assign contents to target.
Definition serialize.hh:538
std::tuple< double, double, double > MinMaxStep
Min, max, stepping for double ranges.
Definition parameter.hh:12
Error
Enum representing Error states.
Definition api.hh:22
const char * ase_error_blurb(Error error)
Describe Error condition.
Definition server.cc:227
std::string decodefs(const std::string &utf8str)
Decode UTF-8 string back into file system path representation, extracting surrogate code points as by...
Definition unicode.cc:131
void anklang_cachedir_clean_stale()
Clean stale cache directories from past runtimes, may be called from any thread.
Definition storage.cc:161
String program_alias()
Retrieve the program name as used for logging or debug messages.
Definition platform.cc:817
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
constexpr const char STANDARD[]
STORAGE GUI READABLE WRITABLE.
Definition api.hh:14
bool string_endswith(const String &string, const String &fragment)
Returns whether string ends with fragment.
Definition strings.cc:863
void anklang_cachedir_cleanup(const String &cachedir)
Cleanup a cachedir previously created with anklang_cachedir_create().
Definition storage.cc:142
std::string encodefs(const std::string &fschars)
Encode a file system path consisting of bytes into UTF-8, using surrogate code points to store non UT...
Definition unicode.cc:112
String json_stringify(const T &source, Writ::Flags flags=Writ::Flags(0))
Create JSON string from source.
Definition serialize.hh:530
Info for device types.
Definition api.hh:203
T pop_back(T... args)
T push_back(T... args)
T resize(T... args)
T size(T... args)
Transport information for AudioSignal processing.
Definition transport.hh:113
int32 current_bar
Bar of current_tick position.
Definition transport.hh:126
double current_semiquaver
The sixteenth with fraction within beat.
Definition transport.hh:128
double current_seconds
Seconds of current_tick position.
Definition transport.hh:131
int8 current_beat
Beat within bar of current_tick position.
Definition transport.hh:127
float current_bpm
Running tempo in beats per minute.
Definition transport.hh:129
int32 current_minutes
Minute of current_tick position.
Definition transport.hh:130
bool set_signature(uint8 beats_per_bar, uint8 beat_unit)
Assign time signature and offset for the signature to take effect.
Definition transport.cc:95
void set_bpm(double bpm)
Assign tempo in beats per minute.
Definition transport.cc:59
T swap(T... args)
typedef ssize_t