141 std::shared_ptr<std::mutex> lock = std::make_shared<std::mutex>();
148 std::string filename;
149 std::vector<ossia::net::node_base*> roots;
150 std::chrono::steady_clock::time_point first_ts;
151 fmt::memory_buffer buf;
153 bool first_is_timestamp =
false;
157 void setSeparator(Separator sep)
noexcept
162 case Separator::Colon:
163 this->separator =
',';
165 case Separator::Semicolon:
166 this->separator =
';';
168 case Separator::Pipe:
169 this->separator =
'|';
174 void setActive(
bool b)
187 f.setFileName(filter_filename(this->filename, context));
188 if(f.fileName().isEmpty())
194 f.open(QIODevice::WriteOnly);
198 f.write(
"timestamp");
200 for(
auto in : this->roots)
202 if(
auto p = in->get_parameter())
204 f.write(&separator, 1);
205 f.write(QByteArray::fromStdString(p->get_node().osc_address()));
213 first_ts = std::chrono::steady_clock::now();
223 using namespace std::chrono;
225 = duration_cast<milliseconds>(steady_clock::now() - first_ts).count();
229 void write(int64_t timestamp)
231 f.write(QString::number(timestamp).toUtf8());
232 std::string separator_bufs =
"\"\n\r\t";
233 separator_bufs += this->separator;
234 for(
auto in : this->roots)
236 if(
auto p = in->get_parameter())
238 f.write(&separator, 1);
242 f.write(buf.data(), buf.size());
252 std::shared_ptr<std::mutex> lock;
259 std::string filename;
260 std::vector<ossia::net::node_base*> roots;
261 std::chrono::steady_clock::time_point first_ts;
262 int64_t nots_index{};
265 boost::container::flat_map<int, ossia::net::parameter_base*> m_map;
266 boost::container::flat_map<int64_t, std::vector<ossia::value>> m_vec_ts;
267 std::vector<std::vector<ossia::value>> m_vec_no_ts;
270 bool first_is_timestamp =
false;
274 void setSeparator(Separator sep)
noexcept
279 case Separator::Colon:
280 this->separator =
',';
282 case Separator::Semicolon:
283 this->separator =
';';
285 case Separator::Pipe:
286 this->separator =
'|';
291 void setActive(
bool b)
300 void setLoops(
bool b) { loops = b; }
302 template <
typename CsvReader>
303 void read(std::string_view data)
307 int columns = r.cols();
309 auto header = r.header();
311 boost::container::flat_map<std::string, ossia::net::parameter_base*> params;
313 for(
auto node : roots)
314 if(
auto p = node->get_parameter())
315 params[node->osc_address()] = p;
320 auto header_it = header.begin();
321 if(first_is_timestamp)
331 for(; header_it != header.end(); ++header_it)
333 auto addr = *header_it;
335 addr.read_raw_value(v);
336 if(
auto it = params.find(v); it != params.end())
338 m_map[i] = it->second;
346 if(first_is_timestamp)
351 m_vec_ts.reserve(r.rows());
352 for(
const auto& row : r)
354 parse_row_with_timestamps(columns, row, v);
360 m_vec_no_ts.reserve(r.rows());
361 for(
const auto& row : r)
363 parse_row_no_timestamps(columns, row, v);
367 first_ts = std::chrono::steady_clock::now();
374 f.setFileName(filter_filename(this->filename, context));
375 if(f.fileName().isEmpty())
381 if(!f.open(QIODevice::ReadOnly))
389 for(
auto in : this->roots)
391 if([[maybe_unused]]
auto p = in->get_parameter())
397 auto data = (
const char*)f.map(0, f.size());
404 read<csv2::Reader<>>({data, data + f.size()});
407 read<csv2::Reader<csv2::delimiter<
';'>>>({data, data + f.size()});
410 read<csv2::Reader<csv2::delimiter<
'|'>>>({data, data + f.size()});
415 void parse_cell_impl(
416 const std::string& v, ossia::net::parameter_base& param, ossia::value& out)
420 std::optional<ossia::value> res;
421 std::string_view text = v;
422 if(v.size() >= 2 && v.starts_with(
'"') && v.ends_with(
'"'))
423 text = text.substr(1, text.size() - 2);
424 res = State::parseValue(text);
430 str.reserve(text.size());
431 for(std::size_t k = 0; k < text.size(); k++)
434 if(text[k] ==
'"' && k + 1 < text.size() && text[k + 1] ==
'"')
437 res = ossia::value{std::move(str)};
442 out = std::move(*res);
443 if(
auto t = param.get_value_type(); out.get_type() != t)
445 ossia::convert(out, t);
452 parse_cell(
const auto& cell, std::string& v, std::vector<ossia::value>& vec,
int i)
454 if(
auto param = m_map[i])
458 parse_cell_impl(v, *param, vec[i]);
463 void parse_row_no_timestamps(
int columns,
auto& row, std::string& v)
465 auto& vec = this->m_vec_no_ts.emplace_back(columns);
468 for(
auto it = row.begin(); it != row.end(); ++it)
470 parse_cell(*it, v, vec, i);
475 void parse_row_with_timestamps(
int columns,
auto& row, std::string& v)
477 if(row.length() <= 1)
480 auto it = row.begin();
481 const auto& ts = *it;
485 auto tstamp = ossia::parse_strict<int64_t>(v);
489 auto& vec = this->m_vec_ts[*tstamp];
490 vec.resize(columns - 1);
493 for(++it; it != row.end(); ++it)
495 parse_cell(*it, v, vec, i);
502 if(first_is_timestamp)
507 using namespace std::chrono;
508 auto ts = duration_cast<milliseconds>(steady_clock::now() - first_ts).count();
510 ts %= m_vec_ts.rbegin()->first + 1;
515 if(m_vec_no_ts.empty())
518 using namespace std::chrono;
519 if(loops && nots_index >= std::ssize(m_vec_no_ts))
521 read_no_ts(nots_index++);
525 void read_no_ts(int64_t timestamp)
529 if(timestamp >= std::ssize(m_vec_no_ts))
531 auto it = m_vec_no_ts.begin() + timestamp;
532 if(it != m_vec_no_ts.end())
539 if(
auto p = m_map.find(i); p != m_map.end())
541 p->second->push_value(v);
549 void read_ts(int64_t timestamp)
551 auto it = m_vec_ts.lower_bound(timestamp);
552 if(it != m_vec_ts.end())
554 if(it != m_vec_ts.begin())
557 for(
auto& v : it->second)
561 if(
auto p = m_map.find(i); p != m_map.end())
563 p->second->push_value(v);
572 for(
auto& v : m_vec_ts.rbegin()->second)
576 if(
auto p = m_map.find(i); p != m_map.end())
578 p->second->push_value(v);
616 std::shared_ptr<recorder_thread> recorder;
617 std::shared_ptr<player_thread> player;
619 std::vector<ossia::net::node_base*> roots;
620 bool first_is_timestamp{};
621 Separator separator{};
626 swap(recorder->filename, path);
627 swap(recorder->roots, roots);
628 player->filename = recorder->filename;
629 player->roots = recorder->roots;
630 player->first_is_timestamp = first_is_timestamp;
631 player->setSeparator(separator);
633 recorder->first_is_timestamp = first_is_timestamp;
634 recorder->setSeparator(separator);