14#define UDEBUG(...) Ase::debug ("undo", __VA_ARGS__)
20static Preference synth_latency_pref =
22 "project.default_license",
_(
"Default License"),
"",
23 "CC-BY-SA-4.0 - https://creativecommons.org/licenses/by-sa/4.0/legalcode",
26 String (
"descr=") +
_(
"Default LICENSE to apply in the project properties."), } });
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" })
38Project::last_project()
40 return all_projects.empty() ?
nullptr : all_projects.back();
65ProjectImpl::ProjectImpl()
74 autoplay_timer_ = main_loop->exec_timer ([
this] () {
93ProjectImpl::~ProjectImpl()
95 main_loop->clear_source (&autoplay_timer_);
102 ProjectImplP project = ProjectImpl::make_shared();
103 all_projects.push_back (project);
109ProjectImpl::discard ()
113 const size_t nerased = Aux::erase_first (all_projects, [
this] (
auto ptr) {
return ptr.get() ==
this; });
120ProjectImpl::_activate ()
123 DeviceImpl::_activate();
124 for (
auto &
track : tracks_)
129ProjectImpl::_deactivate ()
133 (*trackit)->_deactivate();
134 DeviceImpl::_deactivate();
138is_anklang_dir (
const String &path)
140 return Path::check (Path::join (path,
".anklang.project"),
"r");
144find_anklang_parent_dir (
const String &path)
146 for (String p = path; !p.empty() && !Path::isroot (p); p = Path::dirname (p))
147 if (is_anklang_dir (p))
153make_anklang_dir (
const String &path)
155 String mime = Path::join (path,
".anklang.project");
156 return Path::stringwrite (
mime,
"# ANKLANG(1) project directory\n");
168 if (path.
back() ==
'/' ||
169 Path::check (path,
"d"))
170 return Error::FILE_IS_DIR;
175 if (Path::check (path,
"e"))
178 if (!is_anklang_dir (
dir))
179 return Error::NO_PROJECT_DIR;
195 if (!Path::mkdirs (path))
196 return ase_error_from_errno (
errno);
198 if (!make_anklang_dir (path))
199 return ase_error_from_errno (
errno);
200 storage_->anklang_dir = path;
212 ASE_SERVER.user_note (
string_format (
"## Backup failed\n%s: \\\nFailed to create backup: \\\n%s",
218 strings_version_sort (&
backups,
true);
231 storage_->asset_hashes.clear();
239 error = ws.store_file_data (
"project.json",
jsd,
true);
242 for (
const auto &[path,
dest] : storage_->writer_files) {
243 error = ws.store_file (
dest, path);
249 storage_->writer_files.clear();
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();
279ProjectImpl::writer_file_name (
const String &
fspath)
const
283 return Path::join (storage_->writer_cachedir,
fspath);
287ProjectImpl::writer_add_file (
const String &
fspath)
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) });
300ProjectImpl::writer_collect (
const String &
fspath, String *
hexhashp)
303 assert_return (!storage_->anklang_dir.empty(), Error::INTERNAL);
304 if (!Path::check (
fspath,
"fr"))
305 return Error::FILE_NOT_FOUND;
309 return ase_error_from_errno (errno ? errno :
EIO);
311 for (
const auto &
hf : storage_->asset_hashes)
319 if (Path::dircontains (storage_->anklang_dir,
fspath, &
relpath))
331 while (Path::check (
dest,
"e"))
333 if (file_size == Path::file_size (
dest))
345 const StringPair
parts = Path::split_extension (
relpath,
true);
349 if (!Path::mkdirs (Path::dirname (
dest)))
350 return ase_error_from_errno (errno);
354 return ase_error_from_errno (errno);
362ProjectImpl::saved_filename ()
375 if (Path::basename (
fname) ==
".anklang.project" && is_anklang_dir (Path::dirname (
fname)))
378 if (Path::check (
fname,
"d"))
379 fname = Path::join (
fname, Path::basename (Path::strip_slashes (Path::normalize (
fname)))) +
".anklang";
381 if (!Path::check (
fname,
"e"))
384 if (!Path::check (
fname,
"e"))
385 return ase_error_from_errno (
errno);
391 if (
rs.stringread (
"mimetype") !=
"application/x-anklang")
392 return Error::BAD_PROJECT;
396 return Error::FORMAT_INVALID;
397 storage_->loading_file =
fname;
398 storage_->anklang_dir = find_anklang_parent_dir (storage_->loading_file);
413 return Error::PARSE_ERROR;
414 saved_filename_ = storage_->loading_file;
423 return stream_reader_zip_member (storage_->loading_file,
fspath);
430 return_unless (storage_ && storage_->asset_hashes.size(),
"");
432 for (
const auto& [
hash,
relpath] : storage_->asset_hashes)
434 return Path::join (storage_->anklang_dir,
relpath);
442 if (
xs.in_load() && storage_ && storage_->asset_hashes.empty())
443 xs[
"filehashes"] & storage_->asset_hashes;
445 DeviceImpl::serialize (
xs);
448 for (
auto &
xc :
xs[
"tracks"].to_nodes())
451 if (!
xc[
"mastertrack"].as_int())
458 for (
auto &
trackp : tracks_)
462 if (
trackp == tracks_.back())
463 xc.front (
"mastertrack") <<
true;
466 if (storage_ && storage_->asset_hashes.size())
467 xs[
"filehashes"] & storage_->asset_hashes;
490UndoScope::~UndoScope()
493 projectp_->undo_scopes_open_--;
497UndoScope::operator+= (
const VoidF &func)
499 projectp_->push_undo (func);
521 if (undo_scopes_open_ == 1 && (undo_groups_open_ == 0 || undo_group_name_.
size()))
524 undo_group_name_ =
"";
530ProjectImpl::push_undo (
const VoidF &func)
533 if (undostack_.
size() == 1)
540 assert_return (undo_scopes_open_ == 0 && undo_groups_open_ == 0);
543 while (!undostack_.
empty() && undostack_.
back().func)
545 funcs.push_back (undostack_.
back().func);
554 undostack_.
swap (redostack_);
557 for (
const auto &func :
funcs)
560 undostack_.
swap (redostack_);
568 return undostack_.
size() > 0;
574 assert_return (undo_scopes_open_ == 0 && undo_groups_open_ == 0);
577 while (!redostack_.
empty() && redostack_.
back().func)
579 funcs.push_back (redostack_.
back().func);
590 for (
const auto &func :
funcs)
600 return redostack_.
size() > 0;
608 if (undo_groups_open_ == 1)
623 if (!undo_groups_open_)
624 undo_group_name_ =
"";
628ProjectImpl::clear_undo ()
630 assert_warn (undo_scopes_open_ == 0 && undo_groups_open_ == 0);
636size_t ProjectImpl::undo_mem_counter = 0;
639ProjectImpl::undo_size_guess ()
const
641 size_t count = undostack_.
size();
642 count += redostack_.
size();
643 size_t item =
sizeof (UndoFunc);
646 return count *
item + undo_mem_counter;
653 AudioProcessorP proc = master_processor ();
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));
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));
667ProjectImpl::master_processor ()
const
674 AudioProcessorP proc =
device->_audio_processor();
680ProjectImpl::set_bpm (
double newbpm)
683 if (tick_sig_.bpm() !=
nbpm) {
687 if (
newbpm != tick_sig_.bpm())
692ProjectImpl::get_bpm ()
const
694 return tick_sig_.bpm();
698ProjectImpl::set_numerator (
double num)
708ProjectImpl::get_numerator ()
const
710 return tick_sig_.beats_per_bar();
714ProjectImpl::set_denominator (
double den)
716 const bool changed = tick_sig_.
set_signature (tick_sig_.beats_per_bar(),
den);
719 denominator.notify();
724ProjectImpl::get_denominator ()
const
726 return tick_sig_.beat_unit();
730ProjectImpl::update_tempo ()
732 AudioProcessorP proc = master_processor();
734 const TickSignature
tsig (tick_sig_);
736 AudioTransport &transport =
const_cast<AudioTransport&
> (proc->engine().transport());
737 transport.tempo (
tsig);
739 proc->engine().async_jobs +=
job;
747 AudioEngine &engine = *App.engine;
749 ProjectImplP
oldp = engine.get_project();
754 oldp->stop_playback();
755 engine.set_project (
nullptr);
757 engine.set_project (
selfp);
761 main_loop->clear_source (&autoplay_timer_);
762 AudioProcessorP proc = master_processor();
765 for (
auto track : tracks_)
767 const TickSignature
tsig (tick_sig_);
769 AudioEngine &engine = proc->engine();
770 const double udmax = 18446744073709549568.0;
772 engine.set_autostop (s);
773 AudioTransport &transport =
const_cast<AudioTransport&
> (engine.transport());
774 transport.tempo (
tsig);
775 transport.running (
true);
779 proc->engine().async_jobs +=
job;
785 main_loop->clear_source (&autoplay_timer_);
786 AudioProcessorP proc = master_processor();
789 for (
auto track : tracks_)
794 transport.running (
false);
796 transport.set_tick (-AUDIO_BLOCK_MAX_RENDER_SIZE / 2 * transport.tick_sig.ticks_per_sample());
800 transport.set_tick (0);
802 proc->engine().async_jobs +=
job;
808 AudioProcessorP proc = master_processor();
810 return proc->engine().transport().current_bpm > 0.0;
820 emit_event (
"track",
"insert", { {
"track",
track }, });
821 track->_set_parent (
this);
836 track->_set_parent (
nullptr);
837 emit_event (
"track",
"remove");
851ProjectImpl::track_index (
const Track &
child)
const
853 for (
size_t i = 0; i < tracks_.
size(); i++)
854 if (&
child == tracks_[i].get())
863 return tracks_.
back();
879ProjectImpl::_set_event_source (AudioProcessorP
esource)
void emit_notify(const String &detail) override
Emit notify:detail, multiple notifications maybe coalesced if a CoalesceNotifies instance exists.
bool remove_track(Track &child) override
Remove a track owned by this Project.
void group_undo(const String &undoname) override
Merge upcoming undo steps.
void redo() override
Redo the last undo modification.
bool can_redo() override
Check if any redo steps have been recorded.
TrackP master_track() override
Retrieve the master track.
bool is_playing() override
Check whether a project is currently playing (song sequencing).
void undo() override
Undo the last project modification.
bool can_undo() override
Check if any undo steps have been recorded.
TelemetryFieldS telemetry() const override
Retrieve project telemetry locations.
TrackS all_tracks() override
List all tracks of the project.
void ungroup_undo() override
Stop merging undo steps.
TrackP create_track() override
Create and append a new Track.
AudioProcessorP _audio_processor() const override
Retrieve the corresponding AudioProcessor.
void start_playback() override
Start playback of a project, requires active sound engine.
DeviceInfo device_info() override
Describe this Device type.
void stop_playback() override
Stop project playback.
Container for Clip objects and sequencing information.
One entry in a Writ serialization document.
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
#define CLAMP(v, mi, ma)
Yield v clamped to [mi … ma].
#define assert_warn(expr)
Issue an assertion warning if expr evaluates to false.
#define _(...)
Retrieve the translation of a C or C++ string.
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.
The Anklang C++ API namespace.
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.
uint64_t uint64
A 64-bit unsigned integer, use PRI*64 in format strings.
String string_to_hex(const String &input)
Convert bytes in string input to hexadecimal numbers.
bool json_parse(const String &jsonstring, T &target)
Parse a well formed JSON string and assign contents to target.
std::tuple< double, double, double > MinMaxStep
Min, max, stepping for double ranges.
Error
Enum representing Error states.
const char * ase_error_blurb(Error error)
Describe Error condition.
std::string decodefs(const std::string &utf8str)
Decode UTF-8 string back into file system path representation, extracting surrogate code points as by...
void anklang_cachedir_clean_stale()
Clean stale cache directories from past runtimes, may be called from any thread.
String program_alias()
Retrieve the program name as used for logging or debug messages.
std::string String
Convenience alias for std::string.
constexpr const char STANDARD[]
STORAGE GUI READABLE WRITABLE.
bool string_endswith(const String &string, const String &fragment)
Returns whether string ends with fragment.
void anklang_cachedir_cleanup(const String &cachedir)
Cleanup a cachedir previously created with anklang_cachedir_create().
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...
String json_stringify(const T &source, Writ::Flags flags=Writ::Flags(0))
Create JSON string from source.
Transport information for AudioSignal processing.
int32 current_bar
Bar of current_tick position.
double current_semiquaver
The sixteenth with fraction within beat.
double current_seconds
Seconds of current_tick position.
int8 current_beat
Beat within bar of current_tick position.
float current_bpm
Running tempo in beats per minute.
int32 current_minutes
Minute of current_tick position.
bool set_signature(uint8 beats_per_bar, uint8 beat_unit)
Assign time signature and offset for the signature to take effect.
void set_bpm(double bpm)
Assign tempo in beats per minute.