Anklang 0.3.0-460-gc4ef46ba
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->exec_timer ([this] () {
75 return_unless (autoplay_timer_, false);
76 autoplay_timer_ = 0;
77 if (!is_playing())
78 start_playback();
79 return false;
80 }, 500);
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->clear_source (&autoplay_timer_);
96}
97
98
99ProjectImplP
100ProjectImpl::create (const String &projectname)
101{
102 ProjectImplP project = ProjectImpl::make_shared();
103 all_projects.push_back (project);
104 project->name (projectname);
105 return project;
106}
107
108void
109ProjectImpl::discard ()
110{
111 return_unless (!discarded_);
112 stop_playback();
113 const size_t nerased = Aux::erase_first (all_projects, [this] (auto ptr) { return ptr.get() == this; });
114 if (nerased)
115 {} // resource cleanups...
116 discarded_ = true;
117}
118
119void
120ProjectImpl::_activate ()
121{
122 assert_return (!is_active());
123 DeviceImpl::_activate();
124 for (auto &track : tracks_)
125 track->_activate();
126}
127
128void
129ProjectImpl::_deactivate ()
130{
131 assert_return (is_active());
132 for (auto trackit = tracks_.rbegin(); trackit != tracks_.rend(); ++trackit)
133 (*trackit)->_deactivate();
134 DeviceImpl::_deactivate();
135}
136
137static bool
138is_anklang_dir (const String &path)
139{
140 return Path::check (Path::join (path, ".anklang.project"), "r");
141}
142
143static String
144find_anklang_parent_dir (const String &path)
145{
146 for (String p = path; !p.empty() && !Path::isroot (p); p = Path::dirname (p))
147 if (is_anklang_dir (p))
148 return p;
149 return "";
150}
151
152static bool
153make_anklang_dir (const String &path)
154{
155 String mime = Path::join (path, ".anklang.project");
156 return Path::stringwrite (mime, "# ANKLANG(1) project directory\n");
157}
158
159Error
160ProjectImpl::save_project (const String &utf8filename, bool collect)
161{
163 assert_return (storage_ == nullptr, Error::OPERATION_BUSY);
164 PStorage storage (&storage_); // storage_ = &storage;
165 const String dotanklang = ".anklang";
166 String projectfile, path = Path::normalize (Path::abspath (savepath));
167 // check path is a file
168 if (path.back() == '/' ||
169 Path::check (path, "d")) // need file not directory
170 return Error::FILE_IS_DIR;
171 // force .anklang extension
172 if (!string_endswith (path, dotanklang))
173 path += dotanklang;
174 // existing files need proper project directories
175 if (Path::check (path, "e")) // existing file
176 {
177 const String dir = Path::dirname (path);
178 if (!is_anklang_dir (dir))
179 return Error::NO_PROJECT_DIR;
180 projectfile = Path::basename (path);
181 path = dir; // file inside project dir
182 }
183 else // new file name
184 {
185 projectfile = Path::basename (path);
186 const String parentdir = Path::dirname (path);
187 if (is_anklang_dir (parentdir))
188 path = parentdir;
189 else { // use projectfile stem as dir
190 assert_return (string_endswith (path, dotanklang), Error::INTERNAL);
191 path.resize (path.size() - dotanklang.size());
192 }
193 }
194 // create parent directory
195 if (!Path::mkdirs (path))
196 return ase_error_from_errno (errno);
197 // ensure path is_anklang_dir
198 if (!make_anklang_dir (path))
199 return ase_error_from_errno (errno);
200 storage_->anklang_dir = path;
201 const String abs_projectfile = Path::join (path, projectfile);
202 // create backups
203 if (Path::check (abs_projectfile, "e"))
204 {
205 const String backupdir = Path::join (path, "backup");
206 if (!Path::mkdirs (backupdir))
207 return ase_error_from_errno (errno ? errno : EPERM);
208 const StringPair parts = Path::split_extension (projectfile, true);
209 const String backupname = Path::join (backupdir, parts.first + now_strftime (" (%y%m%dT%H%M%S)") + parts.second);
210 const String backupglob = Path::join (backupdir, parts.first + " ([0-9]*[0-9]T[0-9]*[0-9])" + parts.second);
211 if (!Path::rename (abs_projectfile, backupname))
212 ASE_SERVER.user_note (string_format ("## Backup failed\n%s: \\\nFailed to create backup: \\\n%s",
213 backupname, ase_error_blurb (ase_error_from_errno (errno))));
214 else // successful backup, now prune
215 {
217 Path::glob (backupglob, backups);
218 strings_version_sort (&backups, true);
219 const int bmax = 24;
220 while (backups.size() > bmax)
221 {
222 const String bfile = backups.back();
223 backups.pop_back();
224 Path::rmrf (bfile);
225 }
226 }
227 }
228 // start writing
230 storage_->writer_cachedir = anklang_cachedir_create();
231 storage_->asset_hashes.clear();
232 StorageWriter ws (Storage::AUTO_ZSTD);
233 Error error = ws.open_with_mimetype (abs_projectfile, "application/x-anklang");
234 if (!error)
235 {
236 // serialize Project
237 String jsd = json_stringify (*this, Writ::RELAXED);
238 jsd += '\n';
239 error = ws.store_file_data ("project.json", jsd, true);
240 }
241 if (!error)
242 for (const auto &[path, dest] : storage_->writer_files) {
243 error = ws.store_file (dest, path);
244 if (!!error) {
245 printerr ("%s: %s: %s: %s\n", program_alias(), __func__, path, ase_error_blurb (error));
246 break;
247 }
248 }
249 storage_->writer_files.clear();
250 if (!error)
251 error = ws.close();
252 if (!error)
253 saved_filename_ = abs_projectfile;
254 if (!!error)
255 ws.remove_opened();
256 anklang_cachedir_cleanup (storage_->writer_cachedir);
257 return error;
258}
259
260Error
261ProjectImpl::snapshot_project (String &json)
262{
263 assert_return (storage_ == nullptr, Error::OPERATION_BUSY);
264 // writer setup
265 PStorage storage (&storage_); // storage_ = &storage;
266 storage_->writer_cachedir = anklang_cachedir_create();
267 if (storage_->writer_cachedir.empty() || !Path::check (storage_->writer_cachedir, "d"))
268 return Error::NO_PROJECT_DIR;
269 storage_->anklang_dir = storage_->writer_cachedir;
270 storage_->asset_hashes.clear();
271 // serialize Project
272 json = json_stringify (*this, Writ::RELAXED) + '\n';
273 // cleanup
274 anklang_cachedir_cleanup (storage_->writer_cachedir);
275 return Error::NONE;
276}
277
278String
279ProjectImpl::writer_file_name (const String &fspath) const
280{
281 assert_return (storage_ != nullptr, "");
282 assert_return (!storage_->writer_cachedir.empty(), "");
283 return Path::join (storage_->writer_cachedir, fspath);
284}
285
286Error
287ProjectImpl::writer_add_file (const String &fspath)
288{
289 assert_return (storage_ != nullptr, Error::INTERNAL);
290 assert_return (!storage_->writer_cachedir.empty(), Error::INTERNAL);
291 if (!Path::check (fspath, "frw"))
292 return Error::FILE_NOT_FOUND;
293 if (!string_startswith (fspath, storage_->writer_cachedir))
294 return Error::FILE_OPEN_FAILED;
295 storage_->writer_files.push_back ({ fspath, Path::basename (fspath) });
296 return Error::NONE;
297}
298
299Error
300ProjectImpl::writer_collect (const String &fspath, String *hexhashp)
301{
302 assert_return (storage_ != nullptr, Error::INTERNAL);
303 assert_return (!storage_->anklang_dir.empty(), Error::INTERNAL);
304 if (!Path::check (fspath, "fr"))
305 return Error::FILE_NOT_FOUND;
306 // determine hash of file to collect
307 const String hexhash = string_to_hex (blake3_hash_file (fspath));
308 if (hexhash.empty())
309 return ase_error_from_errno (errno ? errno : EIO);
310 // resolve against existing hashes
311 for (const auto &hf : storage_->asset_hashes)
312 if (std::get<0> (hf) == hexhash)
313 {
314 *hexhashp = hexhash;
315 return Error::NONE;
316 }
317 // file may be within project directory
319 if (Path::dircontains (storage_->anklang_dir, fspath, &relpath))
320 {
321 storage_->asset_hashes.push_back ({ hexhash, relpath });
322 *hexhashp = hexhash;
323 return Error::NONE;
324 }
325 // determine unique path name
326 const size_t file_size = Path::file_size (fspath);
327 const String basedir = storage_->anklang_dir;
328 relpath = Path::join ("samples", Path::basename (fspath));
329 String dest = Path::join (basedir, relpath);
330 size_t i = 0;
331 while (Path::check (dest, "e"))
332 {
333 if (file_size == Path::file_size (dest))
334 {
335 const String althash = string_to_hex (blake3_hash_file (dest));
336 if (althash == hexhash)
337 {
338 // found file with same hash within project directory
339 storage_->asset_hashes.push_back ({ hexhash, relpath });
340 *hexhashp = hexhash;
341 return Error::NONE;
342 }
343 }
344 // add counter to create unique name
345 const StringPair parts = Path::split_extension (relpath, true);
346 dest = Path::join (basedir, string_format ("%s(%u)%s", parts.first, ++i, parts.second));
347 }
348 // create parent dir
349 if (!Path::mkdirs (Path::dirname (dest)))
350 return ase_error_from_errno (errno);
351 // copy into project dir
352 const bool copied = Path::copy_file (fspath, dest);
353 if (!copied)
354 return ase_error_from_errno (errno);
355 // success
356 storage_->asset_hashes.push_back ({ hexhash, relpath });
357 *hexhashp = hexhash;
358 return Error::NONE;
359}
360
361String
362ProjectImpl::saved_filename ()
363{
364 return encodefs (saved_filename_);
365}
366
367Error
368ProjectImpl::load_project (const String &utf8filename)
369{
370 const String filename = decodefs (utf8filename);
371 assert_return (storage_ == nullptr, Error::OPERATION_BUSY);
372 PStorage storage (&storage_); // storage_ = &storage;
373 String fname = filename;
374 // turn /dir/.anklang.project -> /dir/
375 if (Path::basename (fname) == ".anklang.project" && is_anklang_dir (Path::dirname (fname)))
376 fname = Path::dirname (fname);
377 // turn /dir/ -> /dir/dir.anklang
378 if (Path::check (fname, "d"))
379 fname = Path::join (fname, Path::basename (Path::strip_slashes (Path::normalize (fname)))) + ".anklang";
380 // add missing '.anklang' extension
381 if (!Path::check (fname, "e"))
382 fname += ".anklang";
383 // check for readable file
384 if (!Path::check (fname, "e"))
385 return ase_error_from_errno (errno);
386 // try reading .anklang container
387 StorageReader rs (Storage::AUTO_ZSTD);
388 Error error = rs.open_for_reading (fname);
389 if (!!error)
390 return error;
391 if (rs.stringread ("mimetype") != "application/x-anklang")
392 return Error::BAD_PROJECT;
393 // find project.json *inside* container
394 String jsd = rs.stringread ("project.json");
395 if (jsd.empty() && errno)
396 return Error::FORMAT_INVALID;
397 storage_->loading_file = fname;
398 storage_->anklang_dir = find_anklang_parent_dir (storage_->loading_file);
399#if 0 // unimplemented
400 String dirname = Path::dirname (fname);
401 // search in dirname or dirname/..
402 if (is_anklang_dir (dirname))
403 rs.search_dir (dirname);
404 else
405 {
406 dirname = Path::dirname (dirname);
407 if (is_anklang_dir (dirname))
408 rs.search_dir (dirname);
409 }
410#endif
411 // parse project
412 if (!json_parse (jsd, *this))
413 return Error::PARSE_ERROR;
414 saved_filename_ = storage_->loading_file;
415 return Error::NONE;
416}
417
418StreamReaderP
419ProjectImpl::load_blob (const String &fspath)
420{
421 assert_return (storage_ != nullptr, nullptr);
422 assert_return (!storage_->loading_file.empty(), nullptr);
423 return stream_reader_zip_member (storage_->loading_file, fspath);
424}
425
427String
428ProjectImpl::loader_resolve (const String &hexhash)
429{
430 return_unless (storage_ && storage_->asset_hashes.size(), "");
431 return_unless (!storage_->anklang_dir.empty(), "");
432 for (const auto& [hash,relpath] : storage_->asset_hashes)
433 if (hexhash == hash)
434 return Path::join (storage_->anklang_dir, relpath);
435 return "";
436}
437
438void
439ProjectImpl::serialize (WritNode &xs)
440{
441 // provide asset_hashes early on
442 if (xs.in_load() && storage_ && storage_->asset_hashes.empty())
443 xs["filehashes"] & storage_->asset_hashes;
444 // serrialize children
445 DeviceImpl::serialize (xs);
446 // load tracks
447 if (xs.in_load())
448 for (auto &xc : xs["tracks"].to_nodes())
449 {
450 TrackImplP trackp = tracks_.back(); // master_track
451 if (!xc["mastertrack"].as_int())
452 trackp = shared_ptr_cast<TrackImpl> (create_track());
453 xc & *trackp;
454 }
455 // save tracks
456 if (xs.in_save())
457 {
458 for (auto &trackp : tracks_)
459 {
460 WritNode xc = xs["tracks"].push();
461 xc & *trackp;
462 if (trackp == tracks_.back()) // master_track
463 xc.front ("mastertrack") << true;
464 }
465 // store external reference hashes *after* all other objects
466 if (storage_ && storage_->asset_hashes.size())
467 xs["filehashes"] & storage_->asset_hashes;
468 }
469}
470
471String
472ProjectImpl::match_serialized (const String &regex, int group)
473{
474 String json;
475 Error error = snapshot_project (json);
476 if (!!error) {
477 log ("Project: failed to serialize project: %s\n", ase_error_blurb (error));
478 return "";
479 }
480 return Re::grep (regex, json, group);
481}
482
483UndoScope::UndoScope (ProjectImplP projectp) :
484 projectp_ (projectp)
485{
487 projectp->undo_scopes_open_++;
488}
489
490UndoScope::~UndoScope()
491{
492 assert_return (projectp_->undo_scopes_open_);
493 projectp_->undo_scopes_open_--;
494}
495
496void
497UndoScope::operator+= (const VoidF &func)
498{
499 projectp_->push_undo (func);
500}
501
502UndoScope
503ProjectImpl::undo_scope (const String &scopename)
504{
505 assert_warn (scopename != "");
506 const size_t old_undo = undostack_.size();
507 const size_t old_redo = redostack_.size();
508 UndoScope undoscope = add_undo_scope (scopename);
509 if (undostack_.size() > old_undo && redostack_.size())
510 redostack_.clear();
511 if ((!old_undo ^ !undostack_.size()) || (!old_redo ^ !redostack_.size()))
512 emit_notify ("dirty");
513 return undoscope;
514}
515
516UndoScope
517ProjectImpl::add_undo_scope (const String &scopename)
518{
519 UndoScope undoscope (shared_ptr_cast<ProjectImpl> (this)); // undo_scopes_open_ += 1
521 if (undo_scopes_open_ == 1 && (undo_groups_open_ == 0 || undo_group_name_.size()))
522 {
523 undostack_.push_back ({ nullptr, undo_group_name_.empty() ? scopename : undo_group_name_ });
524 undo_group_name_ = "";
525 }
526 return undoscope;
527}
528
529void
530ProjectImpl::push_undo (const VoidF &func)
531{
532 undostack_.push_back ({ func, "" });
533 if (undostack_.size() == 1)
534 emit_notify ("dirty");
535}
536
537void
539{
540 assert_return (undo_scopes_open_ == 0 && undo_groups_open_ == 0);
541 return_unless (!undostack_.empty());
543 while (!undostack_.empty() && undostack_.back().func)
544 {
545 funcs.push_back (undostack_.back().func);
546 undostack_.pop_back();
547 }
548 assert_return (!undostack_.empty() && undostack_.back().func == nullptr); // must contain scope name
549 const String scopename = undostack_.back().name;
550 UDEBUG ("Undo: steps=%d scope: %s\n", funcs.size(), scopename);
551 undostack_.pop_back(); // pop scope name
552 // swap undo/redo stacks, run undo steps and scope redo
553 const bool redostack_was_empty = redostack_.empty();
554 undostack_.swap (redostack_);
555 {
556 auto undoscope = add_undo_scope (scopename); // preserves redostack_
557 for (const auto &func : funcs)
558 func();
559 }
560 undostack_.swap (redostack_);
561 if (redostack_was_empty || undostack_.empty())
562 emit_notify ("dirty");
563}
564
565bool
567{
568 return undostack_.size() > 0;
569}
570
571void
573{
574 assert_return (undo_scopes_open_ == 0 && undo_groups_open_ == 0);
575 return_unless (!redostack_.empty());
577 while (!redostack_.empty() && redostack_.back().func)
578 {
579 funcs.push_back (redostack_.back().func);
580 redostack_.pop_back();
581 }
582 assert_return (!redostack_.empty() && redostack_.back().func == nullptr); // must contain scope name
583 const String scopename = redostack_.back().name;
584 UDEBUG ("Undo: steps=%d scope: %s\n", funcs.size(), scopename);
585 redostack_.pop_back(); // pop scope name
586 // run redo steps with undo scope
587 const bool undostack_was_empty = undostack_.empty();
588 {
589 auto undoscope = add_undo_scope (scopename); // preserves redostack_
590 for (const auto &func : funcs)
591 func();
592 }
593 if (undostack_was_empty || redostack_.empty())
594 emit_notify ("dirty");
595}
596
597bool
599{
600 return redostack_.size() > 0;
601}
602
603void
605{
606 assert_return (undoname != "");
607 undo_groups_open_++;
608 if (undo_groups_open_ == 1)
609 undo_group_name_ = undoname;
610 /* Opened undo groups cause:
611 * a) rename of the first opened undo scope
612 * b) merging of undo scopes
613 * c) block undo(), redo() calls
614 * We avoid group state tracking through IPC boundaries.
615 */
616}
617
618void
620{
621 assert_return (undo_groups_open_ > 0);
622 undo_groups_open_--;
623 if (!undo_groups_open_)
624 undo_group_name_ = "";
625}
626
627void
628ProjectImpl::clear_undo ()
629{
630 assert_warn (undo_scopes_open_ == 0 && undo_groups_open_ == 0);
631 undostack_.clear();
632 redostack_.clear();
633 emit_notify ("dirty");
634}
635
636size_t ProjectImpl::undo_mem_counter = 0;
637
638size_t
639ProjectImpl::undo_size_guess () const
640{
641 size_t count = undostack_.size();
642 count += redostack_.size();
643 size_t item = sizeof (UndoFunc);
644 item += sizeof (std::shared_ptr<void>); // undofunc selfp
645 item += 4 * sizeof (uint64); // undofunc arguments: double ClipNote struct
646 return count * item + undo_mem_counter;
647}
648
651{
653 AudioProcessorP proc = master_processor ();
654 assert_return (proc, v);
655 const AudioTransport &transport = proc->transport();
656 v.push_back (telemetry_field ("current_tick", &transport.current_tick_d));
657 v.push_back (telemetry_field ("current_bar", &transport.current_bar));
658 v.push_back (telemetry_field ("current_beat", &transport.current_beat));
659 v.push_back (telemetry_field ("current_sixteenth", &transport.current_semiquaver));
660 v.push_back (telemetry_field ("current_bpm", &transport.current_bpm));
661 v.push_back (telemetry_field ("current_minutes", &transport.current_minutes));
662 v.push_back (telemetry_field ("current_seconds", &transport.current_seconds));
663 return v;
664}
665
666AudioProcessorP
667ProjectImpl::master_processor () const
668{
669 return_unless (!tracks_.empty(), nullptr);
670 TrackP master = const_cast<ProjectImpl*> (this)->master_track();
671 return_unless (master, nullptr);
672 DeviceP device = master->access_device();
673 return_unless (device, nullptr);
674 AudioProcessorP proc = device->_audio_processor();
675 return_unless (proc, nullptr);
676 return proc;
677}
678
679void
680ProjectImpl::set_bpm (double newbpm)
681{
682 const double nbpm = CLAMP (newbpm, MIN_BPM, MAX_BPM);
683 if (tick_sig_.bpm() != nbpm) {
684 tick_sig_.set_bpm (nbpm);
685 update_tempo();
686 }
687 if (newbpm != tick_sig_.bpm())
688 bpm.notify();
689}
690
691double
692ProjectImpl::get_bpm () const
693{
694 return tick_sig_.bpm();
695}
696
697void
698ProjectImpl::set_numerator (double num)
699{
700 const bool changed = tick_sig_.set_signature (num, tick_sig_.beat_unit());
701 if (changed) {
702 update_tempo();
703 numerator.notify();
704 }
705}
706
707double
708ProjectImpl::get_numerator () const
709{
710 return tick_sig_.beats_per_bar();
711}
712
713void
714ProjectImpl::set_denominator (double den)
715{
716 const bool changed = tick_sig_.set_signature (tick_sig_.beats_per_bar(), den);
717 if (changed) {
718 update_tempo();
719 denominator.notify();
720 }
721}
722
723double
724ProjectImpl::get_denominator () const
725{
726 return tick_sig_.beat_unit();
727}
728
729void
730ProjectImpl::update_tempo ()
731{
732 AudioProcessorP proc = master_processor();
733 return_unless (proc);
734 const TickSignature tsig (tick_sig_);
735 auto job = [proc, tsig] () {
736 AudioTransport &transport = const_cast<AudioTransport&> (proc->engine().transport());
737 transport.tempo (tsig);
738 };
739 proc->engine().async_jobs += job;
740}
741
742void
744{
745 assert_return (!discarded_);
746 assert_return (App.engine);
747 AudioEngine &engine = *App.engine;
748 ProjectImplP selfp = shared_ptr_cast<ProjectImpl> (this); // keep alive while engine changes refcounts
749 ProjectImplP oldp = engine.get_project(); // keep alive while engine changes refs
750 if (oldp != selfp)
751 {
752 if (oldp)
753 {
754 oldp->stop_playback();
755 engine.set_project (nullptr);
756 }
757 engine.set_project (selfp);
758 }
759 assert_return (this == &*engine.get_project());
760
761 main_loop->clear_source (&autoplay_timer_);
762 AudioProcessorP proc = master_processor();
763 return_unless (proc);
765 for (auto track : tracks_)
766 track->queue_cmd (*queuep, track->START);
767 const TickSignature tsig (tick_sig_);
768 auto job = [proc, queuep, tsig, autostop] () {
769 AudioEngine &engine = proc->engine();
770 const double udmax = 18446744073709549568.0; // max double exactly matching an uint64_t
771 const uint64_t s = autostop > udmax ? udmax : autostop * engine.sample_rate();
772 engine.set_autostop (s);
773 AudioTransport &transport = const_cast<AudioTransport&> (engine.transport());
774 transport.tempo (tsig);
775 transport.running (true);
776 for (const auto &cmd : *queuep)
777 cmd();
778 };
779 proc->engine().async_jobs += job;
780}
781
782void
784{
785 main_loop->clear_source (&autoplay_timer_);
786 AudioProcessorP proc = master_processor();
787 return_unless (proc);
789 for (auto track : tracks_)
790 track->queue_cmd (*stop_queuep, track->STOP);
791 auto job = [proc, stop_queuep] () {
792 AudioTransport &transport = const_cast<AudioTransport&> (proc->engine().transport());
793 const bool wasrunning = transport.running();
794 transport.running (false);
795 if (!wasrunning)
796 transport.set_tick (-AUDIO_BLOCK_MAX_RENDER_SIZE / 2 * transport.tick_sig.ticks_per_sample());
797 for (const auto &stop : *stop_queuep)
798 stop (!wasrunning); // restart = !wasrunning
799 if (!wasrunning)
800 transport.set_tick (0); // adjust transport and track positions
801 };
802 proc->engine().async_jobs += job;
803}
804
805bool
807{
808 AudioProcessorP proc = master_processor();
809 return_unless (proc, false);
810 return proc->engine().transport().current_bpm > 0.0;
811}
812
813TrackP
815{
816 assert_return (!discarded_, nullptr);
817 const bool havemaster = tracks_.size() != 0;
818 TrackImplP track = TrackImpl::make_shared (*this, !havemaster);
819 tracks_.insert (tracks_.end() - int (havemaster), track);
820 emit_event ("track", "insert", { { "track", track }, });
821 track->_set_parent (this);
822 emit_notify ("all_tracks");
823 return track;
824}
825
826bool
828{
829 assert_return (child._parent() == this, false);
831 return_unless (track && !track->is_master(), false);
832 clear_undo(); // TODO: implement undo for remove_track
833 if (!Aux::erase_first (tracks_, [track] (TrackP t) { return t == track; }))
834 return false;
835 // destroy Track
836 track->_set_parent (nullptr);
837 emit_event ("track", "remove");
838 emit_notify ("all_tracks");
839 return true;
840}
841
842TrackS
844{
845 TrackS tracks (tracks_.size());
846 std::copy (tracks_.begin(), tracks_.end(), tracks.begin());
847 return tracks;
848}
849
851ProjectImpl::track_index (const Track &child) const
852{
853 for (size_t i = 0; i < tracks_.size(); i++)
854 if (&child == tracks_[i].get())
855 return i;
856 return -1;
857}
858
859TrackP
861{
862 assert_return (!tracks_.empty(), nullptr);
863 return tracks_.back();
864}
865
868{
869 return {}; // TODO: DeviceInfo
870}
871
872AudioProcessorP
874{
875 return {}; // TODO: AudioProcessorP
876}
877
878void
879ProjectImpl::_set_event_source (AudioProcessorP esource)
880{
881 // TODO: _set_event_source
882}
883
884} // 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:827
void group_undo(const String &undoname) override
Merge upcoming undo steps.
Definition project.cc:604
void redo() override
Redo the last undo modification.
Definition project.cc:572
bool can_redo() override
Check if any redo steps have been recorded.
Definition project.cc:598
TrackP master_track() override
Retrieve the master track.
Definition project.cc:860
bool is_playing() override
Check whether a project is currently playing (song sequencing).
Definition project.cc:806
void undo() override
Undo the last project modification.
Definition project.cc:538
bool can_undo() override
Check if any undo steps have been recorded.
Definition project.cc:566
TelemetryFieldS telemetry() const override
Retrieve project telemetry locations.
Definition project.cc:650
TrackS all_tracks() override
List all tracks of the project.
Definition project.cc:843
void ungroup_undo() override
Stop merging undo steps.
Definition project.cc:619
TrackP create_track() override
Create and append a new Track.
Definition project.cc:814
AudioProcessorP _audio_processor() const override
Retrieve the corresponding AudioProcessor.
Definition project.cc:873
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:867
void stop_playback() override
Stop project playback.
Definition project.cc:783
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:71
#define CLAMP(v, mi, ma)
Yield v clamped to [mi … ma].
Definition internal.hh:58
#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
log
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:337
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:849
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