DeviceRecorder.hpp
1 #pragma once
2 #include <ossia/network/value/detail/value_conversion_impl.hpp>
3 
4 #include <QDateTime>
5 #include <QFile>
6 
7 #include <AvndProcesses/AddressTools.hpp>
8 #include <halp/audio.hpp>
9 namespace avnd_tools
10 {
17 {
18  halp_meta(name, "CSV recorder")
19  halp_meta(author, "ossia team")
20  halp_meta(category, "Control/Recording")
21  halp_meta(description, "Record the messages of a device at regular interval")
22  halp_meta(c_name, "avnd_device_recorder")
23  halp_meta(uuid, "7161ca22-5684-48f2-bde7-88933500a7fb")
24 
25  // Threaded worker
27  {
28  QFile f{};
29  std::string filename;
30  std::vector<ossia::net::node_base*> roots;
31  std::chrono::steady_clock::time_point first_ts;
32  fmt::memory_buffer buf;
33 
34  void reopen()
35  {
36  f.close();
37 
38  auto filename = QByteArray::fromStdString(this->filename);
39  filename.replace("%t", QDateTime::currentDateTimeUtc().toString().toUtf8());
40  f.setFileName(filename);
41  if(filename.isEmpty())
42  return;
43 
44  f.open(QIODevice::WriteOnly);
45  if(!f.isOpen())
46  return;
47 
48  f.write("timestamp");
49  for(auto in : this->roots)
50  {
51  if(auto p = in->get_parameter())
52  {
53  f.write(",");
54  f.write(QByteArray::fromStdString(p->get_node().osc_address()));
55  }
56  }
57  f.write("\n");
58  f.flush();
59 
60  first_ts = std::chrono::steady_clock::now();
61  buf.clear();
62  buf.reserve(512);
63  }
64 
65  void write()
66  {
67  if(!f.isOpen())
68  return;
69  using namespace std::chrono;
70  const auto ts = duration_cast<std::chrono::milliseconds>(
71  std::chrono::steady_clock::now() - first_ts)
72  .count();
73  write(ts);
74  }
75  void write(int64_t timestamp)
76  {
77  f.write(QString::number(timestamp).toUtf8());
78  for(auto in : this->roots)
79  {
80  if(auto p = in->get_parameter())
81  {
82  f.write(",");
83  buf.clear();
84 
85  ossia::apply(ossia::detail::fmt_writer{buf}, p->value());
86  f.write(buf.data(), buf.size());
87  }
88  }
89  f.write("\n");
90  f.flush();
91  }
92  };
93  std::shared_ptr<recorder_thread> impl = std::make_shared<recorder_thread>();
94 
96  {
97  std::string path;
98  std::vector<ossia::net::node_base*> roots;
99  void operator()(recorder_thread& self)
100  {
101  using namespace std;
102  swap(self.filename, path);
103  swap(self.roots, roots);
104  self.reopen();
105  }
106  };
107 
109  {
110  std::string path;
111  void operator()(recorder_thread& self)
112  {
113  using namespace std;
114  swap(self.filename, path);
115  self.reopen();
116  }
117  };
118 
120  {
121  void operator()(recorder_thread& self) { self.write(); }
122  };
123  using worker_message
124  = ossia::variant<reset_message, reset_path_message, process_message>;
125 
126  struct
127  {
128  std::function<void(std::shared_ptr<recorder_thread>, worker_message)> request;
129  static void work(std::shared_ptr<recorder_thread> t, worker_message&& mess)
130  {
131  ossia::visit(
132  [&]<typename M>(M&& msg) { std::forward<M>(msg)(*t); }, std::move(mess));
133  }
134  } worker;
135 
136  // Object definition
137  struct
138  {
139  PatternSelector pattern;
140  halp::time_chooser<"Interval"> time;
141  struct : halp::lineedit<"File pattern", "">
142  {
143  void update(DeviceRecorder& self)
144  {
145  self.worker.request(self.impl, reset_path_message{self.inputs.filename});
146  }
147  } filename;
148  } inputs;
149 
150  struct
151  {
152  } outputs;
153 
154  using tick = halp::tick_musical;
155 
156  void operator()(const halp::tick_musical& tk)
157  {
158  int64_t elapsed_ns = 0.;
159  if(!first_message_sent_pos)
160  first_message_sent_pos = tk.position_in_nanoseconds;
161  if(last_message_sent_pos)
162  elapsed_ns = tk.position_in_nanoseconds - *last_message_sent_pos;
163 
164  if(elapsed_ns > 0 && elapsed_ns < inputs.time.value * 1e9)
165  return;
166  last_message_sent_pos = tk.position_in_nanoseconds;
167 
168  if(!m_path)
169  return;
170 
171  if(!std::exchange(started, true))
172  {
173  inputs.pattern.reprocess();
174  worker.request(impl, reset_message{inputs.filename, roots});
175  }
176 
177  worker.request(impl, process_message{});
178  }
179 
180  bool started{};
181  std::optional<int64_t> first_message_sent_pos;
182  std::optional<int64_t> last_message_sent_pos;
183 };
184 
185 }
Definition: DeviceRecorder.hpp:120
Definition: DeviceRecorder.hpp:27
Definition: DeviceRecorder.hpp:96
Definition: DeviceRecorder.hpp:109
Definition: DeviceRecorder.hpp:17
Definition: AddressTools.hpp:20