2#include "trkn/tracktion.hh"
16#define UDEBUG(...) Ase::debug ("undo", __VA_ARGS__)
23static Preference synth_latency_pref =
25 "project.default_license",
_(
"Default License"),
"",
26 "CC-BY-SA-4.0 - https://creativecommons.org/licenses/by-sa/4.0/legalcode",
29 String (
"descr=") +
_(
"Default LICENSE to apply in the project properties."), } });
38Project::last_project()
40 return g_projects.empty() ?
nullptr : g_projects.back();
46 tracktion::TransportControl &transport;
49 LoopID ppt = LoopID::INVALID;
55 int fps = 0, frame = 0;
56 int bar = 0, beat = 0;
57 int sxth = 0, tick = 0;
58 int snum = 0, sden = 0;
59 double bpm = 0, sec = 0;
63 transport (
tc), project_ (project),
65 edit_ (project.edit_.get()),
66 pos (*new (transport_block_.block_start)
Position{})
69 transport.addChangeListener (
this);
70 transport.addListener (
this);
79 transport.removeListener (
this);
80 transport.removeChangeListener (
this);
81 SERVER->telemem_release (transport_block_);
87 if (
id == tracktion_engine::IDs::name)
89 if (
id == tracktion_engine::IDs::bpm)
91 if (
id == tracktion_engine::IDs::numerator)
93 if (
id == tracktion_engine::IDs::denominator)
95 if (
id == tracktion_engine::IDs::volume) {
96 auto mvp = project_.edit_->getMasterVolumePlugin();
103 void valueTreeChildOrderChanged (
juce::ValueTree&,
int,
int)
override {}
108 if (source == &transport) {
109 transport_changed (
"change");
112 void autoSaveNow ()
override {}
114 void setVideoPosition (tracktion::TimePosition pos,
bool force_jump)
override {}
117 void recordingAboutToStart (tracktion::InputDeviceInstance &
device, tracktion::EditItemID target)
override {}
118 void recordingAboutToStop (tracktion::InputDeviceInstance &
device, tracktion::EditItemID target)
override {}
119 void recordingFinished (tracktion::InputDeviceInstance &
device, tracktion::EditItemID target,
122 playbackContextChanged ()
override
124 tracktion::EditPlaybackContext *context = transport.getCurrentPlaybackContext();
125 Ase::diag (
"PlaybackContextChanged: context=%p graph=%d playing=%d position=%.3fsecs\n", context,
126 context ? context->isPlaybackGraphAllocated() : 0,
127 context ? context->isPlaying() : 0,
128 context ? context->getPosition().inSeconds() : 0);
131 startVideo ()
override
135 if (ppt == LoopID::INVALID)
138 transport_changed (
"start-video");
141 stopVideo ()
override
144 main_loop->cancel (&ppt);
147 transport_changed (
"stop-video");
148 while (!stopped_callbacks_.empty()) {
149 const auto f = stopped_callbacks_.front();
150 stopped_callbacks_.pop_front();
157 if (transport.isPlaying())
158 stopped_callbacks_.push_back (f);
165 auto position = transport.getPosition();
166 Ase::printerr (
"Transport: playing=%d position=%.3fsecs (%s)\n",
167 transport.isPlaying(), position.inSeconds(), what.
c_str());
172 auto context = transport.getCurrentPlaybackContext();
175 auto &transport = project_.edit_->getTransport();
176 auto &
tempoSeq = project_.edit_->tempoSequence;
180 const tracktion::TimePosition
currentPos = transport.getPosition();
195 pos.beat =
bab.getWholeBeats();
212 pos.sden =
timesig.denominator;
216 pos.fps =
tdf.getFPS();
247static tracktion::WaveAudioClip::Ptr
248load_audio_file_as_clip (tracktion::Edit &edit,
const juce::File &file)
250 edit.ensureNumberOfAudioTracks (1);
251 if (
auto track = tracktion::getAudioTracks (edit)[0]) {
252 tracktion::AudioFile
audioFile (edit.engine, file);
256 { { {}, tracktion::TimeDuration::fromSeconds (audioFile.getLength()) }, {} },
false))
262template<
typename ClipType>
static typename ClipType::Ptr
266 auto &transport = clip.edit.getTransport();
267 transport.setLoopRange (clip.getEditTimeRange());
268 transport.looping =
true;
269 transport.setPosition (0s);
274test_setup (tracktion::Edit &edit)
278 auto clip = load_audio_file_as_clip (edit,
sampleFile);
280 loop_around_clip (*clip);
281 edit.getTransport().ensureContextAllocated();
285ProjectImpl::ProjectImpl()
293 if (!edit_ || !transport_listener_ || !test_setup (*edit_))
294 fatal_error (
"failed to create tracktion::engine::edit");
310ProjectImpl::deactivate_edit()
314 if (transport.isPlaying() || transport.isRecording())
315 transport.
stop (
true,
true);
316 transport.freePlaybackContext();
321ProjectImpl::~ProjectImpl()
324 transport_listener_ =
nullptr;
330ProjectImpl::force_shutdown_all ()
333 for (
size_t i = 0; i < g_projects.size(); i++)
334 if (g_projects[i]->edit_) {
335 g_projects[i]->deactivate_edit();
341ProjectImpl::name()
const
344 return edit_ ? edit_->
state.
getProperty (tracktion_engine::IDs::name).toString().toStdString() :
"";
356ProjectImpl::telemetry ()
const
359 v.push_back (telemetry_field (
"current_tick", &transport_listener_->pos.tick));
360 v.push_back (telemetry_field (
"current_bar", &transport_listener_->pos.bar));
361 v.push_back (telemetry_field (
"current_beat", &transport_listener_->pos.beat));
362 v.push_back (telemetry_field (
"current_sixteenth", &transport_listener_->pos.sxth));
363 v.push_back (telemetry_field (
"current_bpm", &transport_listener_->pos.bpm));
364 v.push_back (telemetry_field (
"current_numerator", &transport_listener_->pos.snum));
365 v.push_back (telemetry_field (
"current_denominator", &transport_listener_->pos.sden));
366 v.push_back (telemetry_field (
"current_minutes", &transport_listener_->pos.min));
367 v.push_back (telemetry_field (
"current_seconds", &transport_listener_->pos.sec));
392 ProjectImplP project = ProjectImpl::make_shared();
393 g_projects.push_back (project);
395 project->edit_->getUndoManager().clearUndoHistory();
400ProjectImpl::discard ()
404 const size_t nerased = Aux::erase_first (g_projects, [
this] (
auto ptr) {
return ptr.get() ==
this; });
411ProjectImpl::_activate ()
414 DeviceImpl::_activate();
419ProjectImpl::_deactivate ()
423 DeviceImpl::_deactivate();
427is_anklang_dir (
const String &path)
429 return Path::check (Path::join (path,
".anklang.project"),
"r");
433find_anklang_parent_dir (
const String &path)
435 for (String p = path; !p.empty() && !Path::isroot (p); p = Path::dirname (p))
436 if (is_anklang_dir (p))
442make_anklang_dir (
const String &path)
444 String mime = Path::join (path,
".anklang.project");
445 return Path::stringwrite (
mime,
"# ANKLANG(1) project directory\n");
457 if (path.
back() ==
'/' ||
458 Path::check (path,
"d"))
459 return Error::FILE_IS_DIR;
464 if (Path::check (path,
"e"))
467 if (!is_anklang_dir (
dir))
468 return Error::NO_PROJECT_DIR;
484 if (!Path::mkdirs (path))
485 return ase_error_from_errno (
errno);
487 if (!make_anklang_dir (path))
488 return ase_error_from_errno (
errno);
489 storage_->anklang_dir = path;
501 ASE_SERVER.user_note (
string_format (
"## Backup failed\n%s: \\\nFailed to create backup: \\\n%s",
507 strings_version_sort (&
backups,
true);
520 storage_->asset_hashes.clear();
528 error = ws.store_file_data (
"project.json",
jsd,
true);
531 for (
const auto &[path,
dest] : storage_->writer_files) {
532 error = ws.store_file (
dest, path);
538 storage_->writer_files.clear();
556 if (storage_->writer_cachedir.empty() || !Path::check (storage_->writer_cachedir,
"d"))
557 return Error::NO_PROJECT_DIR;
558 storage_->anklang_dir = storage_->writer_cachedir;
559 storage_->asset_hashes.clear();
568ProjectImpl::writer_file_name (
const String &
fspath)
const
572 return Path::join (storage_->writer_cachedir,
fspath);
576ProjectImpl::writer_add_file (
const String &
fspath)
579 assert_return (!storage_->writer_cachedir.empty(), Error::INTERNAL);
580 if (!Path::check (
fspath,
"frw"))
581 return Error::FILE_NOT_FOUND;
582 if (!string_startswith (
fspath, storage_->writer_cachedir))
583 return Error::FILE_OPEN_FAILED;
584 storage_->writer_files.push_back ({
fspath, Path::basename (
fspath) });
589ProjectImpl::writer_collect (
const String &
fspath, String *
hexhashp)
592 assert_return (!storage_->anklang_dir.empty(), Error::INTERNAL);
593 if (!Path::check (
fspath,
"fr"))
594 return Error::FILE_NOT_FOUND;
598 return ase_error_from_errno (errno ? errno :
EIO);
600 for (
const auto &
hf : storage_->asset_hashes)
608 if (Path::dircontains (storage_->anklang_dir,
fspath, &
relpath))
620 while (Path::check (
dest,
"e"))
622 if (file_size == Path::file_size (
dest))
634 const StringPair
parts = Path::split_extension (
relpath,
true);
638 if (!Path::mkdirs (Path::dirname (
dest)))
639 return ase_error_from_errno (errno);
643 return ase_error_from_errno (errno);
651ProjectImpl::saved_filename ()
664 if (Path::basename (
fname) ==
".anklang.project" && is_anklang_dir (Path::dirname (
fname)))
667 if (Path::check (
fname,
"d"))
668 fname = Path::join (
fname, Path::basename (Path::strip_slashes (Path::normalize (
fname)))) +
".anklang";
670 if (!Path::check (
fname,
"e"))
673 if (!Path::check (
fname,
"e"))
674 return ase_error_from_errno (
errno);
680 if (
rs.stringread (
"mimetype") !=
"application/x-anklang")
681 return Error::BAD_PROJECT;
685 return Error::FORMAT_INVALID;
686 storage_->loading_file =
fname;
687 storage_->anklang_dir = find_anklang_parent_dir (storage_->loading_file);
702 return Error::PARSE_ERROR;
703 saved_filename_ = storage_->loading_file;
712 return stream_reader_zip_member (storage_->loading_file,
fspath);
719 return_unless (storage_ && storage_->asset_hashes.size(),
"");
721 for (
const auto& [
hash,
relpath] : storage_->asset_hashes)
723 return Path::join (storage_->anklang_dir,
relpath);
739 warning (
"Project: failed to serialize project: %s\n",
ase_error_blurb (error));
754UndoScope::~UndoScope()
758 projectp_->edit_->getUndoManager().beginNewTransaction();
821 return te::volumeFaderPositionToDB (
volPlugin->volume.get());
830 const float sliderPos = te::decibelsToVolumeFaderPosition (
db);
832 volPlugin->volParam->updateFromAttachedValue();
850ProjectImpl::clear_undo ()
858ProjectImpl::bpm (
double newbpm)
863 auto *tempo =
tempoSeq.getTempo (0);
864 if (tempo && tempo->getBpm() !=
nbpm)
865 tempo->setBpm (
nbpm);
869ProjectImpl::bpm ()
const
873 return tempo ? tempo->
getBpm() : 120.0;
877ProjectImpl::numerator (
double num)
881 auto *timeSig =
tempoSeq.getTimeSig (0);
882 if (timeSig && timeSig->numerator !=
num)
883 timeSig->numerator =
num;
887ProjectImpl::numerator ()
const
891 return timeSig ? timeSig->numerator : 4.0;
895ProjectImpl::denominator (
double den)
899 auto *timeSig =
tempoSeq.getTimeSig (0);
900 if (timeSig && timeSig->denominator !=
den)
901 timeSig->denominator =
den;
905ProjectImpl::denominator ()
const
909 return timeSig ? timeSig->denominator : 4.0;
932 transport_listener_->run_when_stopped ([
this] {
934 edit_->
getTransport().setPosition (tracktion::TimePosition::fromSeconds (0.0));
935 transport_listener_->poll_position();
960 auto t = edit_->
insertNewAudioTrack (tracktion::TrackInsertPoint (
nullptr,
nullptr),
nullptr);
961 if (!t)
return nullptr;
963 emit_event (
"track",
"insert", { {
"track", track }, });
975 track->_set_parent (
nullptr);
976 emit_event (
"track",
"remove");
985 auto tf = [&] (
Track &track,
int depth)
995ProjectImpl::track_index (
const Track &
child)
const
999 auto tf = [&] (
Track &track,
int depth)
1001 if (&track == &
child)
1009 const_cast<ProjectImpl*
> (
this)->foreach_track (tf);
1014ProjectImpl::bar_ticks ()
const
1018 auto *timeSig =
tempoSeq.getTimeSig (0);
1022 const int beats_per_bar = timeSig->numerator;
1023 const int beat_unit = timeSig->denominator;
1027 const int64 SEMIQUAVER_TICKS = 1209600;
1031 return beat_ticks * beats_per_bar;
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.
void undo() override
Undo the last project modification.
bool is_playing() const override
Check whether a project is currently playing (song sequencing).
bool can_undo() override
Check if any undo steps have been recorded.
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.
double master_volume() const override
Get master volume in dB.
void start_playback() override
Start playback of a project, requires active sound engine.
DeviceInfo device_info() override
Describe this Device type.
double length() const override
Get the end time of the last clip in seconds.
void pause_playback() override
Pause playback at the current position.
void stop_playback() override
Stop project playback.
Container for Clip objects and sequencing information.
One entry in a Writ serialization document.
String getFileNameWithoutExtension() const
void beginNewTransaction()
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)
VolumeAndPanPlugin::Ptr getMasterVolumePlugin() const
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 *)
juce::UndoManager & getUndoManager() noexcept
void cancelAnyPendingUpdates()
TimeSigSetting * getTimeSig(int index) const
TempoSetting * getTempo(int index) const
void ensureContextAllocated(bool alwaysReallocate=false)
bool isPlayContextActive() const
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 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.
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.
bool json_parse(const String &jsonstring, T &target)
Parse a well formed JSON string and assign contents to target.
int64_t int64
A 64-bit unsigned integer, use PRI*64 in format strings.
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...
std::string anklang_runpath(RPath rpath, const String &segment)
Retrieve various resource paths at runtime.
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.
Reference for an allocated memory block.