1 #ifndef BWF_FILE_HPP__
2 #define BWF_FILE_HPP__
3 
4 #include <array>
5 
6 #include <QFile>
7 #include <QMap>
8 #include <QByteArray>
9 
10 #include "pimpl_h.hpp"
11 
12 class QObject;
13 class QString;
14 class QAudioFormat;
15 
16 //
17 // BWFFile - Broadcast Wave Format File (a.k.a. WAV file)
18 //
19 // The  BWF file  format is  a  backward compatible  variation of  the
20 // Microsoft  WAV file  format. It  contains  an extra  chunk with  id
21 // 'bext' that contains metadata defined by the EBU in:
22 //
23 //  https://tech.ebu.ch/docs/tech/tech3285.pdf
24 //
25 // Also relevant is the recommendation document:
26 //
27 //  https://tech.ebu.ch/docs/r/r098.pdf
28 //
29 // which suggests a format to the free text coding history field.
30 //
31 // This class also supports the LIST-INFO chunk type which also allows
32 // metadata to be  added to a WAV  file, the defined INFO  tag ids are
33 // documented here:
34 //
35 //  http://bwfmetaedit.sourceforge.net/listinfo.html
36 //
37 // These  ids  are not  enforced  but  they  are recommended  as  most
38 // operating systems and audio applications  recognize some or more of
39 // them. Notably Microsoft Windows is not one of the operating systems
40 // that  does :(  In fact  there seems  to be  no documented  metadata
41 // tagging format that Windows Explorer recognizes.
42 //
43 // Changes to  the 'bext' fields  and the LIST-INFO dictionary  may be
44 // made right up  until the file is closed as  the relevant chunks are
45 // saved to the end of the file after the end of the sample data.
46 //
47 // This class emulates the QFile class, in fact it uses a QFile object
48 // instance internally and forwards many of its operations directly to
49 // it.
50 //
51 // BWFFile  is a  QIODevice subclass  and the  implementation provides
52 // access to  the audio sample  data contained in  the BWF file  as if
53 // only that data were  in the file. I.e. the first  sample is at file
54 // offset zero  and the  size of the  file is the  size of  the sample
55 // data.  The headers,  trailers and  metadata are  hidden but  can be
56 // accessed by the operations below.
57 //
58 class BWFFile
59   : public QIODevice
60 {
61   Q_OBJECT
62 public:
63   using FileHandleFlags = QFile::FileHandleFlags;
64   using Permissions = QFile::Permissions;
65   using FileError = QFile::FileError;
66   using MemoryMapFlags = QFile::MemoryMapFlags;
67   using InfoDictionary = QMap<std::array<char, 4>, QByteArray>;
68   using UMID = std::array<quint8, 64>;
69 
70   explicit BWFFile (QAudioFormat const&, QObject * parent = nullptr);
71   explicit BWFFile (QAudioFormat const&, QString const& name,
72                     QObject * parent = nullptr);
73 
74   // The  InfoDictionary should  contain  valid  WAV format  LIST-INFO
75   // identifiers as keys, a list of them can be found here:
76   //
77   // http://bwfmetaedit.sourceforge.net/listinfo.html
78   //
79   // For  files  opened for  ReadOnly  access  the dictionary  is  not
80   // written to  the file.  For  files opened ReadWrite,  any existing
81   // LIST-INFO tags will  be merged into the dictionary  when the file
82   // is opened and if the file  is modified the merged dictionary will
83   // be written back to the file.
84   //
85   // Note that the sample  data may no be in the  native endian, it is
86   // the   callers   responsibility   to  do   any   required   endian
87   // conversions. The  internal data is  always in native  endian with
88   // conversions  being handled  automatically. Use  the BWF::format()
89   // operation     to    access     the    format     including    the
90   // QAudioFormat::byteOrder()  operation to  determine the  data byte
91   // ordering.
92   //
93   explicit BWFFile (QAudioFormat const&, QString const& name,
94                     InfoDictionary const&, QObject * parent = nullptr);
95 
96   ~BWFFile ();
97   QAudioFormat const& format () const;
98   InfoDictionary& list_info ();
99 
100   //
101   // Broadcast Audio Extension fields
102   //
103   // If any of these modifiers are  called then a "bext" chunk will be
104   // written to the file if the  file is writeable and the sample data
105   // is modified.
106   //
107   enum class BextVersion : quint16 {v_0, v_1, v_2};
108   BextVersion bext_version () const;
109   void bext_version (BextVersion = BextVersion::v_2);
110 
111   QByteArray bext_description () const;
112   void bext_description (QByteArray const&); // max 256 bytes
113 
114   QByteArray bext_originator () const;
115   void bext_originator (QByteArray const&);        // max 32 bytes
116 
117   QByteArray bext_originator_reference () const;
118   void bext_originator_reference (QByteArray const&); // max 32 bytes
119 
120   QDateTime bext_origination_date_time () const;
121   void bext_origination_date_time (QDateTime const&); // 1s resolution
122 
123   quint64 bext_time_reference () const;
124   void bext_time_reference (quint64); // samples since midnight at start
125 
126   UMID bext_umid () const; // bext version >= 1 only
127   void bext_umid (UMID const&);
128 
129   quint16 bext_loudness_value () const;
130   void bext_loudness_value (quint16); // bext version >= 2 only
131 
132   quint16 bext_loudness_range () const;
133   void bext_loudness_range (quint16); // bext version >= 2 only
134 
135   quint16 bext_max_true_peak_level () const;
136   void bext_max_true_peak_level (quint16); // bext version >= 2 only
137 
138   quint16 bext_max_momentary_loudness () const;
139   void bext_max_momentary_loudness (quint16); // bext version >= 2 only
140 
141   quint16 bext_max_short_term_loudness () const;
142   void bext_max_short_term_loudness (quint16); // bext version >= 2 only
143 
144   QByteArray bext_coding_history () const;
145   void bext_coding_history (QByteArray const&); // See EBU R 98
146 
147 
148   // Emulate QFile interface
149   bool open (OpenMode) override;
150   bool open (FILE *, OpenMode, FileHandleFlags = QFile::DontCloseHandle);
151   bool open (int fd, OpenMode, FileHandleFlags = QFile::DontCloseHandle);
152   bool copy (QString const& new_name);
153   bool exists () const;
154   bool link (QString const& link_name);
155   bool remove ();
156   bool rename (QString const& new_name);
157   void setFileName (QString const& name);
158   QString symLinkTarget () const;
159   QString fileName () const;
160   Permissions permissions () const;
161 
162   // Resize is of the sample data portion, header and trailer chunks
163   // are excess to the given size
164   bool resize (qint64 new_size);
165 
166   bool setPermissions (Permissions permissions);
167   FileError error () const;
168   bool flush ();
169   int handle () const;
170 
171   // The mapping offset is relative to the start of the sample data
172   uchar * map (qint64 offset, qint64 size,
173                MemoryMapFlags = QFile::NoOptions);
174   bool unmap (uchar * address);
175 
176   void unsetError ();
177 
178 
179   //
180   // QIODevice implementation
181   //
182 
183   // The size returned is of the sample data only, header and trailer
184   // chunks are hidden and handled internally
185   qint64 size () const override;
186 
187   bool isSequential () const override;
188 
189   // The reset  operation clears the  'bext' and LIST-INFO as  if they
190   // were  never supplied.  If the  file  is writable  the 'bext'  and
191   // LIST-INFO chunks will not be  written making the resulting file a
192   // lowest common denominator WAV file.
193   bool reset () override;
194 
195   // Seek offsets are relative to the start of the sample data
196   bool seek (qint64) override;
197 
198   // this can fail due to updating header issues, errors are ignored
199   void close () override;
200 
201 protected:
202   qint64 readData (char * data, qint64 max_size) override;
203   qint64 writeData (char const* data, qint64 max_size) override;
204 
205 private:
206   class impl;
207   pimpl<impl> m_;
208 };
209 
210 #endif
211