2 #include <State/Value.hpp>
4 #include <ossia/detail/parse_strict.hpp>
5 #include <ossia/network/value/detail/value_conversion_impl.hpp>
10 #include <AvndProcesses/AddressTools.hpp>
11 #include <halp/audio.hpp>
44 static std::pair<size_t, size_t> trim(
const char* buffer,
size_t start,
size_t end)
51 template <
char... character_list>
55 constexpr
static bool is_trim_char(
char) {
return false; }
57 template <
class... Tail>
58 constexpr
static bool is_trim_char(
char c,
char head, Tail... tail)
60 return c == head || is_trim_char(c, tail...);
64 static std::pair<size_t, size_t> trim(
const char* buffer,
size_t start,
size_t end)
66 size_t new_start = start, new_end = end;
67 while(new_start != new_end && is_trim_char(buffer[new_start], character_list...))
69 while(new_start != new_end && is_trim_char(buffer[new_end - 1], character_list...))
71 return {new_start, new_end};
78 template <
char character>
81 constexpr
static char value = character;
84 template <
char character>
87 constexpr
static char value = character;
93 constexpr
static bool value = flag;
102 const char* buffer_{
nullptr};
103 size_t buffer_size_{0};
104 size_t header_start_{0};
105 size_t header_end_{0};
108 bool parse_view(std::string_view sv)
111 buffer_size_ = sv.size();
112 return buffer_size_ > 0;
121 const char* buffer_{
nullptr};
124 bool escaped_{
false};
126 friend class CellIterator;
130 std::string_view read_view()
const
132 const auto new_start_end = trim_policy::trim(buffer_, start_, end_);
133 return std::string_view(
134 buffer_ + new_start_end.first, new_start_end.second - new_start_end.first);
139 template <
typename Container>
140 void read_raw_value(Container& result)
const
144 result.reserve(end_ - start_);
145 for(
size_t i = start_; i < end_; ++i)
146 result.push_back(buffer_[i]);
151 template <
typename Container>
152 void read_value(Container& result)
const
156 result.reserve(end_ - start_);
157 const auto new_start_end = trim_policy::trim(buffer_, start_, end_);
158 for(
size_t i = new_start_end.first; i < new_start_end.second; ++i)
159 result.push_back(buffer_[i]);
160 for(
size_t i = 1; i < result.size(); ++i)
162 if(result[i] == quote_character::value
163 && result[i - 1] == quote_character::value)
165 result.erase(i - 1, 1);
173 const char* buffer_{
nullptr};
181 const char* address()
const {
return buffer_; }
183 size_t length()
const {
return end_ - start_; }
186 template <
typename Container>
187 void read_raw_value(Container& result)
const
191 result.reserve(end_ - start_);
192 for(
size_t i = start_; i < end_; ++i)
193 result.push_back(buffer_[i]);
206 CellIterator(
const char* buffer,
size_t buffer_size,
size_t start,
size_t end)
208 , buffer_size_(buffer_size)
225 cell.buffer_ = buffer_;
226 cell.start_ = current_;
229 size_t last_quote_location = 0;
230 bool quote_opened =
false;
231 for(
auto i = current_; i < end_; i++)
234 if(buffer_[i] == delimiter::value && !quote_opened)
238 cell.end_ = current_;
239 cell.escaped_ = escaped;
244 if(buffer_[i] == quote_character::value)
250 last_quote_location = i;
254 escaped = (last_quote_location == i - 1);
255 last_quote_location += (i - last_quote_location) *
size_t(!escaped);
256 quote_opened = escaped || (buffer_[i + 1] != delimiter::value);
261 cell.end_ = current_ + 1;
265 bool operator!=(
const CellIterator& rhs) {
return current_ != rhs.current_; }
270 return CellIterator(buffer_, end_ - start_, start_, end_);
272 CellIterator end()
const {
return CellIterator(buffer_, end_ - start_, end_, end_); }
284 RowIterator(
const char* buffer,
size_t buffer_size,
size_t start)
286 , buffer_size_(buffer_size)
302 result.buffer_ = buffer_;
303 result.start_ = start_;
306 if(
const char* ptr =
static_cast<const char*
>(
307 memchr(&buffer_[start_],
'\n', (buffer_size_ - start_))))
309 end_ = start_ + (ptr - &buffer_[start_]);
322 bool operator!=(
const RowIterator& rhs) {
return start_ != rhs.start_; }
327 if(buffer_size_ == 0)
329 if(first_row_is_header::value)
331 const auto header_indices = header_indices_();
333 buffer_, buffer_size_,
334 header_indices.second > 0 ? header_indices.second + 1 : 0);
338 return RowIterator(buffer_, buffer_size_, 0);
342 RowIterator end()
const
344 return RowIterator(buffer_, buffer_size_, buffer_size_ + 1);
348 std::pair<size_t, size_t> header_indices_()
const
350 size_t start = 0, end = 0;
353 =
static_cast<const char*
>(memchr(&buffer_[start],
'\n', (buffer_size_ - start))))
355 end = start + (ptr - &buffer_[start]);
363 size_t start = 0, end = 0;
365 result.buffer_ = buffer_;
366 result.start_ = start;
370 =
static_cast<const char*
>(memchr(&buffer_[start],
'\n', (buffer_size_ - start))))
372 end = start + (ptr - &buffer_[start]);
381 size_t rows(
bool ignore_empty_lines =
false)
const
384 if(!buffer_ || buffer_size_ == 0)
388 if(not first_row_is_header::value
389 and (not ignore_empty_lines or *(
static_cast<const char*
>(buffer_)) !=
'\r'))
392 for(
const char* p = buffer_;
393 (p =
static_cast<const char*
>(memchr(p,
'\n', (buffer_ + buffer_size_) - p)));
396 if(ignore_empty_lines and (p >= buffer_ + buffer_size_ - 1 or *(p + 1) ==
'\r'))
403 size_t cols()
const {
return header().length(); }
417 halp_meta(name,
"CSV recorder")
418 halp_meta(author,
"ossia team")
419 halp_meta(category,
"Control/Recording")
420 halp_meta(description,
"Record the messages of a device at regular interval")
421 halp_meta(c_name,
"avnd_device_recorder")
422 halp_meta(uuid,
"7161ca22-5684-48f2-bde7-88933500a7fb")
423 halp_meta(manual_url,
"https://ossia.io/score-docs/processes/csv-recorder.html#csv-recorder")
426 struct recorder_thread
429 std::string filename;
430 std::vector<ossia::net::node_base*> roots;
431 std::chrono::steady_clock::time_point first_ts;
432 fmt::memory_buffer buf;
434 bool first_is_timestamp =
false;
437 void setActive(
bool b)
450 auto filename = QByteArray::fromStdString(this->filename);
451 filename.replace(
"%t", QDateTime::currentDateTimeUtc().toString().toUtf8());
452 f.setFileName(filename);
453 if(filename.isEmpty())
459 f.open(QIODevice::WriteOnly);
463 f.write(
"timestamp");
465 for(
auto in : this->roots)
467 if(
auto p = in->get_parameter())
470 f.write(QByteArray::fromStdString(p->get_node().osc_address()));
478 first_ts = std::chrono::steady_clock::now();
488 using namespace std::chrono;
490 = duration_cast<milliseconds>(steady_clock::now() - first_ts).count();
494 void write(int64_t timestamp)
496 f.write(QString::number(timestamp).toUtf8());
497 for(
auto in : this->roots)
499 if(
auto p = in->get_parameter())
504 ossia::apply(ossia::detail::fmt_writer{buf}, p->value());
506 std::string_view sv(buf.data(), buf.data() + buf.size());
507 if(sv.find_first_of(
", \"\n\r\t;") != std::string_view::npos)
511 f.write(buf.data(), buf.size());
516 f.write(buf.data(), buf.size());
528 std::string filename;
529 std::vector<ossia::net::node_base*> roots;
530 std::chrono::steady_clock::time_point first_ts;
533 boost::container::flat_map<int, ossia::net::parameter_base*> m_map;
534 boost::container::flat_map<int64_t, std::vector<ossia::value>> m_vec_ts;
535 std::vector<std::vector<ossia::value>> m_vec_no_ts;
538 bool first_is_timestamp =
false;
541 void setActive(
bool b)
550 void setLoops(
bool b) { loops = b; }
555 auto filename = QByteArray::fromStdString(this->filename).trimmed();
556 filename.replace(
"%t", QDateTime::currentDateTimeUtc().toString().toUtf8());
557 f.setFileName(filename);
558 if(filename.isEmpty())
564 f.open(QIODevice::ReadOnly);
573 for(
auto in : this->roots)
575 if(
auto p = in->get_parameter())
581 auto data = (
const char*)f.map(0, f.size());
585 r.parse_view({data, data + f.size()});
586 int columns = r.cols();
588 auto header = r.header();
590 boost::container::flat_map<std::string, ossia::net::parameter_base*> params;
592 for(
auto node : roots)
593 if(
auto p = node->get_parameter())
594 params[node->osc_address()] = p;
599 auto header_it = header.begin();
600 if(first_is_timestamp)
602 for(; header_it != header.end(); ++header_it)
604 auto addr = *header_it;
606 addr.read_raw_value(v);
607 if(
auto it = params.find(v); it != params.end())
609 m_map[i] = it->second;
616 if(first_is_timestamp)
618 m_vec_ts.reserve(r.
rows());
619 for(
const auto& row : r)
621 parse_row_with_timestamps(columns, row, v);
627 m_vec_no_ts.reserve(r.
rows());
628 for(
const auto& row : r)
630 parse_row_no_timestamps(columns, row, v);
634 first_ts = std::chrono::steady_clock::now();
637 void parse_cell_impl(
638 const std::string& v, ossia::net::parameter_base& param, ossia::value& out)
642 std::optional<ossia::value> res;
643 if(v.starts_with(
'"') && v.ends_with(
'"'))
644 res = State::parseValue(std::string_view(v).substr(1, v.size() - 2));
646 res = State::parseValue(v);
650 out = std::move(*res);
651 if(
auto t = param.get_value_type(); out.get_type() != t)
653 ossia::convert(out, t);
660 parse_cell(
const auto& cell, std::string& v, std::vector<ossia::value>& vec,
int i)
662 if(
auto param = m_map[i])
666 parse_cell_impl(v, *param, vec[i]);
671 void parse_row_no_timestamps(
int columns,
auto& row, std::string& v)
673 auto& vec = this->m_vec_no_ts.emplace_back(columns);
676 for(
auto it = row.begin(); it != row.end(); ++it)
678 parse_cell(*it, v, vec, i);
683 void parse_row_with_timestamps(
int columns,
auto& row, std::string& v)
685 if(row.length() <= 1)
688 auto it = row.begin();
689 const auto& ts = *it;
692 auto tstamp = ossia::parse_strict<int64_t>(v);
695 auto& vec = this->m_vec_ts[*tstamp];
696 vec.resize(columns - 1);
699 for(++it; it != row.end(); ++it)
701 parse_cell(*it, v, vec, i);
708 if(first_is_timestamp)
713 using namespace std::chrono;
714 auto ts = duration_cast<milliseconds>(steady_clock::now() - first_ts).count();
716 ts %= m_vec_ts.rbegin()->first + 1;
721 if(m_vec_no_ts.empty())
724 using namespace std::chrono;
725 auto ts = duration_cast<milliseconds>(steady_clock::now() - first_ts).count();
726 if(loops && ts >= std::ssize(m_vec_no_ts))
732 void read_no_ts(int64_t timestamp)
736 if(timestamp >= std::ssize(m_vec_no_ts))
738 auto it = m_vec_no_ts.begin() + timestamp;
739 if(it != m_vec_no_ts.end())
746 if(
auto p = m_map.find(i); p != m_map.end())
748 p->second->push_value(v);
755 void read_ts(int64_t timestamp)
757 auto it = m_vec_ts.lower_bound(timestamp);
758 if(it != m_vec_ts.end())
761 for(
auto& v : it->second)
765 if(
auto p = m_map.find(i); p != m_map.end())
767 p->second->push_value(v);
775 std::shared_ptr<recorder_thread> record_impl = std::make_shared<recorder_thread>();
776 std::shared_ptr<player_thread> play_impl = std::make_shared<player_thread>();
782 halp::time_chooser<
"Interval", halp::range{.min = 0.00001, .max = 5., .init = 0.25}>
784 struct : halp::lineedit<
"File pattern",
"">
790 halp__enum(
"Mode", None, None, Record, Playback, Loop)
793 struct ts : halp::toggle<"Timestamped", halp::default_on_toggle>
795 halp_meta(description,
"Set to true to use the first column as timestamp")
805 std::shared_ptr<recorder_thread> recorder;
806 std::shared_ptr<player_thread> player;
808 std::vector<ossia::net::node_base*> roots;
809 bool first_is_timestamp{};
814 swap(recorder->filename, path);
815 swap(recorder->roots, roots);
816 player->filename = recorder->filename;
817 player->roots = recorder->roots;
818 player->first_is_timestamp = first_is_timestamp;
819 recorder->first_is_timestamp = first_is_timestamp;
827 std::shared_ptr<recorder_thread> recorder;
828 std::shared_ptr<player_thread> player;
830 bool first_is_timestamp{};
834 swap(recorder->filename, path);
835 player->filename = recorder->filename;
836 player->first_is_timestamp = first_is_timestamp;
837 recorder->first_is_timestamp = first_is_timestamp;
845 std::shared_ptr<recorder_thread> recorder;
846 void operator()() { recorder->write(); }
851 std::shared_ptr<player_thread> player;
852 void operator()() { player->read(); }
857 std::shared_ptr<recorder_thread> recorder;
858 std::shared_ptr<player_thread> player;
863 recorder->setActive(mode == mode_type::Record);
864 player->setActive(mode == mode_type::Playback || mode == mode_type::Loop);
865 player->setLoops(mode == mode_type::Loop);
869 using worker_message = ossia::variant<
875 std::function<void(worker_message)> request;
876 static void work(worker_message&& mess)
878 ossia::visit([&]<
typename M>(M&& msg) {
879 if constexpr(requires { *msg; })
880 (*std::forward<M>(msg))();
882 std::forward<M>(msg)();
887 using tick = halp::tick_musical;
891 worker.request(activate_message{record_impl, play_impl, inputs.mode.value});
896 reset_path_message{record_impl, play_impl, inputs.filename, inputs.timestamped});
899 void operator()(
const halp::tick_musical& tk)
901 int64_t elapsed_ns = 0.;
902 if(!first_message_sent_pos)
903 first_message_sent_pos = tk.position_in_nanoseconds;
904 if(last_message_sent_pos)
905 elapsed_ns = tk.position_in_nanoseconds - *last_message_sent_pos;
907 if(elapsed_ns > 0 && elapsed_ns < inputs.time.value * 1e9)
909 last_message_sent_pos = tk.position_in_nanoseconds;
914 if(!std::exchange(started,
true))
916 inputs.pattern.reprocess();
917 worker.request(std::unique_ptr<reset_message>(
new reset_message{
918 record_impl, play_impl, inputs.filename, roots, inputs.timestamped}));
923 case decltype(inputs.mode)::None:
925 case decltype(inputs.mode)::Record:
926 worker.request(process_message{record_impl});
928 case decltype(inputs.mode)::Playback:
929 case decltype(inputs.mode)::Loop:
930 worker.request(playback_message{play_impl});
936 std::optional<int64_t> first_message_sent_pos;
937 std::optional<int64_t> last_message_sent_pos;
Definition: DeviceRecorder.hpp:120
Definition: DeviceRecorder.hpp:197
Definition: DeviceRecorder.hpp:172
Definition: DeviceRecorder.hpp:276
Definition: DeviceRecorder.hpp:101
size_t rows(bool ignore_empty_lines=false) const
Definition: DeviceRecorder.hpp:381
Definition: DeviceRecorder.hpp:38
Definition: DeviceRecorder.hpp:80
Definition: DeviceRecorder.hpp:86
Definition: DeviceRecorder.hpp:42
Definition: DeviceRecorder.hpp:53