AudioRecorder.hpp
1 #pragma once
2 #include <ossia/audio/drwav_write_handle.hpp>
3 #include <ossia/network/value/detail/value_conversion_impl.hpp>
4 
5 #include <QDateTime>
6 
7 #include <AvndProcesses/AddressTools.hpp>
8 #include <halp/audio.hpp>
9 #include <halp/callback.hpp>
10 
11 namespace avnd_tools
12 {
18 {
19  halp_meta(name, "Audio recorder")
20  halp_meta(author, "ossia team")
21  halp_meta(category, "Audio/Recording")
22  halp_meta(description, "Records audio to a file")
23  halp_meta(c_name, "avnd_audio_recorder")
24  halp_meta(uuid, "4463dddc-6acf-4106-a680-fed67d8030da")
25 
26  using audio_buffer = boost::container::vector<double>;
27 
28  // Threaded worker
30  {
31  ossia::drwav_write_handle f;
32  std::string filename;
33  std::string actual_filename;
34  int channels = 0;
35  int rate = 0;
36  bool must_record = false;
37 
38  bool reopen()
39  {
40  if(must_record)
41  {
42  // Open the file with the correct substitutions
43  auto fname = QByteArray::fromStdString(this->filename);
44  fname.replace("%t", QDateTime::currentDateTimeUtc().toString().toUtf8());
45  actual_filename = fname.toStdString();
46  f.open(actual_filename, channels, rate, 16);
47  return false;
48  }
49  else
50  {
51  bool must_reply = false;
52  if(f.is_open() && f.written_frames() > 0)
53  {
54  must_reply = true;
55  }
56  f.close();
57  return must_reply;
58  }
59  }
60 
61  void write(int frames, audio_buffer& data)
62  {
63  int these_channels = data.size() / frames;
64  if(these_channels != channels)
65  {
66  channels = these_channels;
67  reopen();
68  }
69 
70  if(!f.is_open())
71  return;
72 
73  // Put addresses to the first sample of each channel in an array,
74  // e.g. arr[0] points to the first channel, arr[1] to the second, etc.
75  const double** arr = (const double**)alloca(channels * sizeof(double*));
76  for(int c = 0; c < channels; ++c)
77  arr[c] = data.data() + c * frames;
78 
79  // Write the data
80  f.write_pcm_frames(frames, arr);
81  }
82  };
83  std::shared_ptr<recorder_thread> impl = std::make_shared<recorder_thread>();
84 
86  {
87  std::string path;
88  int rate{};
89  bool must_record{};
90  bool operator()(recorder_thread& self)
91  {
92  using namespace std;
93  swap(self.filename, path);
94  self.rate = rate;
95  self.must_record = must_record;
96  return self.reopen();
97  }
98  };
99 
101  {
102  int frames{};
103  audio_buffer data;
104  bool operator()(recorder_thread& self)
105  {
106  self.write(frames, data);
107  return false;
108  }
109  };
110  using worker_message = ossia::variant<reset_message, process_message>;
111 
112  struct
113  {
114  std::function<void(std::shared_ptr<recorder_thread>, worker_message)> request;
115  static std::function<void(AudioRecorder&)>
116  work(std::shared_ptr<recorder_thread> t, worker_message&& mess)
117  {
118  bool reply = ossia::visit(
119  [&]<typename M>(M&& msg) { return std::forward<M>(msg)(*t); },
120  std::move(mess));
121  if(reply)
122  {
123  return [filename = t->actual_filename](AudioRecorder& self) mutable {
124  using namespace std;
125  swap(self.filename_to_output, filename);
126  };
127  }
128  else
129  {
130  return {};
131  }
132  }
133  } worker;
134 
135  // Object definition
136  struct
137  {
138  halp::dynamic_audio_bus<"Audio", double> audio;
139  struct : halp::lineedit<"File pattern", "">
140  {
141  void update(AudioRecorder& self) { self.update(); }
142  } filename;
143 
144  struct : halp::toggle<"Record">
145  {
146  void update(AudioRecorder& self)
147  {
148  if(prev != value)
149  {
150  prev = value;
151  self.update();
152  }
153  }
154  bool prev{false};
155  } record;
156  } inputs;
157 
158  struct
159  {
160  halp::callback<"Filename", std::string> finished;
161  } outputs;
162 
163  int current_rate = 0;
164  void prepare(halp::setup s) { current_rate = s.rate; }
165 
166  void update()
167  {
168  worker.request(impl, reset_message{inputs.filename, current_rate, inputs.record});
169  }
170 
171  void operator()(int frames)
172  {
173  if(!std::exchange(started, true))
174  update();
175 
176  data.resize(inputs.audio.channels * frames, boost::container::default_init);
177  for(int c = 0; c < inputs.audio.channels; c++)
178  {
179  double* src = inputs.audio.samples[c];
180  double* ptr = data.data() + c * frames;
181  for(int i = 0; i < frames; i++)
182  ptr[i] = src[i];
183  }
184 
185  worker.request(impl, process_message{frames, std::move(data)});
186 
187  if(!filename_to_output.empty())
188  {
189  outputs.finished(filename_to_output);
190  filename_to_output.clear();
191  }
192  }
193 
194  bool started{};
195  std::optional<int64_t> first_message_sent_pos;
196  std::optional<int64_t> last_message_sent_pos;
197 
198  audio_buffer data;
199  std::string filename_to_output;
200 };
201 
202 }
Definition: AudioRecorder.hpp:101
Definition: AudioRecorder.hpp:30
Definition: AudioRecorder.hpp:86
Definition: AudioRecorder.hpp:18