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.