Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move audio import into Trade #326

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 68 additions & 8 deletions src/Magnum/Audio/AbstractImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,20 @@
#include "AbstractImporter.h"

#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/Assert.h>
#include <Corrade/Utility/Directory.h>

#include "Magnum/FileCallback.h"

#ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT
#include "Magnum/Audio/configure.h"
#endif

namespace Magnum { namespace Audio {

std::string AbstractImporter::pluginInterface() {
return "cz.mosra.magnum.Audio.AbstractImporter/0.1";
return "cz.mosra.magnum.Audio.AbstractImporter/0.2";
}

#ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT
Expand All @@ -59,10 +62,24 @@ AbstractImporter::AbstractImporter(PluginManager::Manager<AbstractImporter>& man

AbstractImporter::AbstractImporter(PluginManager::AbstractManager& manager, const std::string& plugin): PluginManager::AbstractManagingPlugin<AbstractImporter>{manager, plugin} {}

void AbstractImporter::setFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*callback)(const std::string&, InputFileCallbackPolicy, void*), void* const userData) {
CORRADE_ASSERT(!isOpened(), "Audio::AbstractImporter::setFileCallback(): can't be set while a file is opened", );
CORRADE_ASSERT(features() & Feature::OpenData, "Audio::AbstractImporter::setFileCallback(): importer doesn't support loading from data, callbacks can't be used", );

_fileCallback = callback;
_fileCallbackUserData = userData;
doSetFileCallback(callback, userData);
}

void AbstractImporter::doSetFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*)(const std::string&, InputFileCallbackPolicy, void*), void*) {}

