mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
WavAudioFormat: Add support for writing 32 bit integral (PCM) format
This commit is contained in:
parent
34675235e5
commit
386daafe23
43 changed files with 677 additions and 125 deletions
|
|
@ -1031,4 +1031,20 @@ AudioFormatWriter* AiffAudioFormat::createWriterFor (OutputStream* out,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> AiffAudioFormat::createWriterFor (std::unique_ptr<OutputStream>& streamToWriteTo,
|
||||
const AudioFormatWriterOptions& options)
|
||||
{
|
||||
if (streamToWriteTo == nullptr || ! getPossibleBitDepths().contains (options.getBitsPerSample()))
|
||||
return nullptr;
|
||||
|
||||
StringPairArray metadata;
|
||||
metadata.addUnorderedMap (options.getMetadataValues());
|
||||
|
||||
return std::make_unique<AiffAudioFormatWriter> (std::exchange (streamToWriteTo, {}).release(),
|
||||
options.getSampleRate(),
|
||||
(unsigned int) options.getNumChannels(),
|
||||
(unsigned int) options.getBitsPerSample(),
|
||||
metadata);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -94,6 +94,10 @@ public:
|
|||
int bitsPerSample,
|
||||
const StringPairArray& metadataValues,
|
||||
int qualityOptionIndex) override;
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> createWriterFor (std::unique_ptr<OutputStream>& streamToWriteTo,
|
||||
const AudioFormatWriterOptions& options) override;
|
||||
|
||||
using AudioFormat::createWriterFor;
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -665,6 +665,13 @@ AudioFormatWriter* CoreAudioFormat::createWriterFor (OutputStream*,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> CoreAudioFormat::createWriterFor (std::unique_ptr<OutputStream>&,
|
||||
const AudioFormatWriterOptions&)
|
||||
{
|
||||
jassertfalse; // not yet implemented!
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -112,6 +112,10 @@ public:
|
|||
int bitsPerSample,
|
||||
const StringPairArray& metadataValues,
|
||||
int qualityOptionIndex) override;
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> createWriterFor (std::unique_ptr<OutputStream>& streamToWriteTo,
|
||||
const AudioFormatWriterOptions& options) override;
|
||||
|
||||
using AudioFormat::createWriterFor;
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -610,7 +610,7 @@ AudioFormatWriter* FlacAudioFormat::createWriterFor (OutputStream* out,
|
|||
if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample))
|
||||
{
|
||||
std::unique_ptr<FlacWriter> w (new FlacWriter (out, sampleRate, numberOfChannels,
|
||||
(uint32) bitsPerSample, qualityOptionIndex));
|
||||
(uint32) bitsPerSample, qualityOptionIndex));
|
||||
if (w->ok)
|
||||
return w.release();
|
||||
}
|
||||
|
|
@ -618,6 +618,24 @@ AudioFormatWriter* FlacAudioFormat::createWriterFor (OutputStream* out,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> FlacAudioFormat::createWriterFor (std::unique_ptr<OutputStream>& streamToWriteTo,
|
||||
const AudioFormatWriterOptions& options)
|
||||
{
|
||||
if (streamToWriteTo == nullptr || ! getPossibleBitDepths().contains (options.getBitsPerSample()))
|
||||
return nullptr;
|
||||
|
||||
auto writer = std::make_unique<FlacWriter> (std::exchange (streamToWriteTo, {}).release(),
|
||||
options.getSampleRate(),
|
||||
(uint32) options.getNumChannels(),
|
||||
(uint32) options.getBitsPerSample(),
|
||||
options.getQualityOptionIndex());
|
||||
|
||||
if (! writer->ok)
|
||||
return nullptr;
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
StringArray FlacAudioFormat::getQualityOptions()
|
||||
{
|
||||
return { "0 (Fastest)", "1", "2", "3", "4", "5 (Default)","6", "7", "8 (Highest quality)" };
|
||||
|
|
|
|||
|
|
@ -63,6 +63,9 @@ public:
|
|||
StringArray getQualityOptions() override;
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AudioFormatWriter> createWriterFor (std::unique_ptr<OutputStream>& streamToWriteTo,
|
||||
const AudioFormatWriterOptions& options) override;
|
||||
|
||||
AudioFormatReader* createReaderFor (InputStream* sourceStream,
|
||||
bool deleteStreamIfOpeningFails) override;
|
||||
|
||||
|
|
@ -72,6 +75,7 @@ public:
|
|||
int bitsPerSample,
|
||||
const StringPairArray& metadataValues,
|
||||
int qualityOptionIndex) override;
|
||||
|
||||
using AudioFormat::createWriterFor;
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -50,36 +50,39 @@ public:
|
|||
{
|
||||
WavAudioFormat wavFormat;
|
||||
|
||||
if (auto out = tempWav.getFile().createOutputStream())
|
||||
writer = wavFormat.createWriterFor (tempWav.getFile().createOutputStream(),
|
||||
AudioFormatWriter::Options{}.withSampleRate (sampleRateIn)
|
||||
.withNumChannels ((int) numberOfChannels)
|
||||
.withBitsPerSample (bitsPerSampleIn)
|
||||
.withMetadataValues (metadata));
|
||||
|
||||
if (writer == nullptr)
|
||||
return;
|
||||
|
||||
args.add (appFile.getFullPathName());
|
||||
|
||||
args.add ("--quiet");
|
||||
|
||||
if (cbrBitrate == 0)
|
||||
{
|
||||
writer.reset (wavFormat.createWriterFor (out.release(), sampleRateIn, numChannels,
|
||||
bitsPerSampleIn, metadata, 0));
|
||||
|
||||
args.add (appFile.getFullPathName());
|
||||
|
||||
args.add ("--quiet");
|
||||
|
||||
if (cbrBitrate == 0)
|
||||
{
|
||||
args.add ("--vbr-new");
|
||||
args.add ("-V");
|
||||
args.add (String (vbrLevel));
|
||||
}
|
||||
else
|
||||
{
|
||||
args.add ("--cbr");
|
||||
args.add ("-b");
|
||||
args.add (String (cbrBitrate));
|
||||
}
|
||||
|
||||
addMetadataArg (metadata, "id3title", "--tt");
|
||||
addMetadataArg (metadata, "id3artist", "--ta");
|
||||
addMetadataArg (metadata, "id3album", "--tl");
|
||||
addMetadataArg (metadata, "id3comment", "--tc");
|
||||
addMetadataArg (metadata, "id3date", "--ty");
|
||||
addMetadataArg (metadata, "id3genre", "--tg");
|
||||
addMetadataArg (metadata, "id3trackNumber", "--tn");
|
||||
args.add ("--vbr-new");
|
||||
args.add ("-V");
|
||||
args.add (String (vbrLevel));
|
||||
}
|
||||
else
|
||||
{
|
||||
args.add ("--cbr");
|
||||
args.add ("-b");
|
||||
args.add (String (cbrBitrate));
|
||||
}
|
||||
|
||||
addMetadataArg (metadata, "id3title", "--tt");
|
||||
addMetadataArg (metadata, "id3artist", "--ta");
|
||||
addMetadataArg (metadata, "id3album", "--tl");
|
||||
addMetadataArg (metadata, "id3comment", "--tc");
|
||||
addMetadataArg (metadata, "id3date", "--ty");
|
||||
addMetadataArg (metadata, "id3genre", "--tg");
|
||||
addMetadataArg (metadata, "id3trackNumber", "--tn");
|
||||
}
|
||||
|
||||
void addMetadataArg (const StringPairArray& metadata, const char* key, const char* lameFlag)
|
||||
|
|
@ -165,10 +168,6 @@ LAMEEncoderAudioFormat::LAMEEncoderAudioFormat (const File& lameApplication)
|
|||
{
|
||||
}
|
||||
|
||||
LAMEEncoderAudioFormat::~LAMEEncoderAudioFormat()
|
||||
{
|
||||
}
|
||||
|
||||
bool LAMEEncoderAudioFormat::canHandleFile (const File&)
|
||||
{
|
||||
return false;
|
||||
|
|
@ -208,6 +207,33 @@ AudioFormatReader* LAMEEncoderAudioFormat::createReaderFor (InputStream*, const
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> LAMEEncoderAudioFormat::createWriterFor (std::unique_ptr<OutputStream> streamToWriteTo,
|
||||
const AudioFormatWriterOptions& options)
|
||||
{
|
||||
if (streamToWriteTo == nullptr)
|
||||
return nullptr;
|
||||
|
||||
int vbr = 4;
|
||||
int cbr = 0;
|
||||
|
||||
const String qual (getQualityOptions() [options.getQualityOptionIndex()]);
|
||||
|
||||
if (qual.contains ("VBR"))
|
||||
vbr = qual.retainCharacters ("0123456789").getIntValue();
|
||||
else
|
||||
cbr = qual.getIntValue();
|
||||
|
||||
return std::make_unique<Writer> (streamToWriteTo.release(),
|
||||
getFormatName(),
|
||||
lameApp,
|
||||
vbr,
|
||||
cbr,
|
||||
options.getSampleRate(),
|
||||
(unsigned int) options.getNumChannels(),
|
||||
options.getBitsPerSample(),
|
||||
options.getMetadataValues());
|
||||
}
|
||||
|
||||
AudioFormatWriter* LAMEEncoderAudioFormat::createWriterFor (OutputStream* streamToWriteTo,
|
||||
double sampleRateToUse,
|
||||
unsigned int numberOfChannels,
|
||||
|
|
|
|||
|
|
@ -60,21 +60,24 @@ public:
|
|||
executable at the location given.
|
||||
*/
|
||||
LAMEEncoderAudioFormat (const File& lameExecutableToUse);
|
||||
~LAMEEncoderAudioFormat();
|
||||
|
||||
bool canHandleFile (const File&);
|
||||
Array<int> getPossibleSampleRates();
|
||||
Array<int> getPossibleBitDepths();
|
||||
bool canDoStereo();
|
||||
bool canDoMono();
|
||||
bool isCompressed();
|
||||
StringArray getQualityOptions();
|
||||
bool canHandleFile (const File&) override;
|
||||
Array<int> getPossibleSampleRates() override;
|
||||
Array<int> getPossibleBitDepths() override;
|
||||
bool canDoStereo() override;
|
||||
bool canDoMono() override;
|
||||
bool isCompressed() override;
|
||||
StringArray getQualityOptions() override;
|
||||
|
||||
AudioFormatReader* createReaderFor (InputStream*, bool deleteStreamIfOpeningFails);
|
||||
std::unique_ptr<AudioFormatWriter> createWriterFor (std::unique_ptr<OutputStream> streamToWriteTo,
|
||||
const AudioFormatWriterOptions& options) override;
|
||||
|
||||
AudioFormatReader* createReaderFor (InputStream*, bool deleteStreamIfOpeningFails) override;
|
||||
|
||||
AudioFormatWriter* createWriterFor (OutputStream*, double sampleRateToUse,
|
||||
unsigned int numberOfChannels, int bitsPerSample,
|
||||
const StringPairArray& metadataValues, int qualityOptionIndex);
|
||||
|
||||
using AudioFormat::createWriterFor;
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -3173,6 +3173,13 @@ AudioFormatReader* MP3AudioFormat::createReaderFor (InputStream* sourceStream, c
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> MP3AudioFormat::createWriterFor (std::unique_ptr<OutputStream>&,
|
||||
const AudioFormatWriterOptions&)
|
||||
{
|
||||
jassertfalse; // not yet implemented!
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AudioFormatWriter* MP3AudioFormat::createWriterFor (OutputStream*, double /*sampleRateToUse*/,
|
||||
unsigned int /*numberOfChannels*/, int /*bitsPerSample*/,
|
||||
const StringPairArray& /*metadataValues*/, int /*qualityOptionIndex*/)
|
||||
|
|
|
|||
|
|
@ -74,6 +74,10 @@ public:
|
|||
AudioFormatWriter* createWriterFor (OutputStream*, double sampleRateToUse,
|
||||
unsigned int numberOfChannels, int bitsPerSample,
|
||||
const StringPairArray& metadataValues, int qualityOptionIndex) override;
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> createWriterFor (std::unique_ptr<OutputStream>& streamToWriteTo,
|
||||
const AudioFormatWriterOptions& options) override;
|
||||
|
||||
using AudioFormat::createWriterFor;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -463,6 +463,28 @@ AudioFormatReader* OggVorbisAudioFormat::createReaderFor (InputStream* in, bool
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> OggVorbisAudioFormat::createWriterFor (std::unique_ptr<OutputStream>& streamToWriteTo,
|
||||
const AudioFormatWriterOptions& options)
|
||||
{
|
||||
if (streamToWriteTo == nullptr)
|
||||
return nullptr;
|
||||
|
||||
StringPairArray metadata;
|
||||
metadata.addUnorderedMap (options.getMetadataValues());
|
||||
|
||||
auto w = std::make_unique<OggWriter> (std::exchange (streamToWriteTo, {}).release(),
|
||||
options.getSampleRate(),
|
||||
(unsigned int) options.getNumChannels(),
|
||||
(unsigned int) options.getBitsPerSample(),
|
||||
options.getQualityOptionIndex(),
|
||||
metadata);
|
||||
|
||||
if (! w->ok)
|
||||
return nullptr;
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
AudioFormatWriter* OggVorbisAudioFormat::createWriterFor (OutputStream* out,
|
||||
double sampleRate,
|
||||
unsigned int numChannels,
|
||||
|
|
|
|||
|
|
@ -93,12 +93,16 @@ public:
|
|||
AudioFormatReader* createReaderFor (InputStream* sourceStream,
|
||||
bool deleteStreamIfOpeningFails) override;
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> createWriterFor (std::unique_ptr<OutputStream>& streamToWriteTo,
|
||||
const AudioFormatWriterOptions& options) override;
|
||||
|
||||
AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo,
|
||||
double sampleRateToUse,
|
||||
unsigned int numberOfChannels,
|
||||
int bitsPerSample,
|
||||
const StringPairArray& metadataValues,
|
||||
int qualityOptionIndex) override;
|
||||
|
||||
using AudioFormat::createWriterFor;
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -35,18 +35,6 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
using StringMap = std::unordered_map<String, String>;
|
||||
|
||||
static auto toMap (const StringPairArray& array)
|
||||
{
|
||||
StringMap result;
|
||||
|
||||
for (auto i = 0; i < array.size(); ++i)
|
||||
result[array.getAllKeys()[i]] = array.getAllValues()[i];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static auto getValueWithDefault (const StringMap& m, const String& key, const String& fallback = {})
|
||||
{
|
||||
const auto iter = m.find (key);
|
||||
|
|
@ -1599,30 +1587,36 @@ class WavAudioFormatWriter final : public AudioFormatWriter
|
|||
public:
|
||||
WavAudioFormatWriter (OutputStream* const out, const double rate,
|
||||
const AudioChannelSet& channelLayoutToUse, const unsigned int bits,
|
||||
const StringPairArray& metadataValues)
|
||||
const StringMap& metadataValues,
|
||||
AudioFormatWriterOptions::SampleFormat sampleFormat)
|
||||
: AudioFormatWriter (out, wavFormatName, rate, channelLayoutToUse, bits)
|
||||
{
|
||||
using namespace WavFileHelpers;
|
||||
using SampleFormat = AudioFormatWriterOptions::SampleFormat;
|
||||
|
||||
if (metadataValues.size() > 0)
|
||||
// The floating point format is only supported with a bit depth of 32
|
||||
jassert (sampleFormat != SampleFormat::floatingPoint || bits == 32);
|
||||
|
||||
usesFloatingPointData = bits == 32 && sampleFormat != SampleFormat::integral;
|
||||
|
||||
if (! metadataValues.empty())
|
||||
{
|
||||
// The meta data should have been sanitised for the WAV format.
|
||||
// If it was originally sourced from an AIFF file the MetaDataSource
|
||||
// key should be removed (or set to "WAV") once this has been done
|
||||
jassert (metadataValues.getValue ("MetaDataSource", "None") != "AIFF");
|
||||
jassert (metadataValues.count ("MetaDataSource") == 0
|
||||
|| metadataValues.at ("MetaDataSource") != "AIFF");
|
||||
|
||||
const auto map = toMap (metadataValues);
|
||||
|
||||
bwavChunk = BWAVChunk::createFrom (map);
|
||||
ixmlChunk = IXMLChunk::createFrom (map);
|
||||
axmlChunk = AXMLChunk::createFrom (map);
|
||||
smplChunk = SMPLChunk::createFrom (map);
|
||||
instChunk = InstChunk::createFrom (map);
|
||||
cueChunk = CueChunk ::createFrom (map);
|
||||
listChunk = ListChunk::createFrom (map);
|
||||
listInfoChunk = ListInfoChunk::createFrom (map);
|
||||
acidChunk = AcidChunk::createFrom (map);
|
||||
trckChunk = TracktionChunk::createFrom (map);
|
||||
bwavChunk = BWAVChunk::createFrom (metadataValues);
|
||||
ixmlChunk = IXMLChunk::createFrom (metadataValues);
|
||||
axmlChunk = AXMLChunk::createFrom (metadataValues);
|
||||
smplChunk = SMPLChunk::createFrom (metadataValues);
|
||||
instChunk = InstChunk::createFrom (metadataValues);
|
||||
cueChunk = CueChunk ::createFrom (metadataValues);
|
||||
listChunk = ListChunk::createFrom (metadataValues);
|
||||
listInfoChunk = ListInfoChunk::createFrom (metadataValues);
|
||||
acidChunk = AcidChunk::createFrom (metadataValues);
|
||||
trckChunk = TracktionChunk::createFrom (metadataValues);
|
||||
}
|
||||
|
||||
headerPosition = out->getPosition();
|
||||
|
|
@ -1773,8 +1767,12 @@ private:
|
|||
else
|
||||
{
|
||||
writeChunkHeader (chunkName ("fmt "), 16);
|
||||
output->writeShort (bitsPerSample < 32 ? (short) 1 /*WAVE_FORMAT_PCM*/
|
||||
: (short) 3 /*WAVE_FORMAT_IEEE_FLOAT*/);
|
||||
|
||||
constexpr short waveFormatIeeeFloat = 3;
|
||||
constexpr short waveFormatPcm = 1;
|
||||
|
||||
output->writeShort (usesFloatingPointData ? waveFormatIeeeFloat
|
||||
: waveFormatPcm);
|
||||
}
|
||||
|
||||
output->writeShort ((short) numChannels);
|
||||
|
|
@ -1789,7 +1787,7 @@ private:
|
|||
output->writeShort ((short) bitsPerSample); // wValidBitsPerSample
|
||||
output->writeInt (channelMask);
|
||||
|
||||
const ExtensibleWavSubFormat& subFormat = bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat;
|
||||
const ExtensibleWavSubFormat& subFormat = usesFloatingPointData ? IEEEFloatFormat : pcmFormat;
|
||||
|
||||
output->writeInt ((int) subFormat.data1);
|
||||
output->writeShort ((short) subFormat.data2);
|
||||
|
|
@ -1809,8 +1807,6 @@ private:
|
|||
writeChunk (trckChunk, chunkName ("Trkn"));
|
||||
|
||||
writeChunkHeader (chunkName ("data"), isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame));
|
||||
|
||||
usesFloatingPointData = (bitsPerSample == 32);
|
||||
}
|
||||
|
||||
static size_t chunkSize (const MemoryBlock& data) noexcept { return data.isEmpty() ? 0 : (8 + data.getSize()); }
|
||||
|
|
@ -2027,6 +2023,25 @@ MemoryMappedAudioFormatReader* WavAudioFormat::createMemoryMappedReader (FileInp
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> WavAudioFormat::createWriterFor (std::unique_ptr<OutputStream>& streamToWriteTo,
|
||||
const AudioFormatWriterOptions& options)
|
||||
{
|
||||
if (streamToWriteTo == nullptr || ! getPossibleBitDepths().contains (options.getBitsPerSample()))
|
||||
return nullptr;
|
||||
|
||||
const auto layout = options.getChannelLayout().value_or (WavFileHelpers::canonicalWavChannelSet (options.getNumChannels()));
|
||||
|
||||
if (! isChannelLayoutSupported (layout))
|
||||
return nullptr;
|
||||
|
||||
return std::make_unique<WavAudioFormatWriter> (std::exchange (streamToWriteTo, {}).release(),
|
||||
options.getSampleRate(),
|
||||
layout,
|
||||
(unsigned int) options.getBitsPerSample(),
|
||||
options.getMetadataValues(),
|
||||
options.getSampleFormat());
|
||||
}
|
||||
|
||||
AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, double sampleRate,
|
||||
unsigned int numChannels, int bitsPerSample,
|
||||
const StringPairArray& metadataValues, int qualityOptionIndex)
|
||||
|
|
@ -2044,44 +2059,39 @@ AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out,
|
|||
{
|
||||
if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample) && isChannelLayoutSupported (channelLayout))
|
||||
return new WavAudioFormatWriter (out, sampleRate, channelLayout,
|
||||
(unsigned int) bitsPerSample, metadataValues);
|
||||
(unsigned int) bitsPerSample, toMap (metadataValues),
|
||||
AudioFormatWriterOptions::SampleFormat::automatic);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
namespace WavFileHelpers
|
||||
{
|
||||
static bool slowCopyWavFileWithNewMetadata (const File& file, const StringPairArray& metadata)
|
||||
static bool slowCopyWavFileWithNewMetadata (const File& file, const StringMap& metadata)
|
||||
{
|
||||
TemporaryFile tempFile (file);
|
||||
WavAudioFormat wav;
|
||||
|
||||
std::unique_ptr<AudioFormatReader> reader (wav.createReaderFor (file.createInputStream().release(), true));
|
||||
|
||||
if (reader != nullptr)
|
||||
{
|
||||
std::unique_ptr<OutputStream> outStream (tempFile.getFile().createOutputStream());
|
||||
if (reader == nullptr)
|
||||
return false;
|
||||
|
||||
if (outStream != nullptr)
|
||||
{
|
||||
std::unique_ptr<AudioFormatWriter> writer (wav.createWriterFor (outStream.get(), reader->sampleRate,
|
||||
reader->numChannels, (int) reader->bitsPerSample,
|
||||
metadata, 0));
|
||||
std::unique_ptr<OutputStream> stream = tempFile.getFile().createOutputStream();
|
||||
auto writer = wav.createWriterFor (stream,
|
||||
AudioFormatWriter::Options{}.withSampleRate (reader->sampleRate)
|
||||
.withNumChannels ((int) reader->numChannels)
|
||||
.withBitsPerSample ((int) reader->bitsPerSample)
|
||||
.withMetadataValues (metadata));
|
||||
|
||||
if (writer != nullptr)
|
||||
{
|
||||
outStream.release();
|
||||
if (writer == nullptr)
|
||||
return false;
|
||||
|
||||
bool ok = writer->writeFromAudioReader (*reader, 0, -1);
|
||||
writer.reset();
|
||||
reader.reset();
|
||||
bool ok = writer->writeFromAudioReader (*reader, 0, -1);
|
||||
writer.reset();
|
||||
reader.reset();
|
||||
|
||||
return ok && tempFile.overwriteTargetFileWithTemporary();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return ok && tempFile.overwriteTargetFileWithTemporary();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2123,7 +2133,7 @@ bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPai
|
|||
}
|
||||
}
|
||||
|
||||
return slowCopyWavFileWithNewMetadata (wavFile, newMetadata);
|
||||
return slowCopyWavFileWithNewMetadata (wavFile, toMap (newMetadata));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2172,15 +2182,14 @@ struct WaveAudioFormatTests final : public UnitTest
|
|||
{
|
||||
beginTest ("Metadata can be written and read");
|
||||
|
||||
const auto newMetadata = getMetadataAfterReading (format, writeToBlock (format, metadataArray));
|
||||
const auto newMetadata = getMetadataAfterReading (format, writeToBlock (format, metadataValues));
|
||||
expect (newMetadata == metadataArray, "Somehow, the metadata is different!");
|
||||
}
|
||||
|
||||
{
|
||||
beginTest ("Files containing a riff info source and an empty ISRC associate the source with the riffInfoSource key");
|
||||
StringPairArray meta;
|
||||
meta.addMap ({ { WavAudioFormat::riffInfoSource, "customsource" },
|
||||
{ WavAudioFormat::internationalStandardRecordingCode, "" } });
|
||||
StringMap meta { { WavAudioFormat::riffInfoSource, "customsource" },
|
||||
{ WavAudioFormat::internationalStandardRecordingCode, "" } };
|
||||
const auto mb = writeToBlock (format, meta);
|
||||
checkPatternsPresent (mb, { "INFOISRC" });
|
||||
checkPatternsNotPresent (mb, { "ISRC:", "<ebucore" });
|
||||
|
|
@ -2192,8 +2201,7 @@ struct WaveAudioFormatTests final : public UnitTest
|
|||
{
|
||||
beginTest ("Files containing a riff info source and no ISRC associate the source with both keys "
|
||||
"for backwards compatibility");
|
||||
StringPairArray meta;
|
||||
meta.addMap ({ { WavAudioFormat::riffInfoSource, "customsource" } });
|
||||
StringMap meta { { WavAudioFormat::riffInfoSource, "customsource" } };
|
||||
const auto mb = writeToBlock (format, meta);
|
||||
checkPatternsPresent (mb, { "INFOISRC", "ISRC:customsource", "<ebucore" });
|
||||
const auto a = getMetadataAfterReading (format, mb);
|
||||
|
|
@ -2204,8 +2212,7 @@ struct WaveAudioFormatTests final : public UnitTest
|
|||
{
|
||||
beginTest ("Files containing an ISRC associate the value with the internationalStandardRecordingCode key "
|
||||
"and the riffInfoSource key for backwards compatibility");
|
||||
StringPairArray meta;
|
||||
meta.addMap ({ { WavAudioFormat::internationalStandardRecordingCode, "AABBBCCDDDDD" } });
|
||||
StringMap meta { { WavAudioFormat::internationalStandardRecordingCode, "AABBBCCDDDDD" } };
|
||||
const auto mb = writeToBlock (format, meta);
|
||||
checkPatternsPresent (mb, { "ISRC:AABBBCCDDDDD", "<ebucore" });
|
||||
checkPatternsNotPresent (mb, { "INFOISRC" });
|
||||
|
|
@ -2216,9 +2223,8 @@ struct WaveAudioFormatTests final : public UnitTest
|
|||
|
||||
{
|
||||
beginTest ("Files containing an ISRC and a riff info source associate the values with the appropriate keys");
|
||||
StringPairArray meta;
|
||||
meta.addMap ({ { WavAudioFormat::riffInfoSource, "source" } });
|
||||
meta.addMap ({ { WavAudioFormat::internationalStandardRecordingCode, "UUVVVXXYYYYY" } });
|
||||
StringMap meta { { WavAudioFormat::riffInfoSource, "source" },
|
||||
{ WavAudioFormat::internationalStandardRecordingCode, "UUVVVXXYYYYY" } };
|
||||
const auto mb = writeToBlock (format, meta);
|
||||
checkPatternsPresent (mb, { "INFOISRC", "ISRC:UUVVVXXYYYYY", "<ebucore" });
|
||||
const auto a = getMetadataAfterReading (format, mb);
|
||||
|
|
@ -2229,13 +2235,19 @@ struct WaveAudioFormatTests final : public UnitTest
|
|||
{
|
||||
beginTest ("Files containing ASWG metadata read and write correctly");
|
||||
MemoryBlock block;
|
||||
StringPairArray meta;
|
||||
StringMap meta;
|
||||
|
||||
for (const auto& key : WavFileHelpers::IXMLChunk::aswgMetadataKeys)
|
||||
meta.set (key, "Test123&<>");
|
||||
meta[key] = "Test123&<>";
|
||||
|
||||
{
|
||||
auto writer = rawToUniquePtr (WavAudioFormat().createWriterFor (new MemoryOutputStream (block, false), 48000, 1, 32, meta, 0));
|
||||
const auto writerOptions = AudioFormatWriterOptions{}.withSampleRate (48000.0)
|
||||
.withNumChannels (1)
|
||||
.withBitsPerSample (32)
|
||||
.withMetadataValues (meta);
|
||||
|
||||
std::unique_ptr<OutputStream> stream = std::make_unique<MemoryOutputStream> (block, false);
|
||||
auto writer = format.createWriterFor (stream, writerOptions);
|
||||
expect (writer != nullptr);
|
||||
}
|
||||
|
||||
|
|
@ -2270,9 +2282,8 @@ struct WaveAudioFormatTests final : public UnitTest
|
|||
auto reader = rawToUniquePtr (WavAudioFormat().createReaderFor (new MemoryInputStream (block, false), true));
|
||||
expect (reader != nullptr);
|
||||
|
||||
for (const auto& key : meta.getAllKeys())
|
||||
for (const auto& [key, oldValue] : meta)
|
||||
{
|
||||
const auto oldValue = meta.getValue (key, "!");
|
||||
const auto newValue = reader->metadataValues.getValue (key, "");
|
||||
expectEquals (oldValue, newValue);
|
||||
}
|
||||
|
|
@ -2280,22 +2291,128 @@ struct WaveAudioFormatTests final : public UnitTest
|
|||
expect (reader->metadataValues.getValue (WavAudioFormat::aswgVersion, "") == "3.01");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
beginTest ("Writing 32-bit integer samples should work");
|
||||
|
||||
const auto minimum = std::numeric_limits<int>::min();
|
||||
const auto maximum = std::numeric_limits<int>::max();
|
||||
|
||||
std::vector<int> dataIn { minimum,
|
||||
minimum + 1,
|
||||
minimum + 999,
|
||||
0,
|
||||
1,
|
||||
maximum - 1001,
|
||||
maximum - 1,
|
||||
maximum };
|
||||
|
||||
const int* ptr = dataIn.data();
|
||||
|
||||
WavFormatWriterTestData integralTestData { &ptr,
|
||||
dataIn.size(),
|
||||
AudioFormatWriterOptions::SampleFormat::integral };
|
||||
|
||||
auto* reader = integralTestData.getReader();
|
||||
|
||||
if (reader == nullptr)
|
||||
{
|
||||
expect (false, "WavFormatReader should be non-null");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<int> dataOut ((size_t) reader->lengthInSamples);
|
||||
int* const outPtr = dataOut.data();
|
||||
reader->read (&outPtr, 1, 0, (int) dataOut.size(), false);
|
||||
|
||||
expect (reader->usesFloatingPointData == false);
|
||||
expect (dataIn == dataOut);
|
||||
}
|
||||
|
||||
{
|
||||
beginTest ("Writing 32-bit float samples should work");
|
||||
|
||||
std::vector<float> dataIn { -1.0f,
|
||||
-0.8f,
|
||||
0.0f,
|
||||
0.8f,
|
||||
1.0f };
|
||||
|
||||
const int* ptr = (int*) dataIn.data();
|
||||
|
||||
WavFormatWriterTestData floatingPointTestData { &ptr,
|
||||
dataIn.size(),
|
||||
AudioFormatWriterOptions::SampleFormat::floatingPoint };
|
||||
|
||||
auto* reader = floatingPointTestData.getReader();
|
||||
|
||||
if (reader == nullptr)
|
||||
{
|
||||
expect (false, "WavFormatReader should be non-null");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<float> dataOut ((size_t) reader->lengthInSamples);
|
||||
float* const outPtr = dataOut.data();
|
||||
reader->read (&outPtr, 1, 0, (int) dataOut.size());
|
||||
|
||||
expect (reader->usesFloatingPointData == true);
|
||||
expect (dataIn.size() == dataOut.size());
|
||||
|
||||
for (auto [index, value] : enumerate (dataOut, size_t{}))
|
||||
expect (approximatelyEqual (value, dataIn[index]));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
MemoryBlock writeToBlock (WavAudioFormat& format, StringPairArray meta)
|
||||
struct WavFormatWriterTestData
|
||||
{
|
||||
WavFormatWriterTestData (const int** ptr,
|
||||
size_t numSamples,
|
||||
AudioFormatWriterOptions::SampleFormat sampleFormat)
|
||||
{
|
||||
const auto writerOptions = AudioFormatWriterOptions{}.withSampleRate (48000.0)
|
||||
.withNumChannels (1)
|
||||
.withBitsPerSample (32)
|
||||
.withSampleFormat (sampleFormat);
|
||||
|
||||
WavAudioFormat format;
|
||||
|
||||
{
|
||||
std::unique_ptr<OutputStream> stream = std::make_unique<MemoryOutputStream> (block, false);
|
||||
auto writer = format.createWriterFor (stream, writerOptions);
|
||||
|
||||
writer->write (ptr, (int) numSamples);
|
||||
}
|
||||
|
||||
reader = rawToUniquePtr (format.createReaderFor (new MemoryInputStream (block, false), true));
|
||||
}
|
||||
|
||||
AudioFormatReader* getReader()
|
||||
{
|
||||
return reader.get();
|
||||
}
|
||||
|
||||
private:
|
||||
MemoryBlock block;
|
||||
std::unique_ptr<AudioFormatReader> reader;
|
||||
};
|
||||
|
||||
MemoryBlock writeToBlock (WavAudioFormat& format, const StringMap& meta)
|
||||
{
|
||||
MemoryBlock mb;
|
||||
|
||||
{
|
||||
const auto writerOptions = AudioFormatWriterOptions{}.withSampleRate (44100.0)
|
||||
.withNumChannels ((int) numTestAudioBufferChannels)
|
||||
.withBitsPerSample (16)
|
||||
.withMetadataValues (meta);
|
||||
|
||||
// The destructor of the writer will modify the block, so make sure that we've
|
||||
// destroyed the writer before returning the block!
|
||||
auto writer = rawToUniquePtr (format.createWriterFor (new MemoryOutputStream (mb, false),
|
||||
44100.0,
|
||||
numTestAudioBufferChannels,
|
||||
16,
|
||||
meta,
|
||||
0));
|
||||
std::unique_ptr<OutputStream> stream = std::make_unique<MemoryOutputStream> (mb, false);
|
||||
auto writer = format.createWriterFor (stream, writerOptions);
|
||||
|
||||
expect (writer != nullptr);
|
||||
AudioBuffer<float> buffer (numTestAudioBufferChannels, numTestAudioBufferSamples);
|
||||
expect (writer->writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples));
|
||||
|
|
|
|||
|
|
@ -297,6 +297,9 @@ public:
|
|||
MemoryMappedAudioFormatReader* createMemoryMappedReader (const File&) override;
|
||||
MemoryMappedAudioFormatReader* createMemoryMappedReader (FileInputStream*) override;
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> createWriterFor (std::unique_ptr<OutputStream>& streamToWriteTo,
|
||||
const AudioFormatWriterOptions& options) override;
|
||||
|
||||
AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo,
|
||||
double sampleRateToUse,
|
||||
unsigned int numberOfChannels,
|
||||
|
|
@ -310,6 +313,7 @@ public:
|
|||
int bitsPerSample,
|
||||
const StringPairArray& metadataValues,
|
||||
int qualityOptionIndex) override;
|
||||
|
||||
using AudioFormat::createWriterFor;
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -365,6 +365,13 @@ AudioFormatWriter* WindowsMediaAudioFormat::createWriterFor (OutputStream* /*str
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> WindowsMediaAudioFormat::createWriterFor (std::unique_ptr<OutputStream>&,
|
||||
const AudioFormatWriterOptions&)
|
||||
{
|
||||
jassertfalse; // not yet implemented!
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
|
|
|||
|
|
@ -60,9 +60,13 @@ public:
|
|||
//==============================================================================
|
||||
AudioFormatReader* createReaderFor (InputStream*, bool deleteStreamIfOpeningFails) override;
|
||||
|
||||
std::unique_ptr<AudioFormatWriter> createWriterFor (std::unique_ptr<OutputStream>& streamToWriteTo,
|
||||
const AudioFormatWriterOptions& options) override;
|
||||
|
||||
AudioFormatWriter* createWriterFor (OutputStream*, double sampleRateToUse,
|
||||
unsigned int numberOfChannels, int bitsPerSample,
|
||||
const StringPairArray& metadataValues, int qualityOptionIndex) override;
|
||||
|
||||
using AudioFormat::createWriterFor;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue