MediaFileHandle.hpp
1 #pragma once
2 #include <Media/AudioDecoder.hpp>
3 #include <Media/SndfileDecoder.hpp>
4 
5 #include <score/tools/std/StringHash.hpp>
6 
7 #include <ossia/audio/drwav_handle.hpp>
8 #include <ossia/detail/hash_map.hpp>
9 #include <ossia/detail/nullable_variant.hpp>
10 #include <ossia/detail/small_vector.hpp>
11 
12 #include <QFile>
13 
14 #include <nano_signal_slot.hpp>
15 #include <score_plugin_media_export.h>
16 
17 #include <array>
18 #include <verdigris>
19 namespace ossia
20 {
21 struct libav_handle;
22 }
23 namespace score
24 {
25 struct DocumentContext;
26 }
27 namespace Media
28 {
29 // Remove the compile time overhead of std::pair for this
30 struct FloatPair
31 {
32  float first, second;
33 };
34 struct RMSData;
36 static constexpr inline int64_t abs_max(int64_t f1, int64_t f2) noexcept
37 {
38  return f2 >= 0 ? f1 < f2 ? f2 : f1 : f1 < -f2 ? f2 : f1;
39 }
40 #if defined(__clang__)
41 static constexpr inline float abs_max(float f1, float f2) noexcept
42 {
43  const int mul = (f2 >= 0.f) ? 1 : -1;
44  if(f1 < mul * f2)
45  return f2;
46  else
47  return f1;
48 }
49 #else
50 static constexpr inline float abs_max(float f1, float f2) noexcept
51 {
52  return f2 >= 0.f ? f1 < f2 ? f2 : f1 : f1 < -f2 ? f2 : f1;
53 }
54 #endif
55 
56 enum class DecodingMethod
57 {
58  Invalid,
59  Mmap,
60  Libav,
61  Sndfile,
62  LibavStream
63 };
64 
66 {
67  QString filePath;
68  QString absoluteFilePath;
69  DecodingMethod method{};
70  int track{-1};
71 };
72 
73 struct SCORE_PLUGIN_MEDIA_EXPORT AudioFile final : public QObject
74 {
75 public:
76  static bool isSupported(const QFile& f);
77 
78  AudioFile();
79  ~AudioFile() override;
80 
81  void load(DecodingSetup opts);
82 
84  QString originalFile() const { return m_originalFile; }
85 
87  QString fileName() const { return m_fileName; }
88 
90  QString absoluteFileName() const { return m_file; }
91 
92  int sampleRate() const { return m_sampleRate; }
93 
94  int64_t decodedSamples() const;
95 
96  // Number of samples in a channel.
97  int64_t samples() const;
98  int64_t channels() const;
99 
100  bool empty() const { return channels() == 0 || samples() == 0; }
101  bool finishedDecoding() const noexcept { return m_fullyDecoded; }
102 
103  const RMSData& rms() const;
104 
106  ossia::audio_array getAudioArray() const;
107 
108  Nano::Signal<void()> on_mediaChanged;
109  Nano::Signal<void()> on_newData;
110  Nano::Signal<void()> on_finishedDecoding;
111 
112  struct MmapReader
113  {
114  std::shared_ptr<QFile> file;
115  void* data{};
116  ossia::drwav_handle wav;
117  };
118 
119  struct RAMReader
120  {
121  ossia::audio_handle handle;
122  ossia::small_vector<ossia::audio_sample*, 8> data;
123  };
124 
126  {
127  explicit LibavReader(int rate) noexcept
128  : decoder{rate}
129  {
130  }
131  AudioDecoder decoder;
132  };
134  {
135  std::string path;
136  int stream{-1};
137  int64_t samples{};
138  int channels{};
139  };
141  {
142  SndfileDecoder decoder;
143  };
144 
145  using libav_ptr = std::shared_ptr<LibavReader>;
147  using mmap_ptr = MmapReader;
148  using sndfile_ptr = SndfileReader;
149  using impl_t
150  = ossia::nullable_variant<mmap_ptr, libav_ptr, sndfile_ptr, libav_stream_ptr>;
151 
152  struct MmapView
153  {
154  // Copy for thread-safety reasons
155  ossia::drwav_handle wav;
156  };
157 
158  struct StreamView
159  {
160  std::shared_ptr<ossia::libav_handle> handle;
161  };
162 
163  struct RAMView
164  {
165  ossia::small_vector<audio_sample*, 8> data;
166  };
167 
168  struct Handle : impl_t
169  {
170  using impl_t::impl_t;
171  Handle(mmap_ptr&& ptr)
172  : impl_t{std::move(ptr)}
173  {
174  }
175  Handle(libav_ptr&& ptr)
176  : impl_t{std::move(ptr)}
177  {
178  }
179  Handle(libav_stream_ptr&& ptr)
180  : impl_t{std::move(ptr)}
181  {
182  }
183  Handle(sndfile_ptr&& ptr)
184  : impl_t{std::move(ptr)}
185  {
186  }
187  Handle& operator=(mmap_ptr&& ptr)
188  {
189  ((impl_t&)*this) = std::move(ptr);
190  return *this;
191  }
192  Handle& operator=(libav_ptr&& ptr)
193  {
194  ((impl_t&)*this) = std::move(ptr);
195  return *this;
196  }
197  Handle& operator=(libav_stream_ptr&& ptr)
198  {
199  ((impl_t&)*this) = std::move(ptr);
200  return *this;
201  }
202  Handle& operator=(sndfile_ptr&& ptr)
203  {
204  ((impl_t&)*this) = std::move(ptr);
205  return *this;
206  }
207  };
208 
209  using view_impl_t = ossia::nullable_variant<MmapView, RAMView, StreamView>;
210  struct ViewHandle : view_impl_t
211  {
212  using view_impl_t::view_impl_t;
213  ViewHandle(const Handle&);
214 
215  void frame(int64_t start_frame, ossia::small_vector<float, 8>& out) noexcept;
216  void absmax_frame(
217  int64_t start_frame, int64_t end_frame,
218  ossia::small_vector<float, 8>& out) noexcept;
219  void minmax_frame(
220  int64_t start_frame, int64_t end_frame,
221  ossia::small_vector<FloatPair, 8>& out) noexcept;
222  };
223 
224  // Note : this is a copy, because it's not thread safe.
225  ViewHandle handle() const noexcept { return m_impl; }
226 
227  const Handle& unsafe_handle() const noexcept { return m_impl; }
228 
229  std::optional<double> knownTempo() const noexcept;
230 
231 private:
232  void load_libav(int rate);
233  void load_libav_stream();
234  void load_drwav();
235  void load_sndfile();
236 
237  friend class SoundComponentSetup;
238 
239  QString m_originalFile;
240  QString m_file;
241  QString m_fileName;
242  int m_track{-1};
243 
244  RMSData* m_rms{};
245  int m_sampleRate{};
246  bool m_fullyDecoded{};
247 
248  Handle m_impl;
249 };
250 
251 class SCORE_PLUGIN_MEDIA_EXPORT AudioFileManager final : public QObject
252 {
253 public:
254  AudioFileManager() noexcept;
255  ~AudioFileManager() noexcept;
256 
257  static AudioFileManager& instance() noexcept;
258  std::shared_ptr<AudioFile>
259  get(const QString&, int stream, const score::DocumentContext&);
260 
261  std::shared_ptr<AudioFile> get(const QString& absolutePath, int stream);
262 
263 private:
264  struct StreamInfo
265  {
266  struct hash
267  {
268  std::size_t operator()(const StreamInfo& f) const noexcept
269  {
270  constexpr const QtPrivate::QHashCombine combine;
271  std::size_t h = 0;
272  h = combine(h, f.file);
273  h = combine(h, f.stream);
274  return h;
275  }
276  };
277  bool operator==(const StreamInfo& other) const noexcept
278  {
279  return file == other.file && stream == other.stream;
280  }
281  QString file;
282  int stream{-1};
283  };
284  ossia::hash_map<StreamInfo, std::shared_ptr<AudioFile>, StreamInfo::hash> m_handles;
285 };
286 
290 SCORE_PLUGIN_MEDIA_EXPORT
291 void writeAudioArrayToFile(const QString& path, const ossia::audio_array& arr, int fs);
292 
293 std::optional<double> estimateTempo(const AudioFile& file);
294 std::optional<double> estimateTempo(const QString& filePath);
295 
296 std::optional<AudioInfo> probe(const QString& path);
297 
298 }
299 
300 Q_DECLARE_METATYPE(std::shared_ptr<Media::AudioFile>)
301 W_REGISTER_ARGTYPE(std::shared_ptr<Media::AudioFile>)
302 Q_DECLARE_METATYPE(const Media::AudioFile*)
303 W_REGISTER_ARGTYPE(const Media::AudioFile*)
Definition: AudioDecoder.hpp:37
Definition: MediaFileHandle.hpp:252
Definition: SndfileDecoder.hpp:9
Definition: SoundComponent.cpp:37
Base toolkit upon which the software is built.
Definition: Application.cpp:90
Definition: MediaFileHandle.hpp:169
Definition: MediaFileHandle.hpp:126
Definition: MediaFileHandle.hpp:134
Definition: MediaFileHandle.hpp:113
Definition: MediaFileHandle.hpp:153
Definition: MediaFileHandle.hpp:120
Definition: MediaFileHandle.hpp:164
Definition: MediaFileHandle.hpp:141
Definition: MediaFileHandle.hpp:159
Definition: MediaFileHandle.hpp:211
Definition: MediaFileHandle.hpp:74
QString fileName() const
Actual filename.
Definition: MediaFileHandle.hpp:87
QString originalFile() const
The text passed to the load function.
Definition: MediaFileHandle.hpp:84
QString absoluteFileName() const
Absolute resolved filename.
Definition: MediaFileHandle.hpp:90
Definition: MediaFileHandle.hpp:267
Definition: MediaFileHandle.hpp:66
Definition: MediaFileHandle.hpp:31
Definition: RMSData.hpp:16
Definition: DocumentContext.hpp:18