bool AbstractImporter::openData(Containers::ArrayView<const char> data) {
CORRADE_ASSERT(features() & Feature::OpenData,
"Audio::AbstractImporter::openData(): feature not supported", {});

/* We accept empty data here (instead of checking for them and failing so
the check doesn't be done on the plugin side) because for some file
formats it could be valid. */
close();
doOpenData(data);
return isOpened();
Expand All @@ -74,20 +91,63 @@ void AbstractImporter::doOpenData(Containers::ArrayView<const char>) {

bool AbstractImporter::openFile(const std::string& filename) {
close();
doOpenFile(filename);

/* If file loading callbacks are not set or the importer supports handling
them directly, call into the implementation */
if(!_fileCallback || (doFeatures() & Feature::FileCallback)) {
doOpenFile(filename);

/* Otherwise, if loading from data is supported, use the callback and pass
the data through to openData(). Mark the file as ready to be closed once
opening is finished. */
} else if(doFeatures() & Feature::OpenData) {
/* This needs to be duplicated here and in the doOpenFile()
implementation in order to support both following cases:
- plugins that don't support FileCallback but have their own
doOpenFile() implementation (callback needs to be used here,
because the base doOpenFile() implementation might never get
called)
- plugins that support FileCallback but want to delegate the actual
file loading to the default implementation (callback used in the
base doOpenFile() implementation, because this branch is never
taken in that case) */
const Containers::Optional<Containers::ArrayView<const char>> data = _fileCallback(filename, InputFileCallbackPolicy::LoadTemporary, _fileCallbackUserData);
if(!data) {
Error() << "Audio::AbstractImporter::openFile(): cannot open file" << filename;
return isOpened();
}
doOpenData(*data);
_fileCallback(filename, InputFileCallbackPolicy::Close, _fileCallbackUserData);

/* Shouldn't get here, the assert is fired already in setFileCallback() */
} else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */

return isOpened();
}

void AbstractImporter::doOpenFile(const std::string& filename) {
CORRADE_ASSERT(features() & Feature::OpenData, "Audio::AbstractImporter::openFile(): not implemented", );

/* Open file */
if(!Utility::Directory::exists(filename)) {
Error() << "Trade::AbstractImporter::openFile(): cannot open file" << filename;
return;
/* If callbacks are set, use them. This is the same implementation as in
openFile(), see the comment there for details. */
if(_fileCallback) {
const Containers::Optional<Containers::ArrayView<const char>> data = _fileCallback(filename, InputFileCallbackPolicy::LoadTemporary, _fileCallbackUserData);
if(!data) {
Error() << "Audio::AbstractImporter::openFile(): cannot open file" << filename;
return;
}
doOpenData(*data);
_fileCallback(filename, InputFileCallbackPolicy::Close, _fileCallbackUserData);

/* Otherwise open the file directly */
} else {
if(!Utility::Directory::exists(filename)) {
Error() << "Audio::AbstractImporter::openFile(): cannot open file" << filename;
return;
}

doOpenData(Utility::Directory::read(filename));
}

doOpenData(Utility::Directory::read(filename));
}

void AbstractImporter::close() {
Expand Down
191 changes: 179 additions & 12 deletions src/Magnum/Audio/AbstractImporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,51 @@ namespace Magnum { namespace Audio {
/**
@brief Base for audio importer plugins

Provides interface for importing various audio formats. See @ref plugins for
more information and `*Importer` classes in @ref Audio namespace for available
importer plugins.
Provides interface for importing various audio formats.

@section Audio-AbstractImporter-usage Usage

Importers are most commonly implemented as plugins. For example, loading an
audio track from the filesystem using the @ref AnyAudioImporter plugin can be
done like this, completely with all error handling:

@snippet MagnumAudio.cpp AbstractImporter-usage

See @ref plugins for more information about general plugin usage and
`*Importer` classes in the @ref Audio namespace for available importer plugins.

@subsection Audio-AbstractImporter-usage-callbacks Loading data from memory, using file callbacks

Besides loading data directly from the filesystem using @ref openFile() like
shown above, it's possible to use @ref openData() to import data from memory.
Note that the particular importer implementation must support
@ref Feature::OpenData for this method to work.

Some audio formats sometimes reference other files (such as songs of a
playlist) and in that case you may want to intercept those references and load
them in a custom way as well. For importers that advertise support for this
with @ref Feature::FileCallback this is done by specifying a file loading
callback using @ref setFileCallback(). The callback gets a filename,
@ref InputFileCallbackPolicy and an user pointer as parameters; returns a
non-owning view on the loaded data or a
@ref Corrade::Containers::NullOpt "Containers::NullOpt" to indicate the file
loading failed. For example, loading a scene from memory-mapped files could
look like below. Note that the file loading callback affects @ref openFile() as
well --- you don't have to load the top-level file manually and pass it to
@ref openData(), any importer supporting the callback feature handles that
correctly.

@snippet MagnumAudio.cpp AbstractImporter-usage-callbacks

For importers that don't support @ref Feature::FileCallback directly, the base
@ref openFile() implementation will use the file callback to pass the loaded
data through to @ref openData(), in case the importer supports at least
@ref Feature::OpenData. If the importer supports neither @ref Feature::FileCallback
nor @ref Feature::OpenData, @ref setFileCallback() doesn't allow the callbacks
to be set.

The input file callback signature is the same for @ref Audio::AbstractImporter,
@ref Trade::AbstractImporter and @ref Text::AbstractFont to allow code reuse.

@section Audio-AbstractImporter-subclassing Subclassing

Expand Down Expand Up @@ -74,7 +116,18 @@ class MAGNUM_AUDIO_EXPORT AbstractImporter: public PluginManager::AbstractManagi
*/
enum class Feature: UnsignedByte {
/** Opening files from raw data using @ref openData() */
OpenData = 1 << 0
OpenData = 1 << 0,

/**
* Specifying callbacks for loading additional files referenced
* from the main file using @ref setFileCallback(). If the importer
* doesn't expose this feature, the format is either single-file or
* loading via callbacks is not supported.
*
* See @ref Audio-AbstractImporter-usage-callbacks and particular
* importer documentation for more information.
*/
FileCallback = 1 << 1
};

/**
Expand Down Expand Up @@ -123,6 +176,90 @@ class MAGNUM_AUDIO_EXPORT AbstractImporter: public PluginManager::AbstractManagi
/** @brief Features supported by this importer */
Features features() const { return doFeatures(); }

/**
* @brief File opening callback function
*
* @see @ref Audio-AbstractImporter-usage-callbacks
*/
auto fileCallback() const -> Containers::Optional<Containers::ArrayView<const char>>(*)(const std::string&, InputFileCallbackPolicy, void*) { return _fileCallback; }

/**
* @brief File opening callback user data
*
* @see @ref Audio-AbstractImporter-usage-callbacks
*/
void* fileCallbackUserData() const { return _fileCallbackUserData; }

/**
* @brief Set file opening callback
*
* In case the importer supports @ref Feature::FileCallback, files
* opened through @ref openFile() will be loaded through the provided
* callback. Besides that, all external files referenced by the
* top-level file will be loaded through the callback function as well,
* usually on demand. The callback function gets a filename,
* @ref InputFileCallbackPolicy and the @p userData pointer as input
* and returns a non-owning view on the loaded data as output or a
* @ref Corrade::Containers::NullOpt if loading failed --- because
* empty files might also be valid in some circumstances, @cpp nullptr @ce
* can't be used to indicate a failure.
*
* In case the importer doesn't support @ref Feature::FileCallback but
* supports at least @ref Feature::OpenData, a file opened through
* @ref openFile() will be internally loaded through the provided
* callback and then passed to @ref openData(). First the file is
* loaded with @ref InputFileCallbackPolicy::LoadTemporary passed to
* the callback, then the returned memory view is passed to
* @ref openData() (sidestepping the potential @ref openFile()
* implementation of that particular importer) and after that the
* callback is called again with @ref InputFileCallbackPolicy::Close
* because the semantics of @ref openData() don't require the data to
* be alive after. In case you need a different behavior, use
* @ref openData() directly.
*
* In case @p callback is @cpp nullptr @ce, the current callback (if
* any) is reset. This function expects that the importer supports
* either @ref Feature::FileCallback or @ref Feature::OpenData. If an
* importer supports neither, callbacks can't be used.
*
* It's expected that this function is called *before* a file is
* opened. It's also expected that the loaded data are kept in scope
* for as long as the importer needs them, based on the value of
* @ref InputFileCallbackPolicy. Documentation of particular importers
* provides more information about the expected callback behavior.
*
* Following is an example of setting up a file loading callback for
* fetching compiled-in resources from @ref Corrade::Utility::Resource.
* See the overload below for a more convenient type-safe way to pass
* the user data pointer.
*
* @snippet MagnumAudio.cpp AbstractImporter-setFileCallback
*
* @see @ref Audio-AbstractImporter-usage-callbacks
*/
void setFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*callback)(const std::string&, InputFileCallbackPolicy, void*), void* userData = nullptr);

/**
* @brief Set file opening callback
*
* Equivalent to calling the above with a lambda wrapper that casts
* @cpp void* @ce back to @cpp T* @ce and dereferences it in order to
* pass it to @p callback. Example usage:
*
* @snippet MagnumAudio.cpp AbstractImporter-setFileCallback-template
*
* @see @ref Audio-AbstractImporter-usage-callbacks
*/
#ifdef DOXYGEN_GENERATING_OUTPUT
template<class T> void setFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*callback)(const std::string&, InputFileCallbackPolicy, T&), T& userData);
#else
/* Otherwise the user would be forced to use the + operator to convert
a lambda to a function pointer and (besides being weird and
annoying) it's also not portable because it doesn't work on MSVC
2015 and older versions of MSVC 2017. */
template<class Callback, class T> void setFileCallback(Callback callback, T& userData);
#endif

/** @brief Whether any file is opened */
bool isOpened() const { return doIsOpened(); }

Expand Down Expand Up @@ -161,24 +298,44 @@ class MAGNUM_AUDIO_EXPORT AbstractImporter: public PluginManager::AbstractManagi

/*@}*/

protected:
/**
* @brief Implementation for @ref openFile()
*
* If @ref Feature::OpenData is supported, default implementation opens
* the file and calls @ref doOpenData() with its contents. It is
* allowed to call this function from your @ref doOpenFile()
* implementation --- in particular, this implementation will also
* correctly handle callbacks set through @ref setFileCallback().
*
* This function is not called when file callbacks are set through
* @ref setFileCallback() and @ref Feature::FileCallback is not
* supported --- instead, file is loaded though the callback and data
* passed through to @ref doOpenData().
*/
virtual void doOpenFile(const std::string& filename);

private:
/** @brief Implementation for @ref features() */
virtual Features doFeatures() const = 0;

/**
* @brief Implementation for @ref setFileCallback()
*
* Useful when the importer needs to modify some internal state on
* callback setup. Default implementation does nothing and this
* function doesn't need to be implemented --- the callback function
* and user data pointer are available through @ref fileCallback() and
* @ref fileCallbackUserData().
*/
virtual void doSetFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*callback)(const std::string&, InputFileCallbackPolicy, void*), void* userData);

/** @brief Implementation for @ref isOpened() */
virtual bool doIsOpened() const = 0;

/** @brief Implementation for @ref openData() */
virtual void doOpenData(Containers::ArrayView<const char> data);

/**
* @brief Implementation for @ref openFile()
*
* If @ref Feature::OpenData is supported, default implementation opens
* the file and calls @ref doOpenData() with its contents.
*/
virtual void doOpenFile(const std::string& filename);

/** @brief Implementation for @ref close() */
virtual void doClose() = 0;

Expand All @@ -190,6 +347,16 @@ class MAGNUM_AUDIO_EXPORT AbstractImporter: public PluginManager::AbstractManagi

/** @brief Implementation for @ref data() */
virtual Containers::Array<char> doData() = 0;

Containers::Optional<Containers::ArrayView<const char>>(*_fileCallback)(const std::string&, InputFileCallbackPolicy, void*){};
void* _fileCallbackUserData{};

/* Used by the templated version only */
struct FileCallbackTemplate {
void(*callback)();
const void* userData;
/* GCC 4.8 complains loudly about missing initializers otherwise */
} _fileCallbackTemplate{nullptr, nullptr};
};

}}
Expand Down
5 changes: 3 additions & 2 deletions src/Magnum/Text/AbstractFont.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ data through to @ref openData(), in case the importer supports at least
nor @ref Feature::OpenData, @ref setFileCallback() doesn't allow the callbacks
to be set.

The input file callback signature is the same for @ref Text::AbstractFont and
@ref Trade::AbstractImporter to allow code reuse.
The input file callback signature is the same for @ref Text::AbstractFont,
@ref Trade::AbstractImporter and @ref Audio::AbstractImporter to allow code
reuse.

@section Text-AbstractFont-subclassing Subclassing

Expand Down
4 changes: 2 additions & 2 deletions src/Magnum/Trade/AbstractImporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ data through to @ref openData(), in case the importer supports at least
nor @ref Feature::OpenData, @ref setFileCallback() doesn't allow the callbacks
to be set.

The input file callback signature is the same for @ref Trade::AbstractImporter
and @ref Text::AbstractFont to allow code reuse.
The input file callback signature is the same for @ref Trade::AbstractImporter,
@ref Audio::AbstractImporter and @ref Text::AbstractFont to allow code reuse.

@subsection Trade-AbstractImporter-usage-state Internal importer state

Expand Down