fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
92
modules/juce_audio_formats/format/juce_AudioFormat.cpp
Normal file
92
modules/juce_audio_formats/format/juce_AudioFormat.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
AudioFormat::AudioFormat (String name, StringArray extensions)
|
||||
: formatName (name), fileExtensions (extensions)
|
||||
{
|
||||
}
|
||||
|
||||
AudioFormat::AudioFormat (StringRef name, StringRef extensions)
|
||||
: formatName (name.text), fileExtensions (StringArray::fromTokens (extensions, false))
|
||||
{
|
||||
}
|
||||
|
||||
AudioFormat::~AudioFormat()
|
||||
{
|
||||
}
|
||||
|
||||
bool AudioFormat::canHandleFile (const File& f)
|
||||
{
|
||||
for (auto& e : getFileExtensions())
|
||||
if (f.hasFileExtension (e))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const String& AudioFormat::getFormatName() const { return formatName; }
|
||||
StringArray AudioFormat::getFileExtensions() const { return fileExtensions; }
|
||||
bool AudioFormat::isCompressed() { return false; }
|
||||
StringArray AudioFormat::getQualityOptions() { return {}; }
|
||||
|
||||
MemoryMappedAudioFormatReader* AudioFormat::createMemoryMappedReader (const File&)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MemoryMappedAudioFormatReader* AudioFormat::createMemoryMappedReader (FileInputStream* fin)
|
||||
{
|
||||
delete fin;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool AudioFormat::isChannelLayoutSupported (const AudioChannelSet& channelSet)
|
||||
{
|
||||
if (channelSet == AudioChannelSet::mono()) return canDoMono();
|
||||
if (channelSet == AudioChannelSet::stereo()) return canDoStereo();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioFormatWriter* AudioFormat::createWriterFor (OutputStream* streamToWriteTo,
|
||||
double sampleRateToUse,
|
||||
const AudioChannelSet& channelLayout,
|
||||
int bitsPerSample,
|
||||
const StringPairArray& metadataValues,
|
||||
int qualityOptionIndex)
|
||||
{
|
||||
if (isChannelLayoutSupported (channelLayout))
|
||||
return createWriterFor (streamToWriteTo, sampleRateToUse,
|
||||
static_cast<unsigned int> (channelLayout.size()),
|
||||
bitsPerSample, metadataValues, qualityOptionIndex);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace juce
|
219
modules/juce_audio_formats/format/juce_AudioFormat.h
Normal file
219
modules/juce_audio_formats/format/juce_AudioFormat.h
Normal file
@ -0,0 +1,219 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Subclasses of AudioFormat are used to read and write different audio
|
||||
file formats.
|
||||
|
||||
@see AudioFormatReader, AudioFormatWriter, WavAudioFormat, AiffAudioFormat
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API AudioFormat
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~AudioFormat();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the name of this format.
|
||||
e.g. "WAV file" or "AIFF file"
|
||||
*/
|
||||
const String& getFormatName() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns all the file extensions that might apply to a file of this format.
|
||||
The first item will be the one that's preferred when creating a new file.
|
||||
So for a wav file this might just return ".wav"; for an AIFF file it might
|
||||
return two items, ".aif" and ".aiff"
|
||||
*/
|
||||
virtual StringArray getFileExtensions() const;
|
||||
|
||||
/** Returns true if this the given file can be read by this format.
|
||||
Subclasses shouldn't do too much work here, just check the extension or
|
||||
file type. The base class implementation just checks the file's extension
|
||||
against one of the ones that was registered in the constructor.
|
||||
*/
|
||||
virtual bool canHandleFile (const File& fileToTest);
|
||||
|
||||
/** Returns a set of sample rates that the format can read and write. */
|
||||
virtual Array<int> getPossibleSampleRates() = 0;
|
||||
|
||||
/** Returns a set of bit depths that the format can read and write. */
|
||||
virtual Array<int> getPossibleBitDepths() = 0;
|
||||
|
||||
/** Returns true if the format can do 2-channel audio. */
|
||||
virtual bool canDoStereo() = 0;
|
||||
|
||||
/** Returns true if the format can do 1-channel audio. */
|
||||
virtual bool canDoMono() = 0;
|
||||
|
||||
/** Returns true if the format uses compressed data. */
|
||||
virtual bool isCompressed();
|
||||
|
||||
/** Returns true if the channel layout is supported by this format. */
|
||||
virtual bool isChannelLayoutSupported (const AudioChannelSet& channelSet);
|
||||
|
||||
/** Returns a list of different qualities that can be used when writing.
|
||||
|
||||
Non-compressed formats will just return an empty array, but for something
|
||||
like Ogg-Vorbis or MP3, it might return a list of bit-rates, etc.
|
||||
|
||||
When calling createWriterFor(), an index from this array is passed in to
|
||||
tell the format which option is required.
|
||||
*/
|
||||
virtual StringArray getQualityOptions();
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to create an object that can read from a stream containing audio
|
||||
data in this format.
|
||||
|
||||
The reader object that is returned can be used to read from the stream, and
|
||||
should then be deleted by the caller.
|
||||
|
||||
@param sourceStream the stream to read from - the AudioFormatReader object
|
||||
that is returned will delete this stream when it no longer
|
||||
needs it.
|
||||
@param deleteStreamIfOpeningFails if no reader can be created, this determines whether this method
|
||||
should delete the stream object that was passed-in. (If a valid
|
||||
reader is returned, it will always be in charge of deleting the
|
||||
stream, so this parameter is ignored)
|
||||
@see AudioFormatReader
|
||||
*/
|
||||
virtual AudioFormatReader* createReaderFor (InputStream* sourceStream,
|
||||
bool deleteStreamIfOpeningFails) = 0;
|
||||
|
||||
/** Attempts to create a MemoryMappedAudioFormatReader, if possible for this format.
|
||||
If the format does not support this, the method will return nullptr;
|
||||
*/
|
||||
virtual MemoryMappedAudioFormatReader* createMemoryMappedReader (const File& file);
|
||||
virtual MemoryMappedAudioFormatReader* createMemoryMappedReader (FileInputStream* fin);
|
||||
|
||||
/** Tries to create an object that can write to a stream with this audio format.
|
||||
|
||||
The writer object that is returned can be used to write to the stream, and
|
||||
should then be deleted by the caller.
|
||||
|
||||
If the stream can't be created for some reason (e.g. the parameters passed in
|
||||
here aren't suitable), this will return nullptr.
|
||||
|
||||
@param streamToWriteTo the stream that the data will go to - this will be
|
||||
deleted by the AudioFormatWriter object when it's no longer
|
||||
needed. If no AudioFormatWriter can be created by this method,
|
||||
the stream will NOT be deleted, so that the caller can re-use it
|
||||
to try to open a different format, etc
|
||||
@param sampleRateToUse the sample rate for the file, which must be one of the ones
|
||||
returned by getPossibleSampleRates()
|
||||
@param numberOfChannels the number of channels - this must be either 1 or 2, and
|
||||
the choice will depend on the results of canDoMono() and
|
||||
canDoStereo()
|
||||
@param bitsPerSample the bits per sample to use - this must be one of the values
|
||||
returned by getPossibleBitDepths()
|
||||
@param metadataValues a set of metadata values that the writer should try to write
|
||||
to the stream. Exactly what these are depends on the format,
|
||||
and the subclass doesn't actually have to do anything with
|
||||
them if it doesn't want to. Have a look at the specific format
|
||||
implementation classes to see possible values that can be
|
||||
used
|
||||
@param qualityOptionIndex the index of one of compression qualities returned by the
|
||||
getQualityOptions() method. If there aren't any quality options
|
||||
for this format, just pass 0 in this parameter, as it'll be
|
||||
ignored
|
||||
@see AudioFormatWriter
|
||||
*/
|
||||
virtual AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo,
|
||||
double sampleRateToUse,
|
||||
unsigned int numberOfChannels,
|
||||
int bitsPerSample,
|
||||
const StringPairArray& metadataValues,
|
||||
int qualityOptionIndex) = 0;
|
||||
|
||||
/** Tries to create an object that can write to a stream with this audio format.
|
||||
|
||||
The writer object that is returned can be used to write to the stream, and
|
||||
should then be deleted by the caller.
|
||||
|
||||
If the stream can't be created for some reason (e.g. the parameters passed in
|
||||
here aren't suitable), this will return nullptr.
|
||||
|
||||
@param streamToWriteTo the stream that the data will go to - this will be
|
||||
deleted by the AudioFormatWriter object when it's no longer
|
||||
needed. If no AudioFormatWriter can be created by this method,
|
||||
the stream will NOT be deleted, so that the caller can re-use it
|
||||
to try to open a different format, etc
|
||||
@param sampleRateToUse the sample rate for the file, which must be one of the ones
|
||||
returned by getPossibleSampleRates()
|
||||
@param channelLayout the channel layout for the file. Use isChannelLayoutSupported
|
||||
to check if the writer supports this layout.
|
||||
@param bitsPerSample the bits per sample to use - this must be one of the values
|
||||
returned by getPossibleBitDepths()
|
||||
@param metadataValues a set of metadata values that the writer should try to write
|
||||
to the stream. Exactly what these are depends on the format,
|
||||
and the subclass doesn't actually have to do anything with
|
||||
them if it doesn't want to. Have a look at the specific format
|
||||
implementation classes to see possible values that can be
|
||||
used
|
||||
@param qualityOptionIndex the index of one of compression qualities returned by the
|
||||
getQualityOptions() method. If there aren't any quality options
|
||||
for this format, just pass 0 in this parameter, as it'll be
|
||||
ignored
|
||||
@see AudioFormatWriter
|
||||
*/
|
||||
virtual AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo,
|
||||
double sampleRateToUse,
|
||||
const AudioChannelSet& channelLayout,
|
||||
int bitsPerSample,
|
||||
const StringPairArray& metadataValues,
|
||||
int qualityOptionIndex);
|
||||
|
||||
protected:
|
||||
/** Creates an AudioFormat object.
|
||||
|
||||
@param formatName this sets the value that will be returned by getFormatName()
|
||||
@param fileExtensions an array of file extensions - these will be returned by getFileExtensions()
|
||||
*/
|
||||
AudioFormat (String formatName, StringArray fileExtensions);
|
||||
|
||||
/** Creates an AudioFormat object.
|
||||
|
||||
@param formatName this sets the value that will be returned by getFormatName()
|
||||
@param fileExtensions a whitespace-separated list of file extensions - these will
|
||||
be returned by getFileExtensions()
|
||||
*/
|
||||
AudioFormat (StringRef formatName, StringRef fileExtensions);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String formatName;
|
||||
StringArray fileExtensions;
|
||||
};
|
||||
|
||||
} // namespace juce
|
166
modules/juce_audio_formats/format/juce_AudioFormatManager.cpp
Normal file
166
modules/juce_audio_formats/format/juce_AudioFormatManager.cpp
Normal file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
AudioFormatManager::AudioFormatManager() {}
|
||||
AudioFormatManager::~AudioFormatManager() {}
|
||||
|
||||
//==============================================================================
|
||||
void AudioFormatManager::registerFormat (AudioFormat* newFormat, bool makeThisTheDefaultFormat)
|
||||
{
|
||||
jassert (newFormat != nullptr);
|
||||
|
||||
if (newFormat != nullptr)
|
||||
{
|
||||
#if JUCE_DEBUG
|
||||
for (auto* af : knownFormats)
|
||||
{
|
||||
if (af->getFormatName() == newFormat->getFormatName())
|
||||
jassertfalse; // trying to add the same format twice!
|
||||
}
|
||||
#endif
|
||||
|
||||
if (makeThisTheDefaultFormat)
|
||||
defaultFormatIndex = getNumKnownFormats();
|
||||
|
||||
knownFormats.add (newFormat);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioFormatManager::registerBasicFormats()
|
||||
{
|
||||
registerFormat (new WavAudioFormat(), true);
|
||||
registerFormat (new AiffAudioFormat(), false);
|
||||
|
||||
#if JUCE_USE_FLAC
|
||||
registerFormat (new FlacAudioFormat(), false);
|
||||
#endif
|
||||
|
||||
#if JUCE_USE_OGGVORBIS
|
||||
registerFormat (new OggVorbisAudioFormat(), false);
|
||||
#endif
|
||||
|
||||
#if JUCE_MAC || JUCE_IOS
|
||||
registerFormat (new CoreAudioFormat(), false);
|
||||
#endif
|
||||
|
||||
#if JUCE_USE_MP3AUDIOFORMAT
|
||||
registerFormat (new MP3AudioFormat(), false);
|
||||
#endif
|
||||
|
||||
#if JUCE_USE_WINDOWS_MEDIA_FORMAT
|
||||
registerFormat (new WindowsMediaAudioFormat(), false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioFormatManager::clearFormats()
|
||||
{
|
||||
knownFormats.clear();
|
||||
defaultFormatIndex = 0;
|
||||
}
|
||||
|
||||
int AudioFormatManager::getNumKnownFormats() const { return knownFormats.size(); }
|
||||
AudioFormat* AudioFormatManager::getKnownFormat (int index) const { return knownFormats[index]; }
|
||||
AudioFormat* AudioFormatManager::getDefaultFormat() const { return getKnownFormat (defaultFormatIndex); }
|
||||
|
||||
AudioFormat* AudioFormatManager::findFormatForFileExtension (const String& fileExtension) const
|
||||
{
|
||||
if (! fileExtension.startsWithChar ('.'))
|
||||
return findFormatForFileExtension ("." + fileExtension);
|
||||
|
||||
for (auto* af : knownFormats)
|
||||
if (af->getFileExtensions().contains (fileExtension, true))
|
||||
return af;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
String AudioFormatManager::getWildcardForAllFormats() const
|
||||
{
|
||||
StringArray extensions;
|
||||
|
||||
for (auto* af : knownFormats)
|
||||
extensions.addArray (af->getFileExtensions());
|
||||
|
||||
extensions.trim();
|
||||
extensions.removeEmptyStrings();
|
||||
|
||||
for (auto& e : extensions)
|
||||
e = (e.startsWithChar ('.') ? "*" : "*.") + e;
|
||||
|
||||
extensions.removeDuplicates (true);
|
||||
return extensions.joinIntoString (";");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AudioFormatReader* AudioFormatManager::createReaderFor (const File& file)
|
||||
{
|
||||
// you need to actually register some formats before the manager can
|
||||
// use them to open a file!
|
||||
jassert (getNumKnownFormats() > 0);
|
||||
|
||||
for (auto* af : knownFormats)
|
||||
if (af->canHandleFile (file))
|
||||
if (auto* in = file.createInputStream())
|
||||
if (auto* r = af->createReaderFor (in, true))
|
||||
return r;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AudioFormatReader* AudioFormatManager::createReaderFor (InputStream* audioFileStream)
|
||||
{
|
||||
// you need to actually register some formats before the manager can
|
||||
// use them to open a file!
|
||||
jassert (getNumKnownFormats() > 0);
|
||||
|
||||
if (audioFileStream != nullptr)
|
||||
{
|
||||
std::unique_ptr<InputStream> in (audioFileStream);
|
||||
auto originalStreamPos = in->getPosition();
|
||||
|
||||
for (auto* af : knownFormats)
|
||||
{
|
||||
if (auto* r = af->createReaderFor (in.get(), false))
|
||||
{
|
||||
in.release();
|
||||
return r;
|
||||
}
|
||||
|
||||
in->setPosition (originalStreamPos);
|
||||
|
||||
// the stream that is passed-in must be capable of being repositioned so
|
||||
// that all the formats can have a go at opening it.
|
||||
jassert (in->getPosition() == originalStreamPos);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace juce
|
144
modules/juce_audio_formats/format/juce_AudioFormatManager.h
Normal file
144
modules/juce_audio_formats/format/juce_AudioFormatManager.h
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class for keeping a list of available audio formats, and for deciding which
|
||||
one to use to open a given file.
|
||||
|
||||
After creating an AudioFormatManager object, you should call registerFormat()
|
||||
or registerBasicFormats() to give it a list of format types that it can use.
|
||||
|
||||
@see AudioFormat
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API AudioFormatManager
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty format manager.
|
||||
|
||||
Before it'll be any use, you'll need to call registerFormat() with all the
|
||||
formats you want it to be able to recognise.
|
||||
*/
|
||||
AudioFormatManager();
|
||||
|
||||
/** Destructor. */
|
||||
~AudioFormatManager();
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a format to the manager's list of available file types.
|
||||
|
||||
The object passed-in will be deleted by this object, so don't keep a pointer
|
||||
to it!
|
||||
|
||||
If makeThisTheDefaultFormat is true, then the getDefaultFormat() method will
|
||||
return this one when called.
|
||||
*/
|
||||
void registerFormat (AudioFormat* newFormat,
|
||||
bool makeThisTheDefaultFormat);
|
||||
|
||||
/** Handy method to make it easy to register the formats that come with JUCE.
|
||||
Currently, this will add WAV and AIFF to the list.
|
||||
*/
|
||||
void registerBasicFormats();
|
||||
|
||||
/** Clears the list of known formats. */
|
||||
void clearFormats();
|
||||
|
||||
/** Returns the number of currently registered file formats. */
|
||||
int getNumKnownFormats() const;
|
||||
|
||||
/** Returns one of the registered file formats. */
|
||||
AudioFormat* getKnownFormat (int index) const;
|
||||
|
||||
/** Iterator access to the list of known formats. */
|
||||
AudioFormat** begin() const noexcept { return knownFormats.begin(); }
|
||||
|
||||
/** Iterator access to the list of known formats. */
|
||||
AudioFormat** end() const noexcept { return knownFormats.end(); }
|
||||
|
||||
/** Looks for which of the known formats is listed as being for a given file
|
||||
extension.
|
||||
|
||||
The extension may have a dot before it, so e.g. ".wav" or "wav" are both ok.
|
||||
*/
|
||||
AudioFormat* findFormatForFileExtension (const String& fileExtension) const;
|
||||
|
||||
/** Returns the format which has been set as the default one.
|
||||
|
||||
You can set a format as being the default when it is registered. It's useful
|
||||
when you want to write to a file, because the best format may change between
|
||||
platforms, e.g. AIFF is preferred on the Mac, WAV on Windows.
|
||||
|
||||
If none has been set as the default, this method will just return the first
|
||||
one in the list.
|
||||
*/
|
||||
AudioFormat* getDefaultFormat() const;
|
||||
|
||||
/** Returns a set of wildcards for file-matching that contains the extensions for
|
||||
all known formats.
|
||||
|
||||
E.g. if might return "*.wav;*.aiff" if it just knows about wavs and aiffs.
|
||||
*/
|
||||
String getWildcardForAllFormats() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Searches through the known formats to try to create a suitable reader for
|
||||
this file.
|
||||
|
||||
If none of the registered formats can open the file, it'll return nullptr.
|
||||
It's the caller's responsibility to delete the reader that is returned.
|
||||
*/
|
||||
AudioFormatReader* createReaderFor (const File& audioFile);
|
||||
|
||||
/** Searches through the known formats to try to create a suitable reader for
|
||||
this stream.
|
||||
|
||||
The stream object that is passed-in will be deleted by this method or by the
|
||||
reader that is returned, so the caller should not keep any references to it.
|
||||
|
||||
The stream that is passed-in must be capable of being repositioned so
|
||||
that all the formats can have a go at opening it.
|
||||
|
||||
If none of the registered formats can open the stream, it'll return nullptr.
|
||||
If it returns a reader, it's the caller's responsibility to delete the reader.
|
||||
*/
|
||||
AudioFormatReader* createReaderFor (InputStream* audioFileStream);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
OwnedArray<AudioFormat> knownFormats;
|
||||
int defaultFormatIndex = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioFormatManager)
|
||||
};
|
||||
|
||||
} // namespace juce
|
419
modules/juce_audio_formats/format/juce_AudioFormatReader.cpp
Normal file
419
modules/juce_audio_formats/format/juce_AudioFormatReader.cpp
Normal file
@ -0,0 +1,419 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
AudioFormatReader::AudioFormatReader (InputStream* in, const String& name)
|
||||
: input (in), formatName (name)
|
||||
{
|
||||
}
|
||||
|
||||
AudioFormatReader::~AudioFormatReader()
|
||||
{
|
||||
delete input;
|
||||
}
|
||||
|
||||
bool AudioFormatReader::read (int* const* destSamples,
|
||||
int numDestChannels,
|
||||
int64 startSampleInSource,
|
||||
int numSamplesToRead,
|
||||
const bool fillLeftoverChannelsWithCopies)
|
||||
{
|
||||
jassert (numDestChannels > 0); // you have to actually give this some channels to work with!
|
||||
|
||||
const size_t originalNumSamplesToRead = (size_t) numSamplesToRead;
|
||||
int startOffsetInDestBuffer = 0;
|
||||
|
||||
if (startSampleInSource < 0)
|
||||
{
|
||||
const int silence = (int) jmin (-startSampleInSource, (int64) numSamplesToRead);
|
||||
|
||||
for (int i = numDestChannels; --i >= 0;)
|
||||
if (destSamples[i] != nullptr)
|
||||
zeromem (destSamples[i], sizeof (int) * (size_t) silence);
|
||||
|
||||
startOffsetInDestBuffer += silence;
|
||||
numSamplesToRead -= silence;
|
||||
startSampleInSource = 0;
|
||||
}
|
||||
|
||||
if (numSamplesToRead <= 0)
|
||||
return true;
|
||||
|
||||
if (! readSamples (const_cast<int**> (destSamples),
|
||||
jmin ((int) numChannels, numDestChannels), startOffsetInDestBuffer,
|
||||
startSampleInSource, numSamplesToRead))
|
||||
return false;
|
||||
|
||||
if (numDestChannels > (int) numChannels)
|
||||
{
|
||||
if (fillLeftoverChannelsWithCopies)
|
||||
{
|
||||
int* lastFullChannel = destSamples[0];
|
||||
|
||||
for (int i = (int) numChannels; --i > 0;)
|
||||
{
|
||||
if (destSamples[i] != nullptr)
|
||||
{
|
||||
lastFullChannel = destSamples[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastFullChannel != nullptr)
|
||||
for (int i = (int) numChannels; i < numDestChannels; ++i)
|
||||
if (destSamples[i] != nullptr)
|
||||
memcpy (destSamples[i], lastFullChannel, sizeof (int) * originalNumSamplesToRead);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = (int) numChannels; i < numDestChannels; ++i)
|
||||
if (destSamples[i] != nullptr)
|
||||
zeromem (destSamples[i], sizeof (int) * originalNumSamplesToRead);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void readChannels (AudioFormatReader& reader, int** chans, AudioBuffer<float>* buffer,
|
||||
int startSample, int numSamples, int64 readerStartSample, int numTargetChannels)
|
||||
{
|
||||
for (int j = 0; j < numTargetChannels; ++j)
|
||||
chans[j] = reinterpret_cast<int*> (buffer->getWritePointer (j, startSample));
|
||||
|
||||
chans[numTargetChannels] = nullptr;
|
||||
reader.read (chans, numTargetChannels, readerStartSample, numSamples, true);
|
||||
}
|
||||
|
||||
void AudioFormatReader::read (AudioBuffer<float>* buffer,
|
||||
int startSample,
|
||||
int numSamples,
|
||||
int64 readerStartSample,
|
||||
bool useReaderLeftChan,
|
||||
bool useReaderRightChan)
|
||||
{
|
||||
jassert (buffer != nullptr);
|
||||
jassert (startSample >= 0 && startSample + numSamples <= buffer->getNumSamples());
|
||||
|
||||
if (numSamples > 0)
|
||||
{
|
||||
const int numTargetChannels = buffer->getNumChannels();
|
||||
|
||||
if (numTargetChannels <= 2)
|
||||
{
|
||||
int* const dest0 = reinterpret_cast<int*> (buffer->getWritePointer (0, startSample));
|
||||
int* const dest1 = reinterpret_cast<int*> (numTargetChannels > 1 ? buffer->getWritePointer (1, startSample) : nullptr);
|
||||
int* chans[3];
|
||||
|
||||
if (useReaderLeftChan == useReaderRightChan)
|
||||
{
|
||||
chans[0] = dest0;
|
||||
chans[1] = numChannels > 1 ? dest1 : nullptr;
|
||||
}
|
||||
else if (useReaderLeftChan || (numChannels == 1))
|
||||
{
|
||||
chans[0] = dest0;
|
||||
chans[1] = nullptr;
|
||||
}
|
||||
else if (useReaderRightChan)
|
||||
{
|
||||
chans[0] = nullptr;
|
||||
chans[1] = dest0;
|
||||
}
|
||||
|
||||
chans[2] = nullptr;
|
||||
read (chans, 2, readerStartSample, numSamples, true);
|
||||
|
||||
// if the target's stereo and the source is mono, dupe the first channel..
|
||||
if (numTargetChannels > 1 && (chans[0] == nullptr || chans[1] == nullptr))
|
||||
memcpy (dest1, dest0, sizeof (float) * (size_t) numSamples);
|
||||
}
|
||||
else if (numTargetChannels <= 64)
|
||||
{
|
||||
int* chans[65];
|
||||
readChannels (*this, chans, buffer, startSample, numSamples, readerStartSample, numTargetChannels);
|
||||
}
|
||||
else
|
||||
{
|
||||
HeapBlock<int*> chans (numTargetChannels + 1);
|
||||
readChannels (*this, chans, buffer, startSample, numSamples, readerStartSample, numTargetChannels);
|
||||
}
|
||||
|
||||
if (! usesFloatingPointData)
|
||||
for (int j = 0; j < numTargetChannels; ++j)
|
||||
if (float* const d = buffer->getWritePointer (j, startSample))
|
||||
FloatVectorOperations::convertFixedToFloat (d, reinterpret_cast<const int*> (d), 1.0f / 0x7fffffff, numSamples);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioFormatReader::readMaxLevels (int64 startSampleInFile, int64 numSamples,
|
||||
Range<float>* const results, const int channelsToRead)
|
||||
{
|
||||
jassert (channelsToRead > 0 && channelsToRead <= (int) numChannels);
|
||||
|
||||
if (numSamples <= 0)
|
||||
{
|
||||
for (int i = 0; i < channelsToRead; ++i)
|
||||
results[i] = Range<float>();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto bufferSize = (int) jmin (numSamples, (int64) 4096);
|
||||
AudioBuffer<float> tempSampleBuffer ((int) channelsToRead, bufferSize);
|
||||
|
||||
auto floatBuffer = tempSampleBuffer.getArrayOfWritePointers();
|
||||
auto intBuffer = reinterpret_cast<int* const*> (floatBuffer);
|
||||
bool isFirstBlock = true;
|
||||
|
||||
while (numSamples > 0)
|
||||
{
|
||||
auto numToDo = (int) jmin (numSamples, (int64) bufferSize);
|
||||
|
||||
if (! read (intBuffer, channelsToRead, startSampleInFile, numToDo, false))
|
||||
break;
|
||||
|
||||
for (int i = 0; i < channelsToRead; ++i)
|
||||
{
|
||||
Range<float> r;
|
||||
|
||||
if (usesFloatingPointData)
|
||||
{
|
||||
r = FloatVectorOperations::findMinAndMax (floatBuffer[i], numToDo);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto intRange = Range<int>::findMinAndMax (intBuffer[i], numToDo);
|
||||
|
||||
r = Range<float> (intRange.getStart() / (float) std::numeric_limits<int>::max(),
|
||||
intRange.getEnd() / (float) std::numeric_limits<int>::max());
|
||||
}
|
||||
|
||||
results[i] = isFirstBlock ? r : results[i].getUnionWith (r);
|
||||
}
|
||||
|
||||
isFirstBlock = false;
|
||||
numSamples -= numToDo;
|
||||
startSampleInFile += numToDo;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioFormatReader::readMaxLevels (int64 startSampleInFile, int64 numSamples,
|
||||
float& lowestLeft, float& highestLeft,
|
||||
float& lowestRight, float& highestRight)
|
||||
{
|
||||
Range<float> levels[2];
|
||||
|
||||
if (numChannels < 2)
|
||||
{
|
||||
readMaxLevels (startSampleInFile, numSamples, levels, (int) numChannels);
|
||||
levels[1] = levels[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
readMaxLevels (startSampleInFile, numSamples, levels, 2);
|
||||
}
|
||||
|
||||
lowestLeft = levels[0].getStart();
|
||||
highestLeft = levels[0].getEnd();
|
||||
lowestRight = levels[1].getStart();
|
||||
highestRight = levels[1].getEnd();
|
||||
}
|
||||
|
||||
int64 AudioFormatReader::searchForLevel (int64 startSample,
|
||||
int64 numSamplesToSearch,
|
||||
double magnitudeRangeMinimum,
|
||||
double magnitudeRangeMaximum,
|
||||
int minimumConsecutiveSamples)
|
||||
{
|
||||
if (numSamplesToSearch == 0)
|
||||
return -1;
|
||||
|
||||
const int bufferSize = 4096;
|
||||
HeapBlock<int> tempSpace (bufferSize * 2 + 64);
|
||||
|
||||
int* tempBuffer[3];
|
||||
tempBuffer[0] = tempSpace.get();
|
||||
tempBuffer[1] = tempSpace.get() + bufferSize;
|
||||
tempBuffer[2] = 0;
|
||||
|
||||
int consecutive = 0;
|
||||
int64 firstMatchPos = -1;
|
||||
|
||||
jassert (magnitudeRangeMaximum > magnitudeRangeMinimum);
|
||||
|
||||
auto doubleMin = jlimit (0.0, (double) std::numeric_limits<int>::max(), magnitudeRangeMinimum * std::numeric_limits<int>::max());
|
||||
auto doubleMax = jlimit (doubleMin, (double) std::numeric_limits<int>::max(), magnitudeRangeMaximum * std::numeric_limits<int>::max());
|
||||
auto intMagnitudeRangeMinimum = roundToInt (doubleMin);
|
||||
auto intMagnitudeRangeMaximum = roundToInt (doubleMax);
|
||||
|
||||
while (numSamplesToSearch != 0)
|
||||
{
|
||||
auto numThisTime = (int) jmin (std::abs (numSamplesToSearch), (int64) bufferSize);
|
||||
int64 bufferStart = startSample;
|
||||
|
||||
if (numSamplesToSearch < 0)
|
||||
bufferStart -= numThisTime;
|
||||
|
||||
if (bufferStart >= (int) lengthInSamples)
|
||||
break;
|
||||
|
||||
read (tempBuffer, 2, bufferStart, numThisTime, false);
|
||||
auto num = numThisTime;
|
||||
|
||||
while (--num >= 0)
|
||||
{
|
||||
if (numSamplesToSearch < 0)
|
||||
--startSample;
|
||||
|
||||
bool matches = false;
|
||||
auto index = (int) (startSample - bufferStart);
|
||||
|
||||
if (usesFloatingPointData)
|
||||
{
|
||||
const float sample1 = std::abs (((float*) tempBuffer[0]) [index]);
|
||||
|
||||
if (sample1 >= magnitudeRangeMinimum
|
||||
&& sample1 <= magnitudeRangeMaximum)
|
||||
{
|
||||
matches = true;
|
||||
}
|
||||
else if (numChannels > 1)
|
||||
{
|
||||
const float sample2 = std::abs (((float*) tempBuffer[1]) [index]);
|
||||
|
||||
matches = (sample2 >= magnitudeRangeMinimum
|
||||
&& sample2 <= magnitudeRangeMaximum);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const int sample1 = std::abs (tempBuffer[0] [index]);
|
||||
|
||||
if (sample1 >= intMagnitudeRangeMinimum
|
||||
&& sample1 <= intMagnitudeRangeMaximum)
|
||||
{
|
||||
matches = true;
|
||||
}
|
||||
else if (numChannels > 1)
|
||||
{
|
||||
const int sample2 = std::abs (tempBuffer[1][index]);
|
||||
|
||||
matches = (sample2 >= intMagnitudeRangeMinimum
|
||||
&& sample2 <= intMagnitudeRangeMaximum);
|
||||
}
|
||||
}
|
||||
|
||||
if (matches)
|
||||
{
|
||||
if (firstMatchPos < 0)
|
||||
firstMatchPos = startSample;
|
||||
|
||||
if (++consecutive >= minimumConsecutiveSamples)
|
||||
{
|
||||
if (firstMatchPos < 0 || firstMatchPos >= lengthInSamples)
|
||||
return -1;
|
||||
|
||||
return firstMatchPos;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
consecutive = 0;
|
||||
firstMatchPos = -1;
|
||||
}
|
||||
|
||||
if (numSamplesToSearch > 0)
|
||||
++startSample;
|
||||
}
|
||||
|
||||
if (numSamplesToSearch > 0)
|
||||
numSamplesToSearch -= numThisTime;
|
||||
else
|
||||
numSamplesToSearch += numThisTime;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
AudioChannelSet AudioFormatReader::getChannelLayout()
|
||||
{
|
||||
return AudioChannelSet::canonicalChannelSet (static_cast<int> (numChannels));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MemoryMappedAudioFormatReader::MemoryMappedAudioFormatReader (const File& f, const AudioFormatReader& reader,
|
||||
int64 start, int64 length, int frameSize)
|
||||
: AudioFormatReader (nullptr, reader.getFormatName()), file (f),
|
||||
dataChunkStart (start), dataLength (length), bytesPerFrame (frameSize)
|
||||
{
|
||||
sampleRate = reader.sampleRate;
|
||||
bitsPerSample = reader.bitsPerSample;
|
||||
lengthInSamples = reader.lengthInSamples;
|
||||
numChannels = reader.numChannels;
|
||||
metadataValues = reader.metadataValues;
|
||||
usesFloatingPointData = reader.usesFloatingPointData;
|
||||
}
|
||||
|
||||
bool MemoryMappedAudioFormatReader::mapEntireFile()
|
||||
{
|
||||
return mapSectionOfFile (Range<int64> (0, lengthInSamples));
|
||||
}
|
||||
|
||||
bool MemoryMappedAudioFormatReader::mapSectionOfFile (Range<int64> samplesToMap)
|
||||
{
|
||||
if (map == nullptr || samplesToMap != mappedSection)
|
||||
{
|
||||
map.reset();
|
||||
|
||||
const Range<int64> fileRange (sampleToFilePos (samplesToMap.getStart()),
|
||||
sampleToFilePos (samplesToMap.getEnd()));
|
||||
|
||||
map.reset (new MemoryMappedFile (file, fileRange, MemoryMappedFile::readOnly));
|
||||
|
||||
if (map->getData() == nullptr)
|
||||
map.reset();
|
||||
else
|
||||
mappedSection = Range<int64> (jmax ((int64) 0, filePosToSample (map->getRange().getStart() + (bytesPerFrame - 1))),
|
||||
jmin (lengthInSamples, filePosToSample (map->getRange().getEnd())));
|
||||
}
|
||||
|
||||
return map != nullptr;
|
||||
}
|
||||
|
||||
static int memoryReadDummyVariable; // used to force the compiler not to optimise-away the read operation
|
||||
|
||||
void MemoryMappedAudioFormatReader::touchSample (int64 sample) const noexcept
|
||||
{
|
||||
if (map != nullptr && mappedSection.contains (sample))
|
||||
memoryReadDummyVariable += *(char*) sampleToPointer (sample);
|
||||
else
|
||||
jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
|
||||
}
|
||||
|
||||
} // namespace juce
|
308
modules/juce_audio_formats/format/juce_AudioFormatReader.h
Normal file
308
modules/juce_audio_formats/format/juce_AudioFormatReader.h
Normal file
@ -0,0 +1,308 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class AudioFormat;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Reads samples from an audio file stream.
|
||||
|
||||
A subclass that reads a specific type of audio format will be created by
|
||||
an AudioFormat object.
|
||||
|
||||
@see AudioFormat, AudioFormatWriter
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API AudioFormatReader
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Creates an AudioFormatReader object.
|
||||
|
||||
@param sourceStream the stream to read from - this will be deleted
|
||||
by this object when it is no longer needed. (Some
|
||||
specialised readers might not use this parameter and
|
||||
can leave it as nullptr).
|
||||
@param formatName the description that will be returned by the getFormatName()
|
||||
method
|
||||
*/
|
||||
AudioFormatReader (InputStream* sourceStream,
|
||||
const String& formatName);
|
||||
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~AudioFormatReader();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a description of what type of format this is.
|
||||
|
||||
E.g. "AIFF"
|
||||
*/
|
||||
const String& getFormatName() const noexcept { return formatName; }
|
||||
|
||||
//==============================================================================
|
||||
/** Reads samples from the stream.
|
||||
|
||||
@param destSamples an array of buffers into which the sample data for each
|
||||
channel will be written.
|
||||
If the format is fixed-point, each channel will be written
|
||||
as an array of 32-bit signed integers using the full
|
||||
range -0x80000000 to 0x7fffffff, regardless of the source's
|
||||
bit-depth. If it is a floating-point format, you should cast
|
||||
the resulting array to a (float**) to get the values (in the
|
||||
range -1.0 to 1.0 or beyond)
|
||||
If the format is stereo, then destSamples[0] is the left channel
|
||||
data, and destSamples[1] is the right channel.
|
||||
The numDestChannels parameter indicates how many pointers this array
|
||||
contains, but some of these pointers can be null if you don't want to
|
||||
read data for some of the channels
|
||||
@param numDestChannels the number of array elements in the destChannels array
|
||||
@param startSampleInSource the position in the audio file or stream at which the samples
|
||||
should be read, as a number of samples from the start of the
|
||||
stream. It's ok for this to be beyond the start or end of the
|
||||
available data - any samples that are out-of-range will be returned
|
||||
as zeros.
|
||||
@param numSamplesToRead the number of samples to read. If this is greater than the number
|
||||
of samples that the file or stream contains. the result will be padded
|
||||
with zeros
|
||||
@param fillLeftoverChannelsWithCopies if true, this indicates that if there's no source data available
|
||||
for some of the channels that you pass in, then they should be filled with
|
||||
copies of valid source channels.
|
||||
E.g. if you're reading a mono file and you pass 2 channels to this method, then
|
||||
if fillLeftoverChannelsWithCopies is true, both destination channels will be filled
|
||||
with the same data from the file's single channel. If fillLeftoverChannelsWithCopies
|
||||
was false, then only the first channel would be filled with the file's contents, and
|
||||
the second would be cleared. If there are many channels, e.g. you try to read 4 channels
|
||||
from a stereo file, then the last 3 would all end up with copies of the same data.
|
||||
@returns true if the operation succeeded, false if there was an error. Note
|
||||
that reading sections of data beyond the extent of the stream isn't an
|
||||
error - the reader should just return zeros for these regions
|
||||
@see readMaxLevels
|
||||
*/
|
||||
bool read (int* const* destSamples,
|
||||
int numDestChannels,
|
||||
int64 startSampleInSource,
|
||||
int numSamplesToRead,
|
||||
bool fillLeftoverChannelsWithCopies);
|
||||
|
||||
/** Fills a section of an AudioBuffer from this reader.
|
||||
|
||||
This will convert the reader's fixed- or floating-point data to
|
||||
the buffer's floating-point format, and will try to intelligently
|
||||
cope with mismatches between the number of channels in the reader
|
||||
and the buffer.
|
||||
*/
|
||||
void read (AudioBuffer<float>* buffer,
|
||||
int startSampleInDestBuffer,
|
||||
int numSamples,
|
||||
int64 readerStartSample,
|
||||
bool useReaderLeftChan,
|
||||
bool useReaderRightChan);
|
||||
|
||||
/** Finds the highest and lowest sample levels from a section of the audio stream.
|
||||
|
||||
This will read a block of samples from the stream, and measure the
|
||||
highest and lowest sample levels from the channels in that section, returning
|
||||
these as normalised floating-point levels.
|
||||
|
||||
@param startSample the offset into the audio stream to start reading from. It's
|
||||
ok for this to be beyond the start or end of the stream.
|
||||
@param numSamples how many samples to read
|
||||
@param results this array will be filled with Range values for each channel.
|
||||
The array must contain numChannels elements.
|
||||
@param numChannelsToRead the number of channels of data to scan. This must be
|
||||
more than zero, but not more than the total number of channels
|
||||
that the reader contains
|
||||
@see read
|
||||
*/
|
||||
virtual void readMaxLevels (int64 startSample, int64 numSamples,
|
||||
Range<float>* results, int numChannelsToRead);
|
||||
|
||||
/** Finds the highest and lowest sample levels from a section of the audio stream.
|
||||
|
||||
This will read a block of samples from the stream, and measure the
|
||||
highest and lowest sample levels from the channels in that section, returning
|
||||
these as normalised floating-point levels.
|
||||
|
||||
@param startSample the offset into the audio stream to start reading from. It's
|
||||
ok for this to be beyond the start or end of the stream.
|
||||
@param numSamples how many samples to read
|
||||
@param lowestLeft on return, this is the lowest absolute sample from the left channel
|
||||
@param highestLeft on return, this is the highest absolute sample from the left channel
|
||||
@param lowestRight on return, this is the lowest absolute sample from the right
|
||||
channel (if there is one)
|
||||
@param highestRight on return, this is the highest absolute sample from the right
|
||||
channel (if there is one)
|
||||
@see read
|
||||
*/
|
||||
virtual void readMaxLevels (int64 startSample, int64 numSamples,
|
||||
float& lowestLeft, float& highestLeft,
|
||||
float& lowestRight, float& highestRight);
|
||||
|
||||
/** Scans the source looking for a sample whose magnitude is in a specified range.
|
||||
|
||||
This will read from the source, either forwards or backwards between two sample
|
||||
positions, until it finds a sample whose magnitude lies between two specified levels.
|
||||
|
||||
If it finds a suitable sample, it returns its position; if not, it will return -1.
|
||||
|
||||
There's also a minimumConsecutiveSamples setting to help avoid spikes or zero-crossing
|
||||
points when you're searching for a continuous range of samples
|
||||
|
||||
@param startSample the first sample to look at
|
||||
@param numSamplesToSearch the number of samples to scan. If this value is negative,
|
||||
the search will go backwards
|
||||
@param magnitudeRangeMinimum the lowest magnitude (inclusive) that is considered a hit, from 0 to 1.0
|
||||
@param magnitudeRangeMaximum the highest magnitude (inclusive) that is considered a hit, from 0 to 1.0
|
||||
@param minimumConsecutiveSamples if this is > 0, the method will only look for a sequence
|
||||
of this many consecutive samples, all of which lie
|
||||
within the target range. When it finds such a sequence,
|
||||
it returns the position of the first in-range sample
|
||||
it found (i.e. the earliest one if scanning forwards, the
|
||||
latest one if scanning backwards)
|
||||
*/
|
||||
int64 searchForLevel (int64 startSample,
|
||||
int64 numSamplesToSearch,
|
||||
double magnitudeRangeMinimum,
|
||||
double magnitudeRangeMaximum,
|
||||
int minimumConsecutiveSamples);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** The sample-rate of the stream. */
|
||||
double sampleRate = 0;
|
||||
|
||||
/** The number of bits per sample, e.g. 16, 24, 32. */
|
||||
unsigned int bitsPerSample = 0;
|
||||
|
||||
/** The total number of samples in the audio stream. */
|
||||
int64 lengthInSamples = 0;
|
||||
|
||||
/** The total number of channels in the audio stream. */
|
||||
unsigned int numChannels = 0;
|
||||
|
||||
/** Indicates whether the data is floating-point or fixed. */
|
||||
bool usesFloatingPointData = false;
|
||||
|
||||
/** A set of metadata values that the reader has pulled out of the stream.
|
||||
|
||||
Exactly what these values are depends on the format, so you can
|
||||
check out the format implementation code to see what kind of stuff
|
||||
they understand.
|
||||
*/
|
||||
StringPairArray metadataValues;
|
||||
|
||||
/** The input stream, for use by subclasses. */
|
||||
InputStream* input;
|
||||
|
||||
//==============================================================================
|
||||
/** Get the channel layout of the audio stream. */
|
||||
virtual AudioChannelSet getChannelLayout();
|
||||
|
||||
//==============================================================================
|
||||
/** Subclasses must implement this method to perform the low-level read operation.
|
||||
|
||||
Callers should use read() instead of calling this directly.
|
||||
|
||||
@param destSamples the array of destination buffers to fill. Some of these
|
||||
pointers may be null
|
||||
@param numDestChannels the number of items in the destSamples array. This
|
||||
value is guaranteed not to be greater than the number of
|
||||
channels that this reader object contains
|
||||
@param startOffsetInDestBuffer the number of samples from the start of the
|
||||
dest data at which to begin writing
|
||||
@param startSampleInFile the number of samples into the source data at which
|
||||
to begin reading. This value is guaranteed to be >= 0.
|
||||
@param numSamples the number of samples to read
|
||||
*/
|
||||
virtual bool readSamples (int** destSamples,
|
||||
int numDestChannels,
|
||||
int startOffsetInDestBuffer,
|
||||
int64 startSampleInFile,
|
||||
int numSamples) = 0;
|
||||
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Used by AudioFormatReader subclasses to copy data to different formats. */
|
||||
template <class DestSampleType, class SourceSampleType, class SourceEndianness>
|
||||
struct ReadHelper
|
||||
{
|
||||
using DestType = AudioData::Pointer<DestSampleType, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>;
|
||||
using SourceType = AudioData::Pointer<SourceSampleType, SourceEndianness, AudioData::Interleaved, AudioData::Const>;
|
||||
|
||||
template <typename TargetType>
|
||||
static void read (TargetType* const* destData, int destOffset, int numDestChannels,
|
||||
const void* sourceData, int numSourceChannels, int numSamples) noexcept
|
||||
{
|
||||
for (int i = 0; i < numDestChannels; ++i)
|
||||
{
|
||||
if (void* targetChan = destData[i])
|
||||
{
|
||||
DestType dest (targetChan);
|
||||
dest += destOffset;
|
||||
|
||||
if (i < numSourceChannels)
|
||||
dest.convertSamples (SourceType (addBytesToPointer (sourceData, i * SourceType::getBytesPerSample()), numSourceChannels), numSamples);
|
||||
else
|
||||
dest.clearSamples (numSamples);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Used by AudioFormatReader subclasses to clear any parts of the data blocks that lie
|
||||
beyond the end of their available length.
|
||||
*/
|
||||
static void clearSamplesBeyondAvailableLength (int** destSamples, int numDestChannels,
|
||||
int startOffsetInDestBuffer, int64 startSampleInFile,
|
||||
int& numSamples, int64 fileLengthInSamples)
|
||||
{
|
||||
jassert (destSamples != nullptr);
|
||||
const int64 samplesAvailable = fileLengthInSamples - startSampleInFile;
|
||||
|
||||
if (samplesAvailable < numSamples)
|
||||
{
|
||||
for (int i = numDestChannels; --i >= 0;)
|
||||
if (destSamples[i] != nullptr)
|
||||
zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * (size_t) numSamples);
|
||||
|
||||
numSamples = (int) samplesAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
String formatName;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioFormatReader)
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
AudioFormatReaderSource::AudioFormatReaderSource (AudioFormatReader* const r,
|
||||
const bool deleteReaderWhenThisIsDeleted)
|
||||
: reader (r, deleteReaderWhenThisIsDeleted),
|
||||
nextPlayPos (0),
|
||||
looping (false)
|
||||
{
|
||||
jassert (reader != nullptr);
|
||||
}
|
||||
|
||||
AudioFormatReaderSource::~AudioFormatReaderSource() {}
|
||||
|
||||
int64 AudioFormatReaderSource::getTotalLength() const { return reader->lengthInSamples; }
|
||||
void AudioFormatReaderSource::setNextReadPosition (int64 newPosition) { nextPlayPos = newPosition; }
|
||||
void AudioFormatReaderSource::setLooping (bool shouldLoop) { looping = shouldLoop; }
|
||||
|
||||
int64 AudioFormatReaderSource::getNextReadPosition() const
|
||||
{
|
||||
return looping ? nextPlayPos % reader->lengthInSamples
|
||||
: nextPlayPos;
|
||||
}
|
||||
|
||||
void AudioFormatReaderSource::prepareToPlay (int /*samplesPerBlockExpected*/, double /*sampleRate*/) {}
|
||||
void AudioFormatReaderSource::releaseResources() {}
|
||||
|
||||
void AudioFormatReaderSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
|
||||
{
|
||||
if (info.numSamples > 0)
|
||||
{
|
||||
const int64 start = nextPlayPos;
|
||||
|
||||
if (looping)
|
||||
{
|
||||
const int64 newStart = start % reader->lengthInSamples;
|
||||
const int64 newEnd = (start + info.numSamples) % reader->lengthInSamples;
|
||||
|
||||
if (newEnd > newStart)
|
||||
{
|
||||
reader->read (info.buffer, info.startSample,
|
||||
(int) (newEnd - newStart), newStart, true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int endSamps = (int) (reader->lengthInSamples - newStart);
|
||||
|
||||
reader->read (info.buffer, info.startSample,
|
||||
endSamps, newStart, true, true);
|
||||
|
||||
reader->read (info.buffer, info.startSample + endSamps,
|
||||
(int) newEnd, 0, true, true);
|
||||
}
|
||||
|
||||
nextPlayPos = newEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
reader->read (info.buffer, info.startSample,
|
||||
info.numSamples, start, true, true);
|
||||
nextPlayPos += info.numSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
102
modules/juce_audio_formats/format/juce_AudioFormatReaderSource.h
Normal file
102
modules/juce_audio_formats/format/juce_AudioFormatReaderSource.h
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A type of AudioSource that will read from an AudioFormatReader.
|
||||
|
||||
@see PositionableAudioSource, AudioTransportSource, BufferingAudioSource
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API AudioFormatReaderSource : public PositionableAudioSource
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an AudioFormatReaderSource for a given reader.
|
||||
|
||||
@param sourceReader the reader to use as the data source - this must
|
||||
not be null
|
||||
@param deleteReaderWhenThisIsDeleted if true, the reader passed-in will be deleted
|
||||
when this object is deleted; if false it will be
|
||||
left up to the caller to manage its lifetime
|
||||
*/
|
||||
AudioFormatReaderSource (AudioFormatReader* sourceReader,
|
||||
bool deleteReaderWhenThisIsDeleted);
|
||||
|
||||
/** Destructor. */
|
||||
~AudioFormatReaderSource();
|
||||
|
||||
//==============================================================================
|
||||
/** Toggles loop-mode.
|
||||
|
||||
If set to true, it will continuously loop the input source. If false,
|
||||
it will just emit silence after the source has finished.
|
||||
|
||||
@see isLooping
|
||||
*/
|
||||
void setLooping (bool shouldLoop) override;
|
||||
|
||||
/** Returns whether loop-mode is turned on or not. */
|
||||
bool isLooping() const override { return looping; }
|
||||
|
||||
/** Returns the reader that's being used. */
|
||||
AudioFormatReader* getAudioFormatReader() const noexcept { return reader; }
|
||||
|
||||
//==============================================================================
|
||||
/** Implementation of the AudioSource method. */
|
||||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
|
||||
|
||||
/** Implementation of the AudioSource method. */
|
||||
void releaseResources() override;
|
||||
|
||||
/** Implementation of the AudioSource method. */
|
||||
void getNextAudioBlock (const AudioSourceChannelInfo&) override;
|
||||
|
||||
//==============================================================================
|
||||
/** Implements the PositionableAudioSource method. */
|
||||
void setNextReadPosition (int64 newPosition) override;
|
||||
|
||||
/** Implements the PositionableAudioSource method. */
|
||||
int64 getNextReadPosition() const override;
|
||||
|
||||
/** Implements the PositionableAudioSource method. */
|
||||
int64 getTotalLength() const override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
OptionalScopedPointer<AudioFormatReader> reader;
|
||||
|
||||
int64 nextPlayPos;
|
||||
bool looping;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioFormatReaderSource)
|
||||
};
|
||||
|
||||
} // namespace juce
|
361
modules/juce_audio_formats/format/juce_AudioFormatWriter.cpp
Normal file
361
modules/juce_audio_formats/format/juce_AudioFormatWriter.cpp
Normal file
@ -0,0 +1,361 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
AudioFormatWriter::AudioFormatWriter (OutputStream* const out,
|
||||
const String& formatName_,
|
||||
const double rate,
|
||||
const unsigned int numChannels_,
|
||||
const unsigned int bitsPerSample_)
|
||||
: sampleRate (rate),
|
||||
numChannels (numChannels_),
|
||||
bitsPerSample (bitsPerSample_),
|
||||
usesFloatingPointData (false),
|
||||
channelLayout (AudioChannelSet::canonicalChannelSet(static_cast<int> (numChannels_))),
|
||||
output (out),
|
||||
formatName (formatName_)
|
||||
{
|
||||
}
|
||||
|
||||
AudioFormatWriter::AudioFormatWriter (OutputStream* const out,
|
||||
const String& formatName_,
|
||||
const double rate,
|
||||
const AudioChannelSet& channelLayout_,
|
||||
const unsigned int bitsPerSample_)
|
||||
: sampleRate (rate),
|
||||
numChannels (static_cast<unsigned int> (channelLayout_.size())),
|
||||
bitsPerSample (bitsPerSample_),
|
||||
usesFloatingPointData (false),
|
||||
channelLayout (channelLayout_),
|
||||
output (out),
|
||||
formatName (formatName_)
|
||||
{
|
||||
}
|
||||
|
||||
AudioFormatWriter::~AudioFormatWriter()
|
||||
{
|
||||
delete output;
|
||||
}
|
||||
|
||||
static void convertFloatsToInts (int* dest, const float* src, int numSamples) noexcept
|
||||
{
|
||||
while (--numSamples >= 0)
|
||||
{
|
||||
const double samp = *src++;
|
||||
|
||||
if (samp <= -1.0)
|
||||
*dest = std::numeric_limits<int>::min();
|
||||
else if (samp >= 1.0)
|
||||
*dest = std::numeric_limits<int>::max();
|
||||
else
|
||||
*dest = roundToInt (std::numeric_limits<int>::max() * samp);
|
||||
|
||||
++dest;
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioFormatWriter::writeFromAudioReader (AudioFormatReader& reader,
|
||||
int64 startSample,
|
||||
int64 numSamplesToRead)
|
||||
{
|
||||
const int bufferSize = 16384;
|
||||
AudioBuffer<float> tempBuffer ((int) numChannels, bufferSize);
|
||||
|
||||
int* buffers[128] = { 0 };
|
||||
|
||||
for (int i = tempBuffer.getNumChannels(); --i >= 0;)
|
||||
buffers[i] = reinterpret_cast<int*> (tempBuffer.getWritePointer (i, 0));
|
||||
|
||||
if (numSamplesToRead < 0)
|
||||
numSamplesToRead = reader.lengthInSamples;
|
||||
|
||||
while (numSamplesToRead > 0)
|
||||
{
|
||||
const int numToDo = (int) jmin (numSamplesToRead, (int64) bufferSize);
|
||||
|
||||
if (! reader.read (buffers, (int) numChannels, startSample, numToDo, false))
|
||||
return false;
|
||||
|
||||
if (reader.usesFloatingPointData != isFloatingPoint())
|
||||
{
|
||||
int** bufferChan = buffers;
|
||||
|
||||
while (*bufferChan != nullptr)
|
||||
{
|
||||
void* const b = *bufferChan++;
|
||||
|
||||
if (isFloatingPoint())
|
||||
FloatVectorOperations::convertFixedToFloat ((float*) b, (int*) b, 1.0f / 0x7fffffff, numToDo);
|
||||
else
|
||||
convertFloatsToInts ((int*) b, (float*) b, numToDo);
|
||||
}
|
||||
}
|
||||
|
||||
if (! write (const_cast<const int**> (buffers), numToDo))
|
||||
return false;
|
||||
|
||||
numSamplesToRead -= numToDo;
|
||||
startSample += numToDo;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioFormatWriter::writeFromAudioSource (AudioSource& source, int numSamplesToRead, const int samplesPerBlock)
|
||||
{
|
||||
AudioBuffer<float> tempBuffer (getNumChannels(), samplesPerBlock);
|
||||
|
||||
while (numSamplesToRead > 0)
|
||||
{
|
||||
auto numToDo = jmin (numSamplesToRead, samplesPerBlock);
|
||||
|
||||
AudioSourceChannelInfo info (&tempBuffer, 0, numToDo);
|
||||
info.clearActiveBufferRegion();
|
||||
|
||||
source.getNextAudioBlock (info);
|
||||
|
||||
if (! writeFromAudioSampleBuffer (tempBuffer, 0, numToDo))
|
||||
return false;
|
||||
|
||||
numSamplesToRead -= numToDo;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioFormatWriter::writeFromFloatArrays (const float* const* channels, int numSourceChannels, int numSamples)
|
||||
{
|
||||
if (numSamples <= 0)
|
||||
return true;
|
||||
|
||||
if (isFloatingPoint())
|
||||
return write ((const int**) channels, numSamples);
|
||||
|
||||
int* chans[256];
|
||||
int scratch[4096];
|
||||
|
||||
jassert (numSourceChannels < numElementsInArray (chans));
|
||||
const int maxSamples = (int) (numElementsInArray (scratch) / numSourceChannels);
|
||||
|
||||
for (int i = 0; i < numSourceChannels; ++i)
|
||||
chans[i] = scratch + (i * maxSamples);
|
||||
|
||||
chans[numSourceChannels] = nullptr;
|
||||
int startSample = 0;
|
||||
|
||||
while (numSamples > 0)
|
||||
{
|
||||
auto numToDo = jmin (numSamples, maxSamples);
|
||||
|
||||
for (int i = 0; i < numSourceChannels; ++i)
|
||||
convertFloatsToInts (chans[i], channels[i] + startSample, numToDo);
|
||||
|
||||
if (! write ((const int**) chans, numToDo))
|
||||
return false;
|
||||
|
||||
startSample += numToDo;
|
||||
numSamples -= numToDo;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioFormatWriter::writeFromAudioSampleBuffer (const AudioBuffer<float>& source, int startSample, int numSamples)
|
||||
{
|
||||
auto numSourceChannels = source.getNumChannels();
|
||||
jassert (startSample >= 0 && startSample + numSamples <= source.getNumSamples() && numSourceChannels > 0);
|
||||
|
||||
if (startSample == 0)
|
||||
return writeFromFloatArrays (source.getArrayOfReadPointers(), numSourceChannels, numSamples);
|
||||
|
||||
const float* chans[256];
|
||||
jassert ((int) numChannels < numElementsInArray (chans));
|
||||
|
||||
for (int i = 0; i < numSourceChannels; ++i)
|
||||
chans[i] = source.getReadPointer (i, startSample);
|
||||
|
||||
chans[numSourceChannels] = nullptr;
|
||||
|
||||
return writeFromFloatArrays (chans, numSourceChannels, numSamples);
|
||||
}
|
||||
|
||||
bool AudioFormatWriter::flush()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class AudioFormatWriter::ThreadedWriter::Buffer : private TimeSliceClient
|
||||
{
|
||||
public:
|
||||
Buffer (TimeSliceThread& tst, AudioFormatWriter* w, int channels, int numSamples)
|
||||
: fifo (numSamples),
|
||||
buffer (channels, numSamples),
|
||||
timeSliceThread (tst),
|
||||
writer (w)
|
||||
{
|
||||
timeSliceThread.addTimeSliceClient (this);
|
||||
}
|
||||
|
||||
~Buffer()
|
||||
{
|
||||
isRunning = false;
|
||||
timeSliceThread.removeTimeSliceClient (this);
|
||||
|
||||
while (writePendingData() == 0)
|
||||
{}
|
||||
}
|
||||
|
||||
bool write (const float* const* data, int numSamples)
|
||||
{
|
||||
if (numSamples <= 0 || ! isRunning)
|
||||
return true;
|
||||
|
||||
jassert (timeSliceThread.isThreadRunning()); // you need to get your thread running before pumping data into this!
|
||||
|
||||
int start1, size1, start2, size2;
|
||||
fifo.prepareToWrite (numSamples, start1, size1, start2, size2);
|
||||
|
||||
if (size1 + size2 < numSamples)
|
||||
return false;
|
||||
|
||||
for (int i = buffer.getNumChannels(); --i >= 0;)
|
||||
{
|
||||
buffer.copyFrom (i, start1, data[i], size1);
|
||||
buffer.copyFrom (i, start2, data[i] + size1, size2);
|
||||
}
|
||||
|
||||
fifo.finishedWrite (size1 + size2);
|
||||
timeSliceThread.notify();
|
||||
return true;
|
||||
}
|
||||
|
||||
int useTimeSlice() override
|
||||
{
|
||||
return writePendingData();
|
||||
}
|
||||
|
||||
int writePendingData()
|
||||
{
|
||||
auto numToDo = fifo.getTotalSize() / 4;
|
||||
|
||||
int start1, size1, start2, size2;
|
||||
fifo.prepareToRead (numToDo, start1, size1, start2, size2);
|
||||
|
||||
if (size1 <= 0)
|
||||
return 10;
|
||||
|
||||
writer->writeFromAudioSampleBuffer (buffer, start1, size1);
|
||||
|
||||
const ScopedLock sl (thumbnailLock);
|
||||
|
||||
if (receiver != nullptr)
|
||||
receiver->addBlock (samplesWritten, buffer, start1, size1);
|
||||
|
||||
samplesWritten += size1;
|
||||
|
||||
if (size2 > 0)
|
||||
{
|
||||
writer->writeFromAudioSampleBuffer (buffer, start2, size2);
|
||||
|
||||
if (receiver != nullptr)
|
||||
receiver->addBlock (samplesWritten, buffer, start2, size2);
|
||||
|
||||
samplesWritten += size2;
|
||||
}
|
||||
|
||||
fifo.finishedRead (size1 + size2);
|
||||
|
||||
if (samplesPerFlush > 0)
|
||||
{
|
||||
flushSampleCounter -= size1 + size2;
|
||||
|
||||
if (flushSampleCounter <= 0)
|
||||
{
|
||||
flushSampleCounter = samplesPerFlush;
|
||||
writer->flush();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setDataReceiver (IncomingDataReceiver* newReceiver)
|
||||
{
|
||||
if (newReceiver != nullptr)
|
||||
newReceiver->reset (buffer.getNumChannels(), writer->getSampleRate(), 0);
|
||||
|
||||
const ScopedLock sl (thumbnailLock);
|
||||
receiver = newReceiver;
|
||||
samplesWritten = 0;
|
||||
}
|
||||
|
||||
void setFlushInterval (int numSamples) noexcept
|
||||
{
|
||||
samplesPerFlush = numSamples;
|
||||
}
|
||||
|
||||
private:
|
||||
AbstractFifo fifo;
|
||||
AudioBuffer<float> buffer;
|
||||
TimeSliceThread& timeSliceThread;
|
||||
std::unique_ptr<AudioFormatWriter> writer;
|
||||
CriticalSection thumbnailLock;
|
||||
IncomingDataReceiver* receiver = {};
|
||||
int64 samplesWritten = 0;
|
||||
int samplesPerFlush = 0, flushSampleCounter = 0;
|
||||
std::atomic<bool> isRunning { true };
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (Buffer)
|
||||
};
|
||||
|
||||
AudioFormatWriter::ThreadedWriter::ThreadedWriter (AudioFormatWriter* writer, TimeSliceThread& backgroundThread, int numSamplesToBuffer)
|
||||
: buffer (new AudioFormatWriter::ThreadedWriter::Buffer (backgroundThread, writer, (int) writer->numChannels, numSamplesToBuffer))
|
||||
{
|
||||
}
|
||||
|
||||
AudioFormatWriter::ThreadedWriter::~ThreadedWriter()
|
||||
{
|
||||
}
|
||||
|
||||
bool AudioFormatWriter::ThreadedWriter::write (const float* const* data, int numSamples)
|
||||
{
|
||||
return buffer->write (data, numSamples);
|
||||
}
|
||||
|
||||
void AudioFormatWriter::ThreadedWriter::setDataReceiver (AudioFormatWriter::ThreadedWriter::IncomingDataReceiver* receiver)
|
||||
{
|
||||
buffer->setDataReceiver (receiver);
|
||||
}
|
||||
|
||||
void AudioFormatWriter::ThreadedWriter::setFlushInterval (int numSamplesPerFlush) noexcept
|
||||
{
|
||||
buffer->setFlushInterval (numSamplesPerFlush);
|
||||
}
|
||||
|
||||
} // namespace juce
|
301
modules/juce_audio_formats/format/juce_AudioFormatWriter.h
Normal file
301
modules/juce_audio_formats/format/juce_AudioFormatWriter.h
Normal file
@ -0,0 +1,301 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Writes samples to an audio file stream.
|
||||
|
||||
A subclass that writes a specific type of audio format will be created by
|
||||
an AudioFormat object.
|
||||
|
||||
After creating one of these with the AudioFormat::createWriterFor() method
|
||||
you can call its write() method to store the samples, and then delete it.
|
||||
|
||||
@see AudioFormat, AudioFormatReader
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API AudioFormatWriter
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Creates an AudioFormatWriter object.
|
||||
|
||||
@param destStream the stream to write to - this will be deleted
|
||||
by this object when it is no longer needed
|
||||
@param formatName the description that will be returned by the getFormatName()
|
||||
method
|
||||
@param sampleRate the sample rate to use - the base class just stores
|
||||
this value, it doesn't do anything with it
|
||||
@param numberOfChannels the number of channels to write - the base class just stores
|
||||
this value, it doesn't do anything with it
|
||||
@param bitsPerSample the bit depth of the stream - the base class just stores
|
||||
this value, it doesn't do anything with it
|
||||
*/
|
||||
AudioFormatWriter (OutputStream* destStream,
|
||||
const String& formatName,
|
||||
double sampleRate,
|
||||
unsigned int numberOfChannels,
|
||||
unsigned int bitsPerSample);
|
||||
|
||||
//==============================================================================
|
||||
/** Creates an AudioFormatWriter object.
|
||||
|
||||
@param destStream the stream to write to - this will be deleted
|
||||
by this object when it is no longer needed
|
||||
@param formatName the description that will be returned by the getFormatName()
|
||||
method
|
||||
@param sampleRate the sample rate to use - the base class just stores
|
||||
this value, it doesn't do anything with it
|
||||
@param audioChannelLayout the channel layout to use for the writer - the base class
|
||||
just stores this value, it doesn't do anything with it
|
||||
@param bitsPerSample the bit depth of the stream - the base class just stores
|
||||
this value, it doesn't do anything with it
|
||||
*/
|
||||
AudioFormatWriter (OutputStream* destStream,
|
||||
const String& formatName,
|
||||
double sampleRate,
|
||||
const AudioChannelSet& audioChannelLayout,
|
||||
unsigned int bitsPerSample);
|
||||
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~AudioFormatWriter();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a description of what type of format this is.
|
||||
|
||||
E.g. "AIFF file"
|
||||
*/
|
||||
const String& getFormatName() const noexcept { return formatName; }
|
||||
|
||||
//==============================================================================
|
||||
/** Writes a set of samples to the audio stream.
|
||||
|
||||
Note that if you're trying to write the contents of an AudioBuffer, you
|
||||
can use writeFromAudioSampleBuffer().
|
||||
|
||||
@param samplesToWrite an array of arrays containing the sample data for
|
||||
each channel to write. This is a zero-terminated
|
||||
array of arrays, and can contain a different number
|
||||
of channels than the actual stream uses, and the
|
||||
writer should do its best to cope with this.
|
||||
If the format is fixed-point, each channel will be formatted
|
||||
as an array of signed integers using the full 32-bit
|
||||
range -0x80000000 to 0x7fffffff, regardless of the source's
|
||||
bit-depth. If it is a floating-point format, you should treat
|
||||
the arrays as arrays of floats, and just cast it to an (int**)
|
||||
to pass it into the method.
|
||||
@param numSamples the number of samples to write
|
||||
*/
|
||||
virtual bool write (const int** samplesToWrite, int numSamples) = 0;
|
||||
|
||||
/** Some formats may support a flush operation that makes sure the file is in a
|
||||
valid state before carrying on.
|
||||
If supported, this means that by calling flush periodically when writing data
|
||||
to a large file, then it should still be left in a readable state if your program
|
||||
crashes.
|
||||
It goes without saying that this method must be called from the same thread that's
|
||||
calling write()!
|
||||
If the format supports flushing and the operation succeeds, this returns true.
|
||||
*/
|
||||
virtual bool flush();
|
||||
|
||||
//==============================================================================
|
||||
/** Reads a section of samples from an AudioFormatReader, and writes these to
|
||||
the output.
|
||||
|
||||
This will take care of any floating-point conversion that's required to convert
|
||||
between the two formats. It won't deal with sample-rate conversion, though.
|
||||
|
||||
If numSamplesToRead < 0, it will write the entire length of the reader.
|
||||
|
||||
@returns false if it can't read or write properly during the operation
|
||||
*/
|
||||
bool writeFromAudioReader (AudioFormatReader& reader,
|
||||
int64 startSample,
|
||||
int64 numSamplesToRead);
|
||||
|
||||
/** Reads some samples from an AudioSource, and writes these to the output.
|
||||
|
||||
The source must already have been initialised with the AudioSource::prepareToPlay() method
|
||||
|
||||
@param source the source to read from
|
||||
@param numSamplesToRead total number of samples to read and write
|
||||
@param samplesPerBlock the maximum number of samples to fetch from the source
|
||||
@returns false if it can't read or write properly during the operation
|
||||
*/
|
||||
bool writeFromAudioSource (AudioSource& source,
|
||||
int numSamplesToRead,
|
||||
int samplesPerBlock = 2048);
|
||||
|
||||
|
||||
/** Writes some samples from an AudioBuffer. */
|
||||
bool writeFromAudioSampleBuffer (const AudioBuffer<float>& source,
|
||||
int startSample, int numSamples);
|
||||
|
||||
/** Writes some samples from a set of float data channels. */
|
||||
bool writeFromFloatArrays (const float* const* channels, int numChannels, int numSamples);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the sample rate being used. */
|
||||
double getSampleRate() const noexcept { return sampleRate; }
|
||||
|
||||
/** Returns the number of channels being written. */
|
||||
int getNumChannels() const noexcept { return (int) numChannels; }
|
||||
|
||||
/** Returns the bit-depth of the data being written. */
|
||||
int getBitsPerSample() const noexcept { return (int) bitsPerSample; }
|
||||
|
||||
/** Returns true if it's a floating-point format, false if it's fixed-point. */
|
||||
bool isFloatingPoint() const noexcept { return usesFloatingPointData; }
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Provides a FIFO for an AudioFormatWriter, allowing you to push incoming
|
||||
data into a buffer which will be flushed to disk by a background thread.
|
||||
*/
|
||||
class ThreadedWriter
|
||||
{
|
||||
public:
|
||||
/** Creates a ThreadedWriter for a given writer and a thread.
|
||||
|
||||
The writer object which is passed in here will be owned and deleted by
|
||||
the ThreadedWriter when it is no longer needed.
|
||||
|
||||
To stop the writer and flush the buffer to disk, simply delete this object.
|
||||
*/
|
||||
ThreadedWriter (AudioFormatWriter* writer,
|
||||
TimeSliceThread& backgroundThread,
|
||||
int numSamplesToBuffer);
|
||||
|
||||
/** Destructor. */
|
||||
~ThreadedWriter();
|
||||
|
||||
/** Pushes some incoming audio data into the FIFO.
|
||||
|
||||
If there's enough free space in the buffer, this will add the data to it,
|
||||
|
||||
If the FIFO is too full to accept this many samples, the method will return
|
||||
false - then you could either wait until the background thread has had time to
|
||||
consume some of the buffered data and try again, or you can give up
|
||||
and lost this block.
|
||||
|
||||
The data must be an array containing the same number of channels as the
|
||||
AudioFormatWriter object is using. None of these channels can be null.
|
||||
*/
|
||||
bool write (const float* const* data, int numSamples);
|
||||
|
||||
/** Receiver for incoming data. */
|
||||
class JUCE_API IncomingDataReceiver
|
||||
{
|
||||
public:
|
||||
IncomingDataReceiver() {}
|
||||
virtual ~IncomingDataReceiver() {}
|
||||
|
||||
virtual void reset (int numChannels, double sampleRate, int64 totalSamplesInSource) = 0;
|
||||
virtual void addBlock (int64 sampleNumberInSource, const AudioBuffer<float>& newData,
|
||||
int startOffsetInBuffer, int numSamples) = 0;
|
||||
};
|
||||
|
||||
/** Allows you to specify a callback that this writer should update with the
|
||||
incoming data.
|
||||
The receiver will be cleared and the writer will begin adding data to it
|
||||
as the data arrives. Pass a null pointer to remove the current receiver.
|
||||
|
||||
The object passed-in must not be deleted while this writer is still using it.
|
||||
*/
|
||||
void setDataReceiver (IncomingDataReceiver*);
|
||||
|
||||
/** Sets how many samples should be written before calling the AudioFormatWriter::flush method.
|
||||
Set this to 0 to disable flushing (this is the default).
|
||||
*/
|
||||
void setFlushInterval (int numSamplesPerFlush) noexcept;
|
||||
|
||||
private:
|
||||
class Buffer;
|
||||
friend struct ContainerDeletePolicy<Buffer>;
|
||||
std::unique_ptr<Buffer> buffer;
|
||||
};
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** The sample rate of the stream. */
|
||||
double sampleRate;
|
||||
|
||||
/** The number of channels being written to the stream. */
|
||||
unsigned int numChannels;
|
||||
|
||||
/** The bit depth of the file. */
|
||||
unsigned int bitsPerSample;
|
||||
|
||||
/** True if it's a floating-point format, false if it's fixed-point. */
|
||||
bool usesFloatingPointData;
|
||||
|
||||
/** The audio channel layout that the writer should use */
|
||||
AudioChannelSet channelLayout;
|
||||
|
||||
/** The output stream for use by subclasses. */
|
||||
OutputStream* output;
|
||||
|
||||
/** Used by AudioFormatWriter subclasses to copy data to different formats. */
|
||||
template <class DestSampleType, class SourceSampleType, class DestEndianness>
|
||||
struct WriteHelper
|
||||
{
|
||||
using DestType = AudioData::Pointer <DestSampleType, DestEndianness, AudioData::Interleaved, AudioData::NonConst>;
|
||||
using SourceType = AudioData::Pointer <SourceSampleType, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>;
|
||||
|
||||
static void write (void* destData, int numDestChannels, const int* const* source,
|
||||
int numSamples, const int sourceOffset = 0) noexcept
|
||||
{
|
||||
for (int i = 0; i < numDestChannels; ++i)
|
||||
{
|
||||
const DestType dest (addBytesToPointer (destData, i * DestType::getBytesPerSample()), numDestChannels);
|
||||
|
||||
if (*source != nullptr)
|
||||
{
|
||||
dest.convertSamples (SourceType (*source + sourceOffset), numSamples);
|
||||
++source;
|
||||
}
|
||||
else
|
||||
{
|
||||
dest.clearSamples (numSamples);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
String formatName;
|
||||
friend class ThreadedWriter;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioFormatWriter)
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
AudioSubsectionReader::AudioSubsectionReader (AudioFormatReader* const source_,
|
||||
const int64 startSample_,
|
||||
const int64 length_,
|
||||
const bool deleteSourceWhenDeleted_)
|
||||
: AudioFormatReader (0, source_->getFormatName()),
|
||||
source (source_),
|
||||
startSample (startSample_),
|
||||
deleteSourceWhenDeleted (deleteSourceWhenDeleted_)
|
||||
{
|
||||
length = jmin (jmax ((int64) 0, source->lengthInSamples - startSample), length_);
|
||||
|
||||
sampleRate = source->sampleRate;
|
||||
bitsPerSample = source->bitsPerSample;
|
||||
lengthInSamples = length;
|
||||
numChannels = source->numChannels;
|
||||
usesFloatingPointData = source->usesFloatingPointData;
|
||||
}
|
||||
|
||||
AudioSubsectionReader::~AudioSubsectionReader()
|
||||
{
|
||||
if (deleteSourceWhenDeleted)
|
||||
delete source;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool AudioSubsectionReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
|
||||
int64 startSampleInFile, int numSamples)
|
||||
{
|
||||
clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
|
||||
startSampleInFile, numSamples, length);
|
||||
|
||||
return source->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer,
|
||||
startSampleInFile + startSample, numSamples);
|
||||
}
|
||||
|
||||
void AudioSubsectionReader::readMaxLevels (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead)
|
||||
{
|
||||
startSampleInFile = jmax ((int64) 0, startSampleInFile);
|
||||
numSamples = jmax ((int64) 0, jmin (numSamples, length - startSampleInFile));
|
||||
|
||||
source->readMaxLevels (startSampleInFile + startSample, numSamples, results, numChannelsToRead);
|
||||
}
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This class is used to wrap an AudioFormatReader and only read from a
|
||||
subsection of the file.
|
||||
|
||||
So if you have a reader which can read a 1000 sample file, you could wrap it
|
||||
in one of these to only access, e.g. samples 100 to 200, and any samples
|
||||
outside that will come back as 0. Accessing sample 0 from this reader will
|
||||
actually read the first sample from the other's subsection, which might
|
||||
be at a non-zero position.
|
||||
|
||||
@see AudioFormatReader
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API AudioSubsectionReader : public AudioFormatReader
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an AudioSubsectionReader for a given data source.
|
||||
|
||||
@param sourceReader the source reader from which we'll be taking data
|
||||
@param subsectionStartSample the sample within the source reader which will be
|
||||
mapped onto sample 0 for this reader.
|
||||
@param subsectionLength the number of samples from the source that will
|
||||
make up the subsection. If this reader is asked for
|
||||
any samples beyond this region, it will return zero.
|
||||
@param deleteSourceWhenDeleted if true, the sourceReader object will be deleted when
|
||||
this object is deleted.
|
||||
*/
|
||||
AudioSubsectionReader (AudioFormatReader* sourceReader,
|
||||
int64 subsectionStartSample,
|
||||
int64 subsectionLength,
|
||||
bool deleteSourceWhenDeleted);
|
||||
|
||||
/** Destructor. */
|
||||
~AudioSubsectionReader();
|
||||
|
||||
|
||||
//==============================================================================
|
||||
bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
|
||||
int64 startSampleInFile, int numSamples) override;
|
||||
|
||||
void readMaxLevels (int64 startSample, int64 numSamples,
|
||||
Range<float>* results, int numChannelsToRead) override;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
AudioFormatReader* const source;
|
||||
int64 startSample, length;
|
||||
const bool deleteSourceWhenDeleted;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSubsectionReader)
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,180 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
BufferingAudioReader::BufferingAudioReader (AudioFormatReader* sourceReader,
|
||||
TimeSliceThread& timeSliceThread,
|
||||
int samplesToBuffer)
|
||||
: AudioFormatReader (nullptr, sourceReader->getFormatName()),
|
||||
source (sourceReader), thread (timeSliceThread),
|
||||
nextReadPosition (0),
|
||||
numBlocks (1 + (samplesToBuffer / samplesPerBlock)),
|
||||
timeoutMs (0)
|
||||
{
|
||||
sampleRate = source->sampleRate;
|
||||
lengthInSamples = source->lengthInSamples;
|
||||
numChannels = source->numChannels;
|
||||
metadataValues = source->metadataValues;
|
||||
bitsPerSample = 32;
|
||||
usesFloatingPointData = true;
|
||||
|
||||
for (int i = 3; --i >= 0;)
|
||||
readNextBufferChunk();
|
||||
|
||||
timeSliceThread.addTimeSliceClient (this);
|
||||
}
|
||||
|
||||
BufferingAudioReader::~BufferingAudioReader()
|
||||
{
|
||||
thread.removeTimeSliceClient (this);
|
||||
}
|
||||
|
||||
void BufferingAudioReader::setReadTimeout (int timeoutMilliseconds) noexcept
|
||||
{
|
||||
timeoutMs = timeoutMilliseconds;
|
||||
}
|
||||
|
||||
bool BufferingAudioReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
|
||||
int64 startSampleInFile, int numSamples)
|
||||
{
|
||||
const uint32 startTime = Time::getMillisecondCounter();
|
||||
clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
|
||||
startSampleInFile, numSamples, lengthInSamples);
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
nextReadPosition = startSampleInFile;
|
||||
|
||||
while (numSamples > 0)
|
||||
{
|
||||
if (const BufferedBlock* const block = getBlockContaining (startSampleInFile))
|
||||
{
|
||||
const int offset = (int) (startSampleInFile - block->range.getStart());
|
||||
const int numToDo = jmin (numSamples, (int) (block->range.getEnd() - startSampleInFile));
|
||||
|
||||
for (int j = 0; j < numDestChannels; ++j)
|
||||
{
|
||||
if (float* dest = (float*) destSamples[j])
|
||||
{
|
||||
dest += startOffsetInDestBuffer;
|
||||
|
||||
if (j < (int) numChannels)
|
||||
FloatVectorOperations::copy (dest, block->buffer.getReadPointer (j, offset), numToDo);
|
||||
else
|
||||
FloatVectorOperations::clear (dest, numToDo);
|
||||
}
|
||||
}
|
||||
|
||||
startOffsetInDestBuffer += numToDo;
|
||||
startSampleInFile += numToDo;
|
||||
numSamples -= numToDo;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (timeoutMs >= 0 && Time::getMillisecondCounter() >= startTime + (uint32) timeoutMs)
|
||||
{
|
||||
for (int j = 0; j < numDestChannels; ++j)
|
||||
if (float* dest = (float*) destSamples[j])
|
||||
FloatVectorOperations::clear (dest + startOffsetInDestBuffer, numSamples);
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
ScopedUnlock ul (lock);
|
||||
Thread::yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BufferingAudioReader::BufferedBlock::BufferedBlock (AudioFormatReader& reader, int64 pos, int numSamples)
|
||||
: range (pos, pos + numSamples),
|
||||
buffer ((int) reader.numChannels, numSamples)
|
||||
{
|
||||
reader.read (&buffer, 0, numSamples, pos, true, true);
|
||||
}
|
||||
|
||||
BufferingAudioReader::BufferedBlock* BufferingAudioReader::getBlockContaining (int64 pos) const noexcept
|
||||
{
|
||||
for (int i = blocks.size(); --i >= 0;)
|
||||
{
|
||||
BufferedBlock* const b = blocks.getUnchecked(i);
|
||||
|
||||
if (b->range.contains (pos))
|
||||
return b;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int BufferingAudioReader::useTimeSlice()
|
||||
{
|
||||
return readNextBufferChunk() ? 1 : 100;
|
||||
}
|
||||
|
||||
bool BufferingAudioReader::readNextBufferChunk()
|
||||
{
|
||||
const int64 pos = nextReadPosition;
|
||||
const int64 startPos = ((pos - 1024) / samplesPerBlock) * samplesPerBlock;
|
||||
const int64 endPos = startPos + numBlocks * samplesPerBlock;
|
||||
|
||||
OwnedArray<BufferedBlock> newBlocks;
|
||||
|
||||
for (int i = blocks.size(); --i >= 0;)
|
||||
if (blocks.getUnchecked(i)->range.intersects (Range<int64> (startPos, endPos)))
|
||||
newBlocks.add (blocks.getUnchecked(i));
|
||||
|
||||
if (newBlocks.size() == numBlocks)
|
||||
{
|
||||
newBlocks.clear (false);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int64 p = startPos; p < endPos; p += samplesPerBlock)
|
||||
{
|
||||
if (getBlockContaining (p) == nullptr)
|
||||
{
|
||||
newBlocks.add (new BufferedBlock (*source, p, samplesPerBlock));
|
||||
break; // just do one block
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
newBlocks.swapWith (blocks);
|
||||
}
|
||||
|
||||
for (int i = blocks.size(); --i >= 0;)
|
||||
newBlocks.removeObject (blocks.getUnchecked(i), false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An AudioFormatReader that uses a background thread to pre-read data from
|
||||
another reader.
|
||||
|
||||
@see AudioFormatReader
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API BufferingAudioReader : public AudioFormatReader,
|
||||
private TimeSliceClient
|
||||
{
|
||||
public:
|
||||
/** Creates a reader.
|
||||
|
||||
@param sourceReader the source reader to wrap. This BufferingAudioReader
|
||||
takes ownership of this object and will delete it later
|
||||
when no longer needed
|
||||
@param timeSliceThread the thread that should be used to do the background reading.
|
||||
Make sure that the thread you supply is running, and won't
|
||||
be deleted while the reader object still exists.
|
||||
@param samplesToBuffer the total number of samples to buffer ahead.
|
||||
*/
|
||||
BufferingAudioReader (AudioFormatReader* sourceReader,
|
||||
TimeSliceThread& timeSliceThread,
|
||||
int samplesToBuffer);
|
||||
|
||||
~BufferingAudioReader();
|
||||
|
||||
/** Sets a number of milliseconds that the reader can block for in its readSamples()
|
||||
method before giving up and returning silence.
|
||||
A value of less that 0 means "wait forever".
|
||||
The default timeout is 0.
|
||||
*/
|
||||
void setReadTimeout (int timeoutMilliseconds) noexcept;
|
||||
|
||||
bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
|
||||
int64 startSampleInFile, int numSamples) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<AudioFormatReader> source;
|
||||
TimeSliceThread& thread;
|
||||
int64 nextReadPosition;
|
||||
const int numBlocks;
|
||||
int timeoutMs;
|
||||
|
||||
enum { samplesPerBlock = 32768 };
|
||||
|
||||
struct BufferedBlock
|
||||
{
|
||||
BufferedBlock (AudioFormatReader& reader, int64 pos, int numSamples);
|
||||
|
||||
Range<int64> range;
|
||||
AudioBuffer<float> buffer;
|
||||
};
|
||||
|
||||
CriticalSection lock;
|
||||
OwnedArray<BufferedBlock> blocks;
|
||||
|
||||
BufferedBlock* getBlockContaining (int64 pos) const noexcept;
|
||||
int useTimeSlice() override;
|
||||
bool readNextBufferChunk();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioReader)
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A specialised type of AudioFormatReader that uses a MemoryMappedFile to read
|
||||
directly from an audio file.
|
||||
|
||||
This allows for incredibly fast random-access to sample data in the mapped
|
||||
region of the file, but not all audio formats support it - see
|
||||
AudioFormat::createMemoryMappedReader().
|
||||
|
||||
Note that before reading samples from a MemoryMappedAudioFormatReader, you must first
|
||||
call mapEntireFile() or mapSectionOfFile() to ensure that the region you want to
|
||||
read has been mapped.
|
||||
|
||||
@see AudioFormat::createMemoryMappedReader, AudioFormatReader
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API MemoryMappedAudioFormatReader : public AudioFormatReader
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Creates an MemoryMappedAudioFormatReader object.
|
||||
|
||||
Note that before attempting to read any data, you must call mapEntireFile()
|
||||
or mapSectionOfFile() to ensure that the region you want to read has
|
||||
been mapped.
|
||||
*/
|
||||
MemoryMappedAudioFormatReader (const File& file, const AudioFormatReader& details,
|
||||
int64 dataChunkStart, int64 dataChunkLength, int bytesPerFrame);
|
||||
|
||||
public:
|
||||
/** Returns the file that is being mapped */
|
||||
const File& getFile() const noexcept { return file; }
|
||||
|
||||
/** Attempts to map the entire file into memory. */
|
||||
bool mapEntireFile();
|
||||
|
||||
/** Attempts to map a section of the file into memory. */
|
||||
virtual bool mapSectionOfFile (Range<int64> samplesToMap);
|
||||
|
||||
/** Returns the sample range that's currently memory-mapped and available for reading. */
|
||||
Range<int64> getMappedSection() const noexcept { return mappedSection; }
|
||||
|
||||
/** Touches the memory for the given sample, to force it to be loaded into active memory. */
|
||||
void touchSample (int64 sample) const noexcept;
|
||||
|
||||
/** Returns the samples for all channels at a given sample position.
|
||||
The result array must be large enough to hold a value for each channel
|
||||
that this reader contains.
|
||||
*/
|
||||
virtual void getSample (int64 sampleIndex, float* result) const noexcept = 0;
|
||||
|
||||
/** Returns the number of bytes currently being mapped */
|
||||
size_t getNumBytesUsed() const { return map != nullptr ? map->getSize() : 0; }
|
||||
|
||||
protected:
|
||||
File file;
|
||||
Range<int64> mappedSection;
|
||||
std::unique_ptr<MemoryMappedFile> map;
|
||||
int64 dataChunkStart, dataLength;
|
||||
int bytesPerFrame;
|
||||
|
||||
/** Converts a sample index to a byte position in the file. */
|
||||
inline int64 sampleToFilePos (int64 sample) const noexcept { return dataChunkStart + sample * bytesPerFrame; }
|
||||
|
||||
/** Converts a byte position in the file to a sample index. */
|
||||
inline int64 filePosToSample (int64 filePos) const noexcept { return (filePos - dataChunkStart) / bytesPerFrame; }
|
||||
|
||||
/** Converts a sample index to a pointer to the mapped file memory. */
|
||||
inline const void* sampleToPointer (int64 sample) const noexcept { return addBytesToPointer (map->getData(), sampleToFilePos (sample) - map->getRange().getStart()); }
|
||||
|
||||
/** Used by AudioFormatReader subclasses to scan for min/max ranges in interleaved data. */
|
||||
template <typename SampleType, typename Endianness>
|
||||
Range<float> scanMinAndMaxInterleaved (int channel, int64 startSampleInFile, int64 numSamples) const noexcept
|
||||
{
|
||||
using SourceType = AudioData::Pointer <SampleType, Endianness, AudioData::Interleaved, AudioData::Const>;
|
||||
|
||||
return SourceType (addBytesToPointer (sampleToPointer (startSampleInFile), ((int) bitsPerSample / 8) * channel), (int) numChannels)
|
||||
.findMinAndMax ((size_t) numSamples);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedAudioFormatReader)
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user