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->add ([
this] () {
76 autoplay_timer_ = LoopID::INVALID;
93ProjectImpl::~ProjectImpl()
95 main_loop->cancel (&autoplay_timer_);
100ProjectImpl::force_shutdown_all ()
107 ProjectImplP project = ProjectImpl::make_shared();
108 all_projects.push_back (project);
114ProjectImpl::discard ()
118 const size_t nerased = Aux::erase_first (all_projects, [
this] (
auto ptr) {
return ptr.get() ==
this; });
125ProjectImpl::_activate ()
128 DeviceImpl::_activate();
129 for (
auto &track : tracks_)
134ProjectImpl::_deactivate ()
138 (*trackit)->_deactivate();
139 DeviceImpl::_deactivate();
143is_anklang_dir (
const String &path)
145 return Path::check (Path::join (path,
".anklang.project"),
"r");
149find_anklang_parent_dir (
const String &path)
151 for (String p = path; !p.empty() && !Path::isroot (p); p = Path::dirname (p))
152 if (is_anklang_dir (p))
158make_anklang_dir (
const String &path)
160 String mime = Path::join (path,
".anklang.project");
161 return Path::stringwrite (
mime,
"# ANKLANG(1) project directory\n");
173 if (path.
back() ==
'/' ||
174 Path::check (path,
"d"))
175 return Error::FILE_IS_DIR;
180 if (Path::check (path,
"e"))
183 if (!is_anklang_dir (
dir))
184 return Error::NO_PROJECT_DIR;
200 if (!Path::mkdirs (path))
201 return ase_error_from_errno (
errno);
203 if (!make_anklang_dir (path))
204 return ase_error_from_errno (
errno);
205 storage_->anklang_dir = path;
217 ASE_SERVER.user_note (
string_format (
"## Backup failed\n%s: \\\nFailed to create backup: \\\n%s",
223 strings_version_sort (&
backups,
true);
236 storage_->asset_hashes.clear();
244 error = ws.store_file_data (
"project.json",
jsd,
true);
247 for (
const auto &[path,
dest] : storage_->writer_files) {
248 error = ws.store_file (
dest, path);
254 storage_->writer_files.clear();
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();
284ProjectImpl::writer_file_name (
const String &
fspath)
const
288 return Path::join (storage_->writer_cachedir,
fspath);
292ProjectImpl::writer_add_file (
const String &
fspath)
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) });
305ProjectImpl::writer_collect (
const String &
fspath, String *
hexhashp)
308 assert_return (!storage_->anklang_dir.empty(), Error::INTERNAL);
309 if (!Path::check (
fspath,
"fr"))
310 return Error::FILE_NOT_FOUND;
314 return ase_error_from_errno (errno ? errno :
EIO);
316 for (
const auto &
hf : storage_->asset_hashes)
324 if (Path::dircontains (storage_->anklang_dir,
fspath, &
relpath))
336 while (Path::check (
dest,
"e"))
338 if (file_size == Path::file_size (
dest))
350 const StringPair
parts = Path::split_extension (
relpath,
true);
354 if (!Path::mkdirs (Path::dirname (
dest)))
355 return ase_error_from_errno (errno);
359 return ase_error_from_errno (errno);
367ProjectImpl::saved_filename ()
380 if (Path::basename (
fname) ==
".anklang.project" && is_anklang_dir (Path::dirname (
fname)))
383 if (Path::check (
fname,
"d"))
384 fname = Path::join (
fname, Path::basename (Path::strip_slashes (Path::normalize (
fname)))) +
".anklang";
386 if (!Path::check (
fname,
"e"))
389 if (!Path::check (
fname,
"e"))
390 return ase_error_from_errno (
errno);
396 if (
rs.stringread (
"mimetype") !=
"application/x-anklang")
397 return Error::BAD_PROJECT;
401 return Error::FORMAT_INVALID;
402 storage_->loading_file =
fname;
403 storage_->anklang_dir = find_anklang_parent_dir (storage_->loading_file);
418 return Error::PARSE_ERROR;
419 saved_filename_ = storage_->loading_file;
428 return stream_reader_zip_member (storage_->loading_file,
fspath);
435 return_unless (storage_ && storage_->asset_hashes.size(),
"");
437 for (
const auto& [
hash,
relpath] : storage_->asset_hashes)
439 return Path::join (storage_->anklang_dir,
relpath);
447 if (
xs.in_load() && storage_ && storage_->asset_hashes.empty())
448 xs[
"filehashes"] & storage_->asset_hashes;
450 DeviceImpl::serialize (
xs);
453 for (
auto &
xc :
xs[
"tracks"].to_nodes())
456 if (!
xc[
"mastertrack"].as_int())
463 for (
auto &
trackp : tracks_)
467 if (
trackp == tracks_.back())
468 xc.front (
"mastertrack") <<
true;
471 if (storage_ && storage_->asset_hashes.size())
472 xs[
"filehashes"] & storage_->asset_hashes;
482 warning (
"Project: failed to serialize project: %s\n",
ase_error_blurb (error));
495UndoScope::~UndoScope()
498 projectp_->undo_scopes_open_--;
502UndoScope::operator+= (
const VoidF &func)
504 projectp_->push_undo (func);
526 if (undo_scopes_open_ == 1 && (undo_groups_open_ == 0 || undo_group_name_.
size()))
529 undo_group_name_ =
"";
535ProjectImpl::push_undo (
const VoidF &func)
538 if (undostack_.
size() == 1)
545 assert_return (undo_scopes_open_ == 0 && undo_groups_open_ == 0);
548 while (!undostack_.
empty() && undostack_.
back().func)
550 funcs.push_back (undostack_.
back().func);
559 undostack_.
swap (redostack_);
562 for (
const auto &func :
funcs)
565 undostack_.
swap (redostack_);
573 return undostack_.
size() > 0;
579 assert_return (undo_scopes_open_ == 0 && undo_groups_open_ == 0);
582 while (!redostack_.
empty() && redostack_.
back().func)
584 funcs.push_back (redostack_.
back().func);
595 for (
const auto &func :
funcs)
605 return redostack_.
size() > 0;
613 if (undo_groups_open_ == 1)
628 if (!undo_groups_open_)
629 undo_group_name_ =
"";
633ProjectImpl::clear_undo ()
635 assert_warn (undo_scopes_open_ == 0 && undo_groups_open_ == 0);
641size_t ProjectImpl::undo_mem_counter = 0;
644ProjectImpl::undo_size_guess ()
const
646 size_t count = undostack_.
size();
647 count += redostack_.
size();
648 size_t item =
sizeof (UndoFunc);
651 return count *
item + undo_mem_counter;
658 AudioProcessorP proc = master_processor ();
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));
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));
672ProjectImpl::master_processor ()
const
679 AudioProcessorP proc =
device->_audio_processor();
685ProjectImpl::set_bpm (
double newbpm)
688 if (tick_sig_.bpm() !=
nbpm) {
692 if (
newbpm != tick_sig_.bpm())
697ProjectImpl::get_bpm ()
const
699 return tick_sig_.bpm();
703ProjectImpl::set_numerator (
double num)
713ProjectImpl::get_numerator ()
const
715 return tick_sig_.beats_per_bar();
719ProjectImpl::set_denominator (
double den)
721 const bool changed = tick_sig_.
set_signature (tick_sig_.beats_per_bar(),
den);
724 denominator.notify();
729ProjectImpl::get_denominator ()
const
731 return tick_sig_.beat_unit();
735ProjectImpl::update_tempo ()
737 AudioProcessorP proc = master_processor();
739 const TickSignature
tsig (tick_sig_);
741 AudioTransport &transport =
const_cast<AudioTransport&
> (proc->engine().transport());
742 transport.tempo (
tsig);
744 proc->engine().async_jobs +=
job;
752 AudioEngine &
engine = *App.engine;
759 oldp->stop_playback();
760 engine.set_project (
nullptr);
766 main_loop->cancel (&autoplay_timer_);
767 AudioProcessorP proc = master_processor();
770 for (
auto track : tracks_)
771 track->queue_cmd (*
queuep, track->START);
772 const TickSignature
tsig (tick_sig_);
774 AudioEngine &
engine = proc->engine();
775 const double udmax = 18446744073709549568.0;
778 AudioTransport &transport =
const_cast<AudioTransport&
> (
engine.transport());
779 transport.tempo (
tsig);
780 transport.running (
true);
784 proc->engine().async_jobs +=
job;
790 main_loop->cancel (&autoplay_timer_);
791 AudioProcessorP proc = master_processor();
794 for (
auto track : tracks_)
799 transport.running (
false);
801 transport.set_tick (-AUDIO_BLOCK_MAX_RENDER_SIZE / 2 * transport.tick_sig.ticks_per_sample());
805 transport.set_tick (0);
807 proc->engine().async_jobs +=
job;
813 AudioProcessorP proc = master_processor();
815 return proc->engine().transport().current_bpm > 0.0;
825 emit_event (
"track",
"insert", { {
"track", track }, });
826 track->_set_parent (
this);
841 track->_set_parent (
nullptr);
842 emit_event (
"track",
"remove");
856ProjectImpl::track_index (
const Track &
child)
const
858 for (
size_t i = 0; i < tracks_.
size(); i++)
859 if (&
child == tracks_[i].get())
868 return tracks_.
back();
884ProjectImpl::_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.