2#include "trkn/tracktion.hh"
18#define UDEBUG(...) Ase::debug ("undo", __VA_ARGS__)
25static Preference synth_latency_pref =
27 "project.default_license",
_(
"Default License"),
"",
28 "CC-BY-SA-4.0 - https://creativecommons.org/licenses/by-sa/4.0/legalcode",
31 String (
"descr=") +
_(
"Default LICENSE to apply in the project properties."), } });
36Project::last_project()
38 return g_projects.empty() ?
nullptr : g_projects.back();
44 tracktion::TransportControl &transport;
47 LoopID ppt = LoopID::INVALID;
53 int fps = 0, frame = 0;
54 int bar = 0, beat = 0;
55 int sxth = 0, tick = 0;
56 int snum = 0, sden = 0;
57 double bpm = 0, sec = 0;
61 transport (
tc), project_ (project),
63 edit_ (project.edit_.get()),
64 pos (*new (transport_block_.block_start)
Position{})
67 transport.addChangeListener (
this);
68 transport.addListener (
this);
77 transport.removeListener (
this);
78 transport.removeChangeListener (
this);
79 SERVER->telemem_release (transport_block_);
85 if (
id == tracktion_engine::IDs::name &&
vtree == project_.edit_->state)
87 if (
id == tracktion_engine::IDs::bpm)
89 if (
id == tracktion_engine::IDs::numerator)
91 if (
id == tracktion_engine::IDs::denominator)
93 if ((te::Clip::isClipState (
vtree) && (
id == te::IDs::start ||
id == te::IDs::length)) ||
94 vtree.hasType (te::IDs::TEMPO) ||
vtree.hasType (te::IDs::TIMESIG))
100 if (parent == edit_->
state && te::TrackList::isTrack (
child))
102 if (te::TrackList::isTrack (
child) || te::Clip::isClipState (
child) || parent.
hasType (te::IDs::TEMPOSEQUENCE))
108 if (parent == edit_->
state && te::TrackList::isTrack (
child))
110 if (te::TrackList::isTrack (
child) || te::Clip::isClipState (
child) || parent.
hasType (te::IDs::TEMPOSEQUENCE))
124 if (source == &transport) {
125 transport_changed (
"change");
128 void autoSaveNow ()
override {}
130 void setVideoPosition (tracktion::TimePosition pos,
bool force_jump)
override {}
133 void recordingAboutToStart (tracktion::InputDeviceInstance &
device, tracktion::EditItemID target)
override {}
134 void recordingAboutToStop (tracktion::InputDeviceInstance &
device, tracktion::EditItemID target)
override {}
135 void recordingFinished (tracktion::InputDeviceInstance &
device, tracktion::EditItemID target,
138 playbackContextChanged ()
override
140 tracktion::EditPlaybackContext *context = transport.getCurrentPlaybackContext();
141 Ase::diag (
"PlaybackContextChanged: context=%p graph=%d playing=%d position=%.3fsecs\n", context,
142 context ? context->isPlaybackGraphAllocated() : 0,
143 context ? context->isPlaying() : 0,
144 context ? context->getPosition().inSeconds() : 0);
147 startVideo ()
override
151 if (ppt == LoopID::INVALID)
154 transport_changed (
"start-video");
157 stopVideo ()
override
160 main_loop->cancel (&ppt);
163 transport_changed (
"stop-video");
164 while (!stopped_callbacks_.empty()) {
165 const auto f = stopped_callbacks_.front();
166 stopped_callbacks_.pop_front();
173 if (transport.isPlaying())
174 stopped_callbacks_.push_back (f);
181 auto position = transport.getPosition();
182 Ase::printerr (
"Transport: playing=%d position=%.3fsecs (%s)\n",
183 transport.isPlaying(), position.inSeconds(), what.
c_str());
188 auto context = transport.getCurrentPlaybackContext();
191 auto &transport = project_.edit_->getTransport();
192 auto &
tempoSeq = project_.edit_->tempoSequence;
196 const tracktion::TimePosition
currentPos = transport.getPosition();
211 pos.beat =
bab.getWholeBeats();
228 pos.sden =
timesig.denominator;
232 pos.fps =
tdf.getFPS();
268 track->name (
"LiquidSFZTest");
275 ClipP clip =
trackimpl->create_midi_clip (
"NotesClip", start, duration);
279 auto add_note = [&] (
int start,
int key,
int duration)
283 note.note_number = key;
284 note.start_beat = start;
285 note.length_beats = duration;
292 clip->change_batch (
batch,
"Add Note");
297 auto plugin =
trackimpl->create_plugin (LiquidSFZPlugin::xmlTypeName);
305 transport.
setLoopRange({ tracktion::TimePosition::fromSeconds (start), tracktion::TimeDuration::fromSeconds (duration) });
306 transport.looping =
true;
307 transport.setPosition (tracktion::TimePosition::fromSeconds (start));
321ProjectImpl::ProjectImpl()
328 if (!edit_ || !transport_listener_)
329 fatal_error (
"failed to create tracktion::engine::edit");
331 if (
auto filename = getenv (
"SFZ"))
332 test_sfz (
this, edit_.get(), filename);
353ProjectImpl::deactivate_edit()
357 if (transport.isPlaying() || transport.isRecording())
358 transport.
stop (
true,
true);
359 transport.freePlaybackContext();
361 transport_listener_ =
nullptr;
365ProjectImpl::~ProjectImpl()
373ProjectImpl::force_shutdown_all ()
376 for (
size_t i = 0; i < g_projects.size(); i++)
377 if (g_projects[i]->edit_) {
378 g_projects[i]->deactivate_edit();
384ProjectImpl::name()
const
387 return edit_ ? edit_->
state.
getProperty (tracktion_engine::IDs::name).toString().toStdString() :
"";
404 v.push_back (telemetry_field (
"current_tick", &transport_listener_->pos.tick));
405 v.push_back (telemetry_field (
"current_bar", &transport_listener_->pos.bar));
406 v.push_back (telemetry_field (
"current_beat", &transport_listener_->pos.beat));
407 v.push_back (telemetry_field (
"current_sixteenth", &transport_listener_->pos.sxth));
408 v.push_back (telemetry_field (
"current_bpm", &transport_listener_->pos.bpm));
409 v.push_back (telemetry_field (
"current_numerator", &transport_listener_->pos.snum));
410 v.push_back (telemetry_field (
"current_denominator", &transport_listener_->pos.sden));
411 v.push_back (telemetry_field (
"current_minutes", &transport_listener_->pos.min));
412 v.push_back (telemetry_field (
"current_seconds", &transport_listener_->pos.sec));
437 ProjectImplP project = ProjectImpl::make_shared();
438 g_projects.push_back (project);
440 project->edit_->getUndoManager().clearUndoHistory();
463is_anklang_dir (
const String &path)
465 return Path::check (Path::join (path,
".anklang.project"),
"r");
469find_anklang_parent_dir (
const String &path)
472 if (is_anklang_dir (p))
478make_anklang_dir (
const String &path)
480 String mime = Path::join (path,
".anklang.project");
481 return Path::stringwrite (
mime,
"# ANKLANG(1) project directory\n");
493 if (path.
back() ==
'/' ||
495 return Error::FILE_IS_DIR;
503 if (!is_anklang_dir (
dir))
504 return Error::NO_PROJECT_DIR;
521 return ase_error_from_errno (
errno);
523 if (!make_anklang_dir (path))
524 return ase_error_from_errno (
errno);
525 storage_->anklang_dir = path;
537 ASE_SERVER.user_note (
string_format (
"## Backup failed\n%s: \\\nFailed to create backup: \\\n%s",
543 strings_version_sort (&
backups,
true);
556 storage_->asset_hashes.
clear();
562 error = ws.store_file_data (
"project.json",
"{}\n",
true);
565 for (
const auto &[path,
dest] : storage_->writer_files) {
566 error = ws.store_file (
dest, path);
572 storage_->writer_files.
clear();
590 if (storage_->writer_cachedir.
empty() || !
Path::check (storage_->writer_cachedir,
"d"))
591 return Error::NO_PROJECT_DIR;
592 storage_->anklang_dir = storage_->writer_cachedir;
593 storage_->asset_hashes.
clear();
602ProjectImpl::writer_file_name (
const String &
fspath)
const
606 return Path::join (storage_->writer_cachedir,
fspath);
615 return Error::FILE_NOT_FOUND;
617 return Error::FILE_OPEN_FAILED;
628 return Error::FILE_NOT_FOUND;
632 return ase_error_from_errno (errno ? errno :
EIO);
634 for (
const auto &
hf : storage_->asset_hashes)
668 const StringPair
parts = Path::split_extension (
relpath,
true);
673 return ase_error_from_errno (errno);
677 return ase_error_from_errno (errno);
708 return ase_error_from_errno (
errno);
714 if (
rs.stringread (
"mimetype") !=
"application/x-anklang")
715 return Error::BAD_PROJECT;
719 return Error::FORMAT_INVALID;
720 storage_->loading_file =
fname;
721 storage_->anklang_dir = find_anklang_parent_dir (storage_->loading_file);
736 saved_filename_ = storage_->loading_file;
745 return stream_reader_zip_member (storage_->loading_file,
fspath);
754 for (
const auto& [
hash,
relpath] : storage_->asset_hashes)
756 return Path::join (storage_->anklang_dir,
relpath);
766 warning (
"Project: failed to serialize project: %s\n",
ase_error_blurb (error));
781UndoScope::~UndoScope()
785 projectp_->edit_->getUndoManager().beginNewTransaction();
807 trkn_restore_parameter_values (*edit_);
825 trkn_restore_parameter_values (*edit_);
847 warning (
"Project.length is read-only");
853 return {
"all_tracks",
"length" };
871ProjectImpl::clear_undo ()
879ProjectImpl::bpm (
double newbpm)
885ProjectImpl::bpm ()
const
891ProjectImpl::numerator (
int num)
897ProjectImpl::numerator ()
const
903ProjectImpl::denominator (
int den)
909ProjectImpl::denominator ()
const
934 transport_listener_->run_when_stopped ([
this] {
936 edit_->
getTransport().setPosition (tracktion::TimePosition::fromSeconds (0.0));
937 transport_listener_->poll_position();
962 auto t = edit_->
insertNewAudioTrack (tracktion::TrackInsertPoint (
nullptr,
nullptr),
nullptr);
963 if (!t)
return nullptr;
965 while (trkn_has_track_name (*edit_,
"Track " +
juce::String (n)))
969 emit_event (
"track",
"insert", { {
"track", track }, });
978 auto tf = [&] (
Track &track,
int depth)
990 warning (
"Project.all_tracks is read-only");
void emit_notify(const String &detail) override
Emit notify:detail, multiple notifications maybe coalesced if a CoalesceNotifies instance exists.
void remove_self() override
Remove self from parent container.
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.
void undo() override
Undo the last project modification.
String match_serialized(const String ®ex, int group) override
Match regex against the serialized project state.
bool is_playing() const override
Check whether a project is currently playing (song sequencing).
void remove_self() override
Remove self from parent container.
bool can_undo() override
Check if any undo steps have been recorded.
StringS read_only_properties() const override
List x()/x (v) properties that this object can't change, their setters ignore the value or warn.
TelemetryFieldS telemetry() const override
Retrieve project telemetry locations.
TrackS all_tracks() const override
List all tracks, changes on notify:all_tracks.
void ungroup_undo() override
Stop merging undo steps.
String saved_filename() override
Retrieve UTF-8 filename for save or from load.
TrackP create_track() override
Create and append a new Track.
Error save_project(const String &utf8filename, bool collect) override
Store Project and collect external files.
String loader_resolve(const String &hexhash)
Find file from hash code, returns fspath.
void start_playback() override
Start playback of a project, requires active sound engine.
DeviceInfo device_info() override
Describe this Device type.
void discard() override
Discard project and associated resources.
Error load_project(const String &utf8filename) override
Load project from file filename.
double length() const override
End time of the last clip in seconds, changes on notify:length.
void pause_playback() override
Pause playback at the current position.
void stop_playback() override
Stop project playback.
static String grep(const String ®ex, const String &input, int group=0, Flags=DEFAULT)
Find regex in input and return matching string.
Container for Clip objects and sequencing information.
void beginNewTransaction()
bool hasType(const Identifier &typeName) const noexcept
ValueTree getChild(int index) const
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
void addListener(Listener *listener)
const var & getProperty(const Identifier &name) const noexcept
void removeListener(Listener *listener)
TransportControl & getTransport() const noexcept
TimeDuration getLength() const
void visitAllTopLevelTracks(std::function< bool(Track &)>) const
MasterTrack * getMasterTrack() const
TempoSequence tempoSequence
juce::ReferenceCountedObjectPtr< AudioTrack > insertNewAudioTrack(TrackInsertPoint, SelectionManager *)
void visitAllTracksRecursive(std::function< bool(Track &)>) const
juce::UndoManager & getUndoManager() noexcept
void cancelAnyPendingUpdates()
TimeSigSetting * getTimeSig(int index) const
TempoSetting * getTempo(int index) const
void setBpm(double newBpm)
void setName(const juce::String &)
virtual bool isFolderTrack() const
juce::String getName() const override
virtual bool isAudioTrack() const
void ensureContextAllocated(bool alwaysReallocate=false)
bool isPlayContextActive() const
void setLoopRange(TimeRange)
void play(bool justSendMMCIfEnabled)
void stop(bool discardRecordings, bool clearDevices, bool canSendMMCStop=true)
#define ASE_CLASS_NON_COPYABLE(ClassName)
Delete copy ctor and assignment operator.
#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 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.
void glob(const String &pathpattern, StringS &dirs, StringS &files)
Create list with directories and filenames matching pathpattern with shell wildcards.
String basename(const String &path)
Strips all directory components from path and returns the resulting file name.
String dirname(const String &path)
Retrieve the directory part of the filename path.
bool mkdirs(const String &dirpath, uint mode)
Create the directories in dirpath with mode, check errno on false returns.
bool check(const String &file, const String &mode)
String strip_slashes(const String &path)
Strip trailing directory terminators.
String abspath(const String &path, const String &incwd)
void rmrf(const String &dir)
Recursively delete directory tree.
size_t file_size(const String &path)
Retrieve the on-disk size in bytes of path.
bool copy_file(const String &src, const String &dest)
Copy a file to a new non-existing location, sets errno and returns false on error.
bool isroot(const String &path, bool dos_drives)
Return wether path is an absolute pathname which identifies the root directory.
bool dircontains(const String &dirpath, const String &descendant, String *relpath)
Check if descendant belongs to the directory hierarchy under dirpath.
String normalize(const String &path)
Convert path to normal form.
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.
String string_to_hex(const String &input)
Convert bytes in string input to hexadecimal numbers.
void register_ase_obj(VirtualBase *ase_impl, tracktion::Selectable &selectable)
Helper: register AseImpl with a tracktion Selectable via ase_obj_.
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.
void unregister_ase_obj(VirtualBase *ase_impl, tracktion::Selectable *selectable)
Helper: unregister AseImpl from a tracktion Selectable (selectable may be nullptr)
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...
bool string_startswith(const String &string, const String &fragment)
Returns whether string starts with fragment.
juce::Array< AudioTrack * > getAudioTracks(const Edit &)
A Tracktion MIDI note with a session handle for edits.
Reference for an allocated memory block.