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'))
405 const auto& h = header();
407 for(
const auto& e : h)
426 halp_meta(name,
"CSV recorder")
427 halp_meta(author,
"ossia team")
428 halp_meta(category,
"Control/Recording")
429 halp_meta(description,
"Record the messages of a device at regular interval")
430 halp_meta(c_name,
"avnd_device_recorder")
431 halp_meta(uuid,
"7161ca22-5684-48f2-bde7-88933500a7fb")
432 halp_meta(manual_url,
"https://ossia.io/score-docs/processes/csv-recorder.html#csv-recorder")
435 struct recorder_thread
438 std::string filename;
439 std::vector<ossia::net::node_base*> roots;
440 std::chrono::steady_clock::time_point first_ts;
441 fmt::memory_buffer buf;
443 bool first_is_timestamp =
false;
446 void setActive(
bool b)
459 auto filename = QByteArray::fromStdString(this->filename);
460 filename.replace(
"%t", QDateTime::currentDateTimeUtc().toString().toUtf8());
461 f.setFileName(filename);
462 if(filename.isEmpty())
468 f.open(QIODevice::WriteOnly);
472 f.write(
"timestamp");
474 for(
auto in : this->roots)
476 if(
auto p = in->get_parameter())
479 f.write(QByteArray::fromStdString(p->get_node().osc_address()));
487 first_ts = std::chrono::steady_clock::now();
497 using namespace std::chrono;
499 = duration_cast<milliseconds>(steady_clock::now() - first_ts).count();
503 void write(int64_t timestamp)
505 f.write(QString::number(timestamp).toUtf8());
506 for(
auto in : this->roots)
508 if(
auto p = in->get_parameter())
513 ossia::apply(ossia::detail::fmt_writer{buf}, p->value());
515 std::string_view sv(buf.data(), buf.data() + buf.size());
516 if(sv.find_first_of(
", \"\n\r\t;") != std::string_view::npos)
520 f.write(buf.data(), buf.size());
525 f.write(buf.data(), buf.size());
537 std::string filename;
538 std::vector<ossia::net::node_base*> roots;
539 std::chrono::steady_clock::time_point first_ts;
540 int64_t nots_index{};
543 boost::container::flat_map<int, ossia::net::parameter_base*> m_map;
544 boost::container::flat_map<int64_t, std::vector<ossia::value>> m_vec_ts;
545 std::vector<std::vector<ossia::value>> m_vec_no_ts;
548 bool first_is_timestamp =
false;
551 void setActive(
bool b)
560 void setLoops(
bool b) { loops = b; }
565 auto filename = QByteArray::fromStdString(this->filename).trimmed();
566 filename.replace(
"%t", QDateTime::currentDateTimeUtc().toString().toUtf8());
567 f.setFileName(filename);
568 if(filename.isEmpty())
574 f.open(QIODevice::ReadOnly);
583 for(
auto in : this->roots)
585 if(
auto p = in->get_parameter())
591 auto data = (
const char*)f.map(0, f.size());
595 r.parse_view({data, data + f.size()});
596 int columns = r.cols();
598 auto header = r.header();
600 boost::container::flat_map<std::string, ossia::net::parameter_base*> params;
602 for(
auto node : roots)
603 if(
auto p = node->get_parameter())
604 params[node->osc_address()] = p;
609 auto header_it = header.begin();
610 if(first_is_timestamp)
612 for(; header_it != header.end(); ++header_it)
614 auto addr = *header_it;
616 addr.read_raw_value(v);
617 if(
auto it = params.find(v); it != params.end())
619 m_map[i] = it->second;
626 if(first_is_timestamp)
628 m_vec_ts.reserve(r.
rows());
629 for(
const auto& row : r)
631 parse_row_with_timestamps(columns, row, v);
637 m_vec_no_ts.reserve(r.
rows());
638 for(
const auto& row : r)
640 parse_row_no_timestamps(columns, row, v);
644 first_ts = std::chrono::steady_clock::now();
647 void parse_cell_impl(
648 const std::string& v, ossia::net::parameter_base& param, ossia::value& out)
652 std::optional<ossia::value> res;
653 if(v.starts_with(
'"') && v.ends_with(
'"'))
654 res = State::parseValue(std::string_view(v).substr(1, v.size() - 2));
656 res = State::parseValue(v);
660 out = std::move(*res);
661 if(
auto t = param.get_value_type(); out.get_type() != t)
663 ossia::convert(out, t);
670 parse_cell(
const auto& cell, std::string& v, std::vector<ossia::value>& vec,
int i)
672 if(
auto param = m_map[i])
676 parse_cell_impl(v, *param, vec[i]);
681 void parse_row_no_timestamps(
int columns,
auto& row, std::string& v)
683 auto& vec = this->m_vec_no_ts.emplace_back(columns);
686 for(
auto it = row.begin(); it != row.end(); ++it)
688 parse_cell(*it, v, vec, i);
693 void parse_row_with_timestamps(
int columns,
auto& row, std::string& v)
695 if(row.length() <= 1)
698 auto it = row.begin();
699 const auto& ts = *it;
702 auto tstamp = ossia::parse_strict<int64_t>(v);
705 auto& vec = this->m_vec_ts[*tstamp];
706 vec.resize(columns - 1);
709 for(++it; it != row.end(); ++it)
711 parse_cell(*it, v, vec, i);
718 if(first_is_timestamp)
723 using namespace std::chrono;
724 auto ts = duration_cast<milliseconds>(steady_clock::now() - first_ts).count();
726 ts %= m_vec_ts.rbegin()->first + 1;
731 if(m_vec_no_ts.empty())
734 using namespace std::chrono;
735 if(loops && nots_index >= std::ssize(m_vec_no_ts))
737 read_no_ts(nots_index++);
741 void read_no_ts(int64_t timestamp)
745 if(timestamp >= std::ssize(m_vec_no_ts))
747 auto it = m_vec_no_ts.begin() + timestamp;
748 if(it != m_vec_no_ts.end())
755 if(
auto p = m_map.find(i); p != m_map.end())
757 p->second->push_value(v);
764 void read_ts(int64_t timestamp)
766 auto it = m_vec_ts.lower_bound(timestamp);
767 if(it != m_vec_ts.end())
770 for(
auto& v : it->second)
774 if(
auto p = m_map.find(i); p != m_map.end())
776 p->second->push_value(v);
784 std::shared_ptr<recorder_thread> record_impl = std::make_shared<recorder_thread>();
785 std::shared_ptr<player_thread> play_impl = std::make_shared<player_thread>();
791 halp::time_chooser<
"Interval", halp::range{.min = 0.00001, .max = 5., .init = 0.25}>
793 struct : halp::lineedit<
"File pattern",
"">
799 halp__enum(
"Mode", None, None, Record, Playback, Loop)
802 struct ts : halp::toggle<"Timestamped", halp::default_on_toggle>
804 halp_meta(description,
"Set to true to use the first column as timestamp")
814 std::shared_ptr<recorder_thread> recorder;
815 std::shared_ptr<player_thread> player;
817 std::vector<ossia::net::node_base*> roots;
818 bool first_is_timestamp{};
823 swap(recorder->filename, path);
824 swap(recorder->roots, roots);
825 player->filename = recorder->filename;
826 player->roots = recorder->roots;
827 player->first_is_timestamp = first_is_timestamp;
828 recorder->first_is_timestamp = first_is_timestamp;
836 std::shared_ptr<recorder_thread> recorder;
837 std::shared_ptr<player_thread> player;
839 bool first_is_timestamp{};
843 swap(recorder->filename, path);
844 player->filename = recorder->filename;
845 player->first_is_timestamp = first_is_timestamp;
846 recorder->first_is_timestamp = first_is_timestamp;
854 std::shared_ptr<recorder_thread> recorder;
855 void operator()() { recorder->write(); }
860 std::shared_ptr<player_thread> player;
861 void operator()() { player->read(); }
866 std::shared_ptr<recorder_thread> recorder;
867 std::shared_ptr<player_thread> player;
872 recorder->setActive(mode == mode_type::Record);
873 player->setActive(mode == mode_type::Playback || mode == mode_type::Loop);
874 player->setLoops(mode == mode_type::Loop);
878 using worker_message = ossia::variant<
884 std::function<void(worker_message)> request;
885 static void work(worker_message&& mess)
887 ossia::visit([&]<
typename M>(M&& msg) {
888 if constexpr(requires { *msg; })
889 (*std::forward<M>(msg))();
891 std::forward<M>(msg)();
896 using tick = halp::tick_musical;
900 worker.request(activate_message{record_impl, play_impl, inputs.mode.value});
905 reset_path_message{record_impl, play_impl, inputs.filename, inputs.timestamped});
908 void operator()(
const halp::tick_musical& tk)
910 int64_t elapsed_ns = 0.;
911 if(!first_message_sent_pos)
912 first_message_sent_pos = tk.position_in_nanoseconds;
913 if(last_message_sent_pos)
914 elapsed_ns = tk.position_in_nanoseconds - *last_message_sent_pos;
916 if(elapsed_ns > 0 && elapsed_ns < inputs.time.value * 1e9)
918 last_message_sent_pos = tk.position_in_nanoseconds;
923 if(!std::exchange(started,
true))
925 inputs.pattern.reprocess();
926 worker.request(std::unique_ptr<reset_message>(
new reset_message{
927 record_impl, play_impl, inputs.filename, roots, inputs.timestamped}));
932 case decltype(inputs.mode)::None:
934 case decltype(inputs.mode)::Record:
935 worker.request(process_message{record_impl});
937 case decltype(inputs.mode)::Playback:
938 case decltype(inputs.mode)::Loop:
939 worker.request(playback_message{play_impl});
945 std::optional<int64_t> first_message_sent_pos;
946 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