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