diff options
Diffstat (limited to 'src/djinterop/enginelibrary/el_track_impl.cpp')
-rw-r--r-- | src/djinterop/enginelibrary/el_track_impl.cpp | 795 |
1 files changed, 795 insertions, 0 deletions
diff --git a/src/djinterop/enginelibrary/el_track_impl.cpp b/src/djinterop/enginelibrary/el_track_impl.cpp new file mode 100644 index 0000000..5f9d589 --- /dev/null +++ b/src/djinterop/enginelibrary/el_track_impl.cpp @@ -0,0 +1,795 @@ +#include <cmath> + +#include <djinterop/djinterop.hpp> +#include <djinterop/enginelibrary/el_crate_impl.hpp> +#include <djinterop/enginelibrary/el_database_impl.hpp> +#include <djinterop/enginelibrary/el_track_impl.hpp> +#include <djinterop/impl/util.hpp> + +namespace djinterop +{ +namespace enginelibrary +{ +using std::chrono::duration_cast; +using std::chrono::milliseconds; +using std::chrono::seconds; +using std::chrono::system_clock; + +namespace +{ +boost::optional<system_clock::time_point> to_time_point( + boost::optional<int64_t> timestamp) +{ + boost::optional<system_clock::time_point> result; + if (timestamp) + { + result = system_clock::time_point{seconds(*timestamp)}; + } + return result; +} + +boost::optional<int64_t> to_timestamp( + boost::optional<system_clock::time_point> time) +{ + boost::optional<int64_t> result; + if (time) + { + result = duration_cast<seconds>(time->time_since_epoch()).count(); + } + return result; +} +} // namespace + +el_track_impl::el_track_impl(std::shared_ptr<el_storage> storage, int64_t id) + : track_impl{id}, storage_{std::move(storage)} +{ +} + +boost::optional<std::string> el_track_impl::get_metadata_str( + metadata_str_type type) +{ + boost::optional<std::string> result; + storage_->music_db << "SELECT text FROM MetaData WHERE id = ? AND " + "type = ? AND text IS NOT NULL" + << id() << static_cast<int64_t>(type) >> + [&](std::string text) { + if (!result) + { + result = std::move(text); + } + else + { + throw track_database_inconsistency{ + "More than one MetaData entry of the same type for the " + "same track", + id()}; + } + }; + return result; +} + +void el_track_impl::set_metadata_str( + metadata_str_type type, boost::optional<boost::string_view> content) +{ + if (content) + { + set_metadata_str(type, std::string{*content}); + } + else + { + storage_->music_db + << "REPLACE INTO MetaData (id, type, text) VALUES (?, ?, ?)" << id() + << static_cast<int64_t>(type) << nullptr; + } +} + +void el_track_impl::set_metadata_str( + metadata_str_type type, const std::string& content) +{ + storage_->music_db + << "REPLACE INTO MetaData (id, type, text) VALUES (?, ?, ?)" << id() + << static_cast<int64_t>(type) << content; +} + +boost::optional<int64_t> el_track_impl::get_metadata_int(metadata_int_type type) +{ + boost::optional<int64_t> result; + storage_->music_db << "SELECT value FROM MetaDataInteger WHERE id = " + "? AND type = ? AND value IS NOT NULL" + << id() << static_cast<int64_t>(type) >> + [&](int64_t value) { + if (!result) + { + result = value; + } + else + { + throw track_database_inconsistency{ + "More than one MetaDataInteger entry of the same type " + "for the same track", + id()}; + } + }; + return result; +} + +void el_track_impl::set_metadata_int( + metadata_int_type type, boost::optional<int64_t> content) +{ + storage_->music_db + << "REPLACE INTO MetaDataInteger (id, type, value) VALUES (?, ?, ?)" + << id() << static_cast<int64_t>(type) << content; +} + +beat_data el_track_impl::get_beat_data() +{ + return get_perfdata<beat_data>("beatData"); +} + +void el_track_impl::set_beat_data(beat_data data) +{ + set_perfdata("beatData", data); +} + +high_res_waveform_data el_track_impl::get_high_res_waveform_data() +{ + return get_perfdata<high_res_waveform_data>("highResolutionWaveFormData"); +} + +void el_track_impl::set_high_res_waveform_data(high_res_waveform_data data) +{ + set_perfdata("highResolutionWaveFormData", data); +} + +loops_data el_track_impl::get_loops_data() +{ + return get_perfdata<loops_data>("loops"); +} + +void el_track_impl::set_loops_data(loops_data data) +{ + set_perfdata("loops", data); +} + +overview_waveform_data el_track_impl::get_overview_waveform_data() +{ + return get_perfdata<overview_waveform_data>("overviewWaveFormData"); +} + +void el_track_impl::set_overview_waveform_data(overview_waveform_data data) +{ + set_perfdata("overviewWaveFormData", data); +} + +quick_cues_data el_track_impl::get_quick_cues_data() +{ + return get_perfdata<quick_cues_data>("quickCues"); +} + +void el_track_impl::set_quick_cues_data(quick_cues_data data) +{ + set_perfdata("quickCues", data); +} + +track_data el_track_impl::get_track_data() +{ + return get_perfdata<track_data>("trackData"); +} + +void el_track_impl::set_track_data(track_data data) +{ + set_perfdata("trackData", data); +} + +std::vector<beatgrid_marker> el_track_impl::adjusted_beatgrid() +{ + auto beat_d = get_beat_data(); + return std::move(beat_d.adjusted_beatgrid); +} + +void el_track_impl::set_adjusted_beatgrid(std::vector<beatgrid_marker> beatgrid) +{ + storage_->perfdata_db << "BEGIN"; + auto beat_d = get_beat_data(); + beat_d.adjusted_beatgrid = std::move(beatgrid); + set_beat_data(std::move(beat_d)); + storage_->perfdata_db << "COMMIT"; +} + +double el_track_impl::adjusted_main_cue() +{ + return get_quick_cues_data().adjusted_main_cue; +} + +void el_track_impl::set_adjusted_main_cue(double sample_offset) +{ + storage_->perfdata_db << "BEGIN"; + auto quick_cues_d = get_quick_cues_data(); + quick_cues_d.adjusted_main_cue = sample_offset; + set_quick_cues_data(std::move(quick_cues_d)); + storage_->perfdata_db << "COMMIT"; +} + +boost::optional<std::string> el_track_impl::album() +{ + return get_metadata_str(metadata_str_type::album); +} + +void el_track_impl::set_album(boost::optional<boost::string_view> album) +{ + set_metadata_str(metadata_str_type::album, album); +} + +boost::optional<int64_t> el_track_impl::album_art_id() +{ + int64_t cell = get_cell<int64_t>("idAlbumArt"); + boost::optional<int64_t> album_art_id; + if (cell < 1) + { + // TODO (haslersn): Throw something. + } + else if (cell > 1) + { + album_art_id = cell; + } + return album_art_id; +} + +void el_track_impl::set_album_art_id(boost::optional<int64_t> album_art_id) +{ + if (album_art_id && *album_art_id <= 1) + { + // TODO (haslersn): Throw something. + } + set_cell("idAlbumArt", album_art_id.value_or(1)); + // 1 is the magic number for "no album art" +} + +boost::optional<std::string> el_track_impl::artist() +{ + return get_metadata_str(metadata_str_type::artist); +} + +void el_track_impl::set_artist(boost::optional<boost::string_view> artist) +{ + set_metadata_str(metadata_str_type::artist, artist); +} + +boost::optional<double> el_track_impl::average_loudness() +{ + return get_track_data().average_loudness; +} + +void el_track_impl::set_average_loudness( + boost::optional<double> average_loudness) +{ + storage_->perfdata_db << "BEGIN"; + auto track_d = get_track_data(); + track_d.average_loudness = average_loudness; + set_track_data(track_d); + storage_->perfdata_db << "COMMIT"; +} + +boost::optional<int64_t> el_track_impl::bitrate() +{ + return get_cell<boost::optional<int64_t>>("bitrate"); +} + +void el_track_impl::set_bitrate(boost::optional<int64_t> bitrate) +{ + set_cell("bitrate", bitrate); +} + +boost::optional<double> el_track_impl::bpm() +{ + return get_cell<boost::optional<double>>("bpmAnalyzed"); +} + +void el_track_impl::set_bpm(boost::optional<double> bpm) +{ + set_cell("bpmAnalyzed", bpm); + boost::optional<int64_t> ceiled_bpm; + if (bpm) + { + ceiled_bpm = static_cast<int64_t>(std::ceil(*bpm)); + } + set_cell("bpm", ceiled_bpm); +} + +boost::optional<std::string> el_track_impl::comment() +{ + return get_metadata_str(metadata_str_type::comment); +} + +void el_track_impl::set_comment(boost::optional<boost::string_view> comment) +{ + set_metadata_str(metadata_str_type::comment, comment); +} + +boost::optional<std::string> el_track_impl::composer() +{ + return get_metadata_str(metadata_str_type::composer); +} + +void el_track_impl::set_composer(boost::optional<boost::string_view> composer) +{ + set_metadata_str(metadata_str_type::composer, composer); +} + +database el_track_impl::db() +{ + return database{std::make_shared<el_database_impl>(storage_)}; +} + +std::vector<crate> el_track_impl::containing_crates() +{ + std::vector<crate> results; + storage_->music_db << "SELECT crateId FROM CrateTrackList WHERE trackId = ?" + << id() >> + [&](int64_t id) { + results.push_back( + crate{std::make_shared<el_crate_impl>(storage_, id)}); + }; + return results; +} + +std::vector<beatgrid_marker> el_track_impl::default_beatgrid() +{ + auto beat_d = get_beat_data(); + return std::move(beat_d.default_beatgrid); +} + +void el_track_impl::set_default_beatgrid(std::vector<beatgrid_marker> beatgrid) +{ + storage_->perfdata_db << "BEGIN"; + auto beat_d = get_beat_data(); + beat_d.default_beatgrid = std::move(beatgrid); + set_beat_data(std::move(beat_d)); + storage_->perfdata_db << "COMMIT"; +} + +double el_track_impl::default_main_cue() +{ + return get_quick_cues_data().default_main_cue; +} + +void el_track_impl::set_default_main_cue(double sample_offset) +{ + storage_->perfdata_db << "BEGIN"; + auto quick_cues_d = get_quick_cues_data(); + quick_cues_d.default_main_cue = sample_offset; + set_quick_cues_data(std::move(quick_cues_d)); + storage_->perfdata_db << "COMMIT"; +} + +boost::optional<milliseconds> el_track_impl::duration() +{ + boost::optional<milliseconds> result; + auto smp = sampling(); + if (smp) + { + double secs = smp->sample_count / smp->sample_rate; + return milliseconds{static_cast<int64_t>(1000 * secs)}; + } + auto secs = get_cell<boost::optional<int64_t>>("length"); + if (secs) + { + return milliseconds{*secs * 1000}; + } + return boost::none; +} + +std::string el_track_impl::file_extension() +{ + auto rel_path = relative_path(); + return get_file_extension(rel_path) + .value_or(boost::string_view{}) + .to_string(); +} + +std::string el_track_impl::filename() +{ + auto rel_path = relative_path(); + return get_filename(rel_path).to_string(); +} + +boost::optional<std::string> el_track_impl::genre() +{ + return get_metadata_str(metadata_str_type::genre); +} + +void el_track_impl::set_genre(boost::optional<boost::string_view> genre) +{ + set_metadata_str(metadata_str_type::genre, genre); +} + +boost::optional<hot_cue> el_track_impl::hot_cue_at(int32_t index) +{ + auto quick_cues_d = get_quick_cues_data(); + return std::move(quick_cues_d.hot_cues[index]); +} + +void el_track_impl::set_hot_cue_at(int32_t index, boost::optional<hot_cue> cue) +{ + storage_->perfdata_db << "BEGIN"; + auto quick_cues_d = get_quick_cues_data(); + quick_cues_d.hot_cues[index] = std::move(cue); + set_quick_cues_data(std::move(quick_cues_d)); + storage_->perfdata_db << "END"; +} + +std::array<boost::optional<hot_cue>, 8> el_track_impl::hot_cues() +{ + auto quick_cues_d = get_quick_cues_data(); + return std::move(quick_cues_d.hot_cues); +} + +void el_track_impl::set_hot_cues(std::array<boost::optional<hot_cue>, 8> cues) +{ + storage_->perfdata_db << "BEGIN"; + // TODO (haslersn): The following can be optimized because in this case we + // overwrite all hot_cues + auto quick_cues_d = get_quick_cues_data(); + quick_cues_d.hot_cues = std::move(cues); + set_quick_cues_data(std::move(quick_cues_d)); + storage_->perfdata_db << "END"; +} + +boost::optional<track_import_info> el_track_impl::import_info() +{ + if (get_cell<int64_t>("isExternalTrack") == 0) + { + return boost::none; + } + return track_import_info{get_cell<std::string>("uuidOfExternalDatabase"), + get_cell<int64_t>("idTrackInExternalDatabase")}; + // TODO (haslersn): How should we handle cells that unexpectedly don't + // contain integral values? +} + +void el_track_impl::set_import_info( + const boost::optional<track_import_info>& import_info) +{ + if (import_info) + { + set_cell("isExternalTrack", 1); + set_cell("uuidOfExternalDatabase", import_info->external_db_uuid()); + set_cell("idTrackInExternalDatabase", import_info->external_track_id()); + } + else + { + set_cell("isExternalTrack", 0); + set_cell("uuidOfExternalDatabase", nullptr); + set_cell("idTrackInExternalDatabase", nullptr); + } +} + +bool el_track_impl::is_valid() +{ + bool valid = false; + storage_->music_db << "SELECT COUNT(*) FROM Track WHERE id = ?" << id() >> + [&](int count) { + if (count == 1) + { + valid = true; + } + else if (count > 1) + { + throw track_database_inconsistency{ + "More than one track with the same ID", id()}; + } + }; + return valid; +} + +boost::optional<musical_key> el_track_impl::key() +{ + boost::optional<musical_key> result; + auto key_num = get_metadata_int(metadata_int_type::musical_key); + if (key_num) + { + result = static_cast<musical_key>(*key_num); + } + return result; +} + +void el_track_impl::set_key(boost::optional<musical_key> key) +{ + boost::optional<int64_t> key_num; + if (key) + { + key_num = static_cast<int64_t>(*key); + } + + storage_->perfdata_db << "BEGIN"; + auto track_d = get_track_data(); + track_d.key = key; + set_track_data(track_d); + storage_->perfdata_db << "COMMIT"; + + // TODO (haslersn): atomic? + set_metadata_int(metadata_int_type::musical_key, key_num); +} + +boost::optional<system_clock::time_point> el_track_impl::last_accessed_at() + +{ + // TODO (haslersn): Is there a difference between + // `el_track_impl::last_accessed_at()` and + // `el_track_impl::last_played_at()`, except for the ceiling of the + // timestamp? + return to_time_point(get_metadata_int(metadata_int_type::last_accessed_ts)); +} + +void el_track_impl::set_last_accessed_at( + boost::optional<system_clock::time_point> accessed_at) +{ + if (accessed_at) + { + // Field is always ceiled to the midnight at the end of the day the + // track is played, it seems. + // TODO (haslersn): ^ Why "played" and not "accessed"? + // TODO (haslersn): Shouldn't we just set the unceiled time? This would + // leave the decision whether to ceil it to the library user. Also, it + // would make `el_track_impl::last_accessed_at()` consistent with the + // value that has been set using this method. + auto timestamp = *to_timestamp(accessed_at); + auto secs_per_day = 86400; + timestamp += secs_per_day - 1; + timestamp -= timestamp % secs_per_day; + set_metadata_int(metadata_int_type::last_accessed_ts, timestamp); + } + else + { + set_metadata_int(metadata_int_type::last_accessed_ts, boost::none); + } +} + +boost::optional<system_clock::time_point> el_track_impl::last_modified_at() + +{ + return to_time_point(get_metadata_int(metadata_int_type::last_modified_ts)); +} + +void el_track_impl::set_last_modified_at( + boost::optional<system_clock::time_point> modified_at) +{ + set_metadata_int( + metadata_int_type::last_modified_ts, to_timestamp(modified_at)); +} + +boost::optional<system_clock::time_point> el_track_impl::last_played_at() + +{ + return to_time_point(get_metadata_int(metadata_int_type::last_played_ts)); +} + +void el_track_impl::set_last_played_at( + boost::optional<system_clock::time_point> played_at) +{ + static boost::optional<boost::string_view> zero{"0"}; + static boost::optional<boost::string_view> one{"1"}; + set_metadata_str(metadata_str_type::ever_played, played_at ? one : zero); + set_metadata_int( + metadata_int_type::last_played_ts, to_timestamp(played_at)); + if (played_at) + { + // TODO (haslersn): Add entry to HistorylistTrackList + } + else + { + // TODO (haslersn): Should HistorylistTrackList now be cleared of this + // track? + } +} + +boost::optional<loop> el_track_impl::loop_at(int32_t index) +{ + auto loops_d = get_loops_data(); + return std::move(loops_d.loops[index]); +} + +void el_track_impl::set_loop_at(int32_t index, boost::optional<loop> l) +{ + storage_->perfdata_db << "BEGIN"; + auto loops_d = get_loops_data(); + loops_d.loops[index] = std::move(l); + set_loops_data(std::move(loops_d)); + storage_->perfdata_db << "END"; +} + +std::array<boost::optional<loop>, 8> el_track_impl::loops() +{ + auto loops_d = get_loops_data(); + return std::move(loops_d.loops); +} + +void el_track_impl::set_loops(std::array<boost::optional<loop>, 8> cues) +{ + storage_->perfdata_db << "BEGIN"; + loops_data loops_d; + loops_d.loops = std::move(cues); + set_loops_data(std::move(loops_d)); + storage_->perfdata_db << "END"; +} + +std::vector<waveform_entry> el_track_impl::overview_waveform() +{ + auto overview_waveform_d = get_overview_waveform_data(); + return std::move(overview_waveform_d.waveform); +} + +boost::optional<std::string> el_track_impl::publisher() +{ + return get_metadata_str(metadata_str_type::publisher); +} + +void el_track_impl::set_publisher(boost::optional<boost::string_view> publisher) +{ + set_metadata_str(metadata_str_type::publisher, publisher); +} + +int64_t el_track_impl::recommended_waveform_size() +{ + auto smp = sampling(); + if (!smp) + { + return 0; + } + if (smp->sample_count <= 0) + { + throw track_database_inconsistency{ + "Track has non-positive sample count", id()}; + } + if (smp->sample_rate <= 0) + { + throw track_database_inconsistency{"Track has non-positive sample rate", + id()}; + } + return static_cast<int64_t>(smp->sample_count * 105 / smp->sample_rate); +} + +std::string el_track_impl::relative_path() +{ + return get_cell<std::string>("path"); +} + +void el_track_impl::set_relative_path(boost::string_view relative_path) +{ + // TODO (haslersn): Should we check the path? + set_cell("path", std::string{relative_path}); + auto filename = get_filename(relative_path); + set_cell("filename", std::string{filename}); + auto extension = get_file_extension(filename); + set_metadata_str(metadata_str_type::file_extension, extension); +} + +boost::optional<sampling_info> el_track_impl::sampling() +{ + return get_track_data().sampling; +} + +void el_track_impl::set_sampling(boost::optional<sampling_info> sampling) +{ + // TODO (haslersn): atomic? + + boost::optional<int64_t> secs; + if (sampling) + { + secs = static_cast<int64_t>( + sampling->sample_count / sampling->sample_rate); + + // Set metadata duration_mm_ss as "MM:SS" + std::ostringstream oss; + oss << std::setw(2) << std::setfill('0'); + oss << (*secs / 60); + oss << ":"; + oss << (*secs % 60); + auto str = oss.str(); + set_metadata_str( + metadata_str_type::duration_mm_ss, boost::string_view{str}); + } + else + { + set_metadata_str(metadata_str_type::duration_mm_ss, boost::none); + } + set_cell("length", secs); + set_cell("lengthCalculated", secs); + + storage_->perfdata_db << "BEGIN"; + + // read old data + auto track_d = get_track_data(); + auto beat_d = get_beat_data(); + auto high_res_waveform_d = get_high_res_waveform_data(); + auto overview_waveform_d = get_overview_waveform_data(); + + track_d.sampling = sampling; + beat_d.sampling = sampling; + set_beat_data(std::move(beat_d)); + set_track_data(std::move(track_d)); + + int64_t sample_count = sampling ? sampling->sample_count : 0; + + if (!high_res_waveform_d.waveform.empty()) + { + high_res_waveform_d.samples_per_entry = + sample_count / high_res_waveform_d.waveform.size(); + set_high_res_waveform_data(std::move(high_res_waveform_d)); + } + + if (!overview_waveform_d.waveform.empty()) + { + overview_waveform_d.samples_per_entry = + sample_count / overview_waveform_d.waveform.size(); + set_overview_waveform_data(std::move(overview_waveform_d)); + } + + storage_->perfdata_db << "COMMIT"; +} + +boost::optional<std::string> el_track_impl::title() +{ + return get_metadata_str(metadata_str_type::title); +} + +void el_track_impl::set_title(boost::optional<boost::string_view> title) +{ + set_metadata_str(metadata_str_type::title, title); +} + +boost::optional<int32_t> el_track_impl::track_number() +{ + return get_cell<boost::optional<int32_t>>("playOrder"); +} + +void el_track_impl::set_track_number(boost::optional<int32_t> track_number) +{ + set_cell("playOrder", track_number); +} + +std::vector<waveform_entry> el_track_impl::waveform() +{ + auto high_res_waveform_d = get_high_res_waveform_data(); + return std::move(high_res_waveform_d.waveform); +} + +void el_track_impl::set_waveform(std::vector<waveform_entry> waveform) +{ + overview_waveform_data overview_waveform_d; + high_res_waveform_data high_res_waveform_d; + + if (!waveform.empty()) + { + // TODO (haslersn): atomic? + auto smp = sampling(); + int64_t sample_count = smp ? smp->sample_count : 0; + overview_waveform_d.samples_per_entry = sample_count / 1024; + overview_waveform_d.waveform.reserve(1024); + for (int32_t i = 0; i < 1024; ++i) + { + auto entry = waveform[waveform.size() * (2 * i + 1) / 2048]; + overview_waveform_d.waveform.push_back(entry); + } + high_res_waveform_d.samples_per_entry = sample_count / waveform.size(); + high_res_waveform_d.waveform = std::move(waveform); + } + + storage_->perfdata_db << "BEGIN"; + set_overview_waveform_data(std::move(overview_waveform_d)); + set_high_res_waveform_data(std::move(high_res_waveform_d)); + storage_->perfdata_db << "END"; +} + +boost::optional<int32_t> el_track_impl::year() +{ + return get_cell<boost::optional<int32_t>>("year"); +} + +void el_track_impl::set_year(boost::optional<int32_t> year) +{ + set_cell("year", year); +} + +} // namespace enginelibrary +} // namespace djinterop |