WSJT-X/Audio/BWFFile.hpp

211 lines
7.1 KiB
C++

#ifndef BWF_FILE_HPP__
#define BWF_FILE_HPP__
#include <array>
#include <QFile>
#include <QMap>
#include <QByteArray>
#include "pimpl_h.hpp"
class QObject;
class QString;
class QAudioFormat;
//
// BWFFile - Broadcast Wave Format File (a.k.a. WAV file)
//
// The BWF file format is a backward compatible variation of the
// Microsoft WAV file format. It contains an extra chunk with id
// 'bext' that contains metadata defined by the EBU in:
//
// https://tech.ebu.ch/docs/tech/tech3285.pdf
//
// Also relevant is the recommendation document:
//
// https://tech.ebu.ch/docs/r/r098.pdf
//
// which suggests a format to the free text coding history field.
//
// This class also supports the LIST-INFO chunk type which also allows
// metadata to be added to a WAV file, the defined INFO tag ids are
// documented here:
//
// http://bwfmetaedit.sourceforge.net/listinfo.html
//
// These ids are not enforced but they are recommended as most
// operating systems and audio applications recognize some or more of
// them. Notably Microsoft Windows is not one of the operating systems
// that does :( In fact there seems to be no documented metadata
// tagging format that Windows Explorer recognizes.
//
// Changes to the 'bext' fields and the LIST-INFO dictionary may be
// made right up until the file is closed as the relevant chunks are
// saved to the end of the file after the end of the sample data.
//
// This class emulates the QFile class, in fact it uses a QFile object
// instance internally and forwards many of its operations directly to
// it.
//
// BWFFile is a QIODevice subclass and the implementation provides
// access to the audio sample data contained in the BWF file as if
// only that data were in the file. I.e. the first sample is at file
// offset zero and the size of the file is the size of the sample
// data. The headers, trailers and metadata are hidden but can be
// accessed by the operations below.
//
class BWFFile
: public QIODevice
{
Q_OBJECT
public:
using FileHandleFlags = QFile::FileHandleFlags;
using Permissions = QFile::Permissions;
using FileError = QFile::FileError;
using MemoryMapFlags = QFile::MemoryMapFlags;
using InfoDictionary = QMap<std::array<char, 4>, QByteArray>;
using UMID = std::array<quint8, 64>;
explicit BWFFile (QAudioFormat const&, QObject * parent = nullptr);
explicit BWFFile (QAudioFormat const&, QString const& name,
QObject * parent = nullptr);
// The InfoDictionary should contain valid WAV format LIST-INFO
// identifiers as keys, a list of them can be found here:
//
// http://bwfmetaedit.sourceforge.net/listinfo.html
//
// For files opened for ReadOnly access the dictionary is not
// written to the file. For files opened ReadWrite, any existing
// LIST-INFO tags will be merged into the dictionary when the file
// is opened and if the file is modified the merged dictionary will
// be written back to the file.
//
// Note that the sample data may no be in the native endian, it is
// the callers responsibility to do any required endian
// conversions. The internal data is always in native endian with
// conversions being handled automatically. Use the BWF::format()
// operation to access the format including the
// QAudioFormat::byteOrder() operation to determine the data byte
// ordering.
//
explicit BWFFile (QAudioFormat const&, QString const& name,
InfoDictionary const&, QObject * parent = nullptr);
~BWFFile ();
QAudioFormat const& format () const;
InfoDictionary& list_info ();
//
// Broadcast Audio Extension fields
//
// If any of these modifiers are called then a "bext" chunk will be
// written to the file if the file is writeable and the sample data
// is modified.
//
enum class BextVersion : quint16 {v_0, v_1, v_2};
BextVersion bext_version () const;
void bext_version (BextVersion = BextVersion::v_2);
QByteArray bext_description () const;
void bext_description (QByteArray const&); // max 256 bytes
QByteArray bext_originator () const;
void bext_originator (QByteArray const&); // max 32 bytes
QByteArray bext_originator_reference () const;
void bext_originator_reference (QByteArray const&); // max 32 bytes
QDateTime bext_origination_date_time () const;
void bext_origination_date_time (QDateTime const&); // 1s resolution
quint64 bext_time_reference () const;
void bext_time_reference (quint64); // samples since midnight at start
UMID bext_umid () const; // bext version >= 1 only
void bext_umid (UMID const&);
quint16 bext_loudness_value () const;
void bext_loudness_value (quint16); // bext version >= 2 only
quint16 bext_loudness_range () const;
void bext_loudness_range (quint16); // bext version >= 2 only
quint16 bext_max_true_peak_level () const;
void bext_max_true_peak_level (quint16); // bext version >= 2 only
quint16 bext_max_momentary_loudness () const;
void bext_max_momentary_loudness (quint16); // bext version >= 2 only
quint16 bext_max_short_term_loudness () const;
void bext_max_short_term_loudness (quint16); // bext version >= 2 only
QByteArray bext_coding_history () const;
void bext_coding_history (QByteArray const&); // See EBU R 98
// Emulate QFile interface
bool open (OpenMode) override;
bool open (FILE *, OpenMode, FileHandleFlags = QFile::DontCloseHandle);
bool open (int fd, OpenMode, FileHandleFlags = QFile::DontCloseHandle);
bool copy (QString const& new_name);
bool exists () const;
bool link (QString const& link_name);
bool remove ();
bool rename (QString const& new_name);
void setFileName (QString const& name);
QString symLinkTarget () const;
QString fileName () const;
Permissions permissions () const;
// Resize is of the sample data portion, header and trailer chunks
// are excess to the given size
bool resize (qint64 new_size);
bool setPermissions (Permissions permissions);
FileError error () const;
bool flush ();
int handle () const;
// The mapping offset is relative to the start of the sample data
uchar * map (qint64 offset, qint64 size,
MemoryMapFlags = QFile::NoOptions);
bool unmap (uchar * address);
void unsetError ();
//
// QIODevice implementation
//
// The size returned is of the sample data only, header and trailer
// chunks are hidden and handled internally
qint64 size () const override;
bool isSequential () const override;
// The reset operation clears the 'bext' and LIST-INFO as if they
// were never supplied. If the file is writable the 'bext' and
// LIST-INFO chunks will not be written making the resulting file a
// lowest common denominator WAV file.
bool reset () override;
// Seek offsets are relative to the start of the sample data
bool seek (qint64) override;
// this can fail due to updating header issues, errors are ignored
void close () override;
protected:
qint64 readData (char * data, qint64 max_size) override;
qint64 writeData (char const* data, qint64 max_size) override;
private:
class impl;
pimpl<impl> m_;
};
#endif