in pedalboard/io/ReadableAudioFile.h [786:1070]
inline void init_readable_audio_file(
py::module &m,
py::class_<ReadableAudioFile, AudioFile, std::shared_ptr<ReadableAudioFile>>
&pyReadableAudioFile) {
pyReadableAudioFile
.def(py::init([](std::string filename) -> ReadableAudioFile * {
// This definition is only here to provide nice docstrings.
throw std::runtime_error(
"Internal error: __init__ should never be called, as this "
"class implements __new__.");
}),
py::arg("filename"))
.def(py::init([](py::object filelike) -> ReadableAudioFile * {
// This definition is only here to provide nice docstrings.
throw std::runtime_error(
"Internal error: __init__ should never be called, as this "
"class implements __new__.");
}),
py::arg("file_like"))
.def_static(
"__new__",
[](const py::object *, std::string filename) {
return std::make_shared<ReadableAudioFile>(filename);
},
py::arg("cls"), py::arg("filename"))
.def_static(
"__new__",
[](const py::object *, py::object filelike) {
if (!isReadableFileLike(filelike) &&
!tryConvertingToBuffer(filelike)) {
throw py::type_error(
"Expected either a filename, a file-like object (with "
"read, seek, seekable, and tell methods) or a memoryview, "
"but received: " +
py::repr(filelike).cast<std::string>());
}
if (std::optional<py::buffer> buf =
tryConvertingToBuffer(filelike)) {
return std::make_shared<ReadableAudioFile>(
std::make_unique<PythonMemoryViewInputStream>(*buf,
filelike));
} else {
return std::make_shared<ReadableAudioFile>(
std::make_unique<PythonInputStream>(filelike));
}
},
py::arg("cls"), py::arg("file_like"))
.def("read", &ReadableAudioFile::read, py::arg("num_frames") = 0, R"(
Read the given number of frames (samples in each channel) from this audio file at its current position.
``num_frames`` is a required argument, as audio files can be deceptively large. (Consider that
an hour-long ``.ogg`` file may be only a handful of megabytes on disk, but may decompress to
nearly a gigabyte in memory.) Audio files should be read in chunks, rather than all at once, to avoid
hard-to-debug memory problems and out-of-memory crashes.
Audio samples are returned as a multi-dimensional :class:`numpy.array` with the shape
``(channels, samples)``; i.e.: a stereo audio file will have shape ``(2, <length>)``.
Returned data is always in the ``float32`` datatype.
If the file does not contain enough audio data to fill ``num_frames``, the returned
:class:`numpy.array` will contain as many frames as could be read from the file. (In some cases,
passing :py:attr:`frames` as ``num_frames`` may still return less data than expected. See documentation
for :py:attr:`frames` and :py:attr:`exact_duration_known` for more information about situations
in which this may occur.)
For most (but not all) audio files, the minimum possible sample value will be ``-1.0f`` and the
maximum sample value will be ``+1.0f``.
.. note::
For convenience, the ``num_frames`` argument may be a floating-point number. However, if the
provided number of frames contains a fractional part (i.e.: ``1.01`` instead of ``1.00``) then
an exception will be thrown, as a fractional number of samples cannot be returned.
)")
.def("read_raw", &ReadableAudioFile::readRaw, py::arg("num_frames") = 0,
R"(
Read the given number of frames (samples in each channel) from this audio file at its current position.
``num_frames`` is a required argument, as audio files can be deceptively large. (Consider that
an hour-long ``.ogg`` file may be only a handful of megabytes on disk, but may decompress to
nearly a gigabyte in memory.) Audio files should be read in chunks, rather than all at once, to avoid
hard-to-debug memory problems and out-of-memory crashes.
Audio samples are returned as a multi-dimensional :class:`numpy.array` with the shape
``(channels, samples)``; i.e.: a stereo audio file will have shape ``(2, <length>)``.
Returned data is in the raw format stored by the underlying file (one of ``int8``, ``int16``,
``int32``, or ``float32``) and may have any magnitude.
If the file does not contain enough audio data to fill ``num_frames``, the returned
:class:`numpy.array` will contain as many frames as could be read from the file. (In some cases,
passing :py:attr:`frames` as ``num_frames`` may still return less data than expected. See documentation
for :py:attr:`frames` and :py:attr:`exact_duration_known` for more information about situations
in which this may occur.)
.. note::
For convenience, the ``num_frames`` argument may be a floating-point number. However, if the
provided number of frames contains a fractional part (i.e.: ``1.01`` instead of ``1.00``) then
an exception will be thrown, as a fractional number of samples cannot be returned.
)")
.def("seekable", &ReadableAudioFile::isSeekable,
"Returns True if this file is currently open and calls to seek() "
"will work.")
.def("seek", &ReadableAudioFile::seek, py::arg("position"),
"Seek this file to the provided location in frames. Future reads "
"will start from this position.")
.def("tell", &ReadableAudioFile::tell,
"Return the current position of the read pointer in this audio "
"file, in frames. This value will increase as :meth:`read` is "
"called, and may decrease if :meth:`seek` is called.")
.def("close", &ReadableAudioFile::close,
"Close this file, rendering this object unusable.")
.def("__enter__", &ReadableAudioFile::enter,
"Use this :class:`ReadableAudioFile` as a context manager, "
"automatically closing the file and releasing resources when the "
"context manager exits.")
.def("__exit__", &ReadableAudioFile::exit,
"Stop using this :class:`ReadableAudioFile` as a context manager, "
"close the file, release its resources.")
.def("__repr__",
[](const ReadableAudioFile &file) {
std::ostringstream ss;
ss << "<pedalboard.io.ReadableAudioFile";
if (file.getFilename() && !file.getFilename()->empty()) {
ss << " filename=\"" << *file.getFilename() << "\"";
} else if (PythonInputStream *stream =
file.getPythonInputStream()) {
ss << " file_like=" << stream->getRepresentation();
}
ss << " samplerate=" << file.getSampleRateAsDouble();
ss << " num_channels=" << file.getNumChannels();
ss << " frames=" << file.getLengthInSamples();
ss << " file_dtype=" << file.getFileDatatype();
if (file.isClosed()) {
ss << " closed";
}
ss << " at " << &file;
ss << ">";
return ss.str();
})
.def_property_readonly(
"name", &ReadableAudioFile::getFilename,
"The name of this file.\n\nIf this :class:`ReadableAudioFile` was "
"opened from a file-like object, this will be ``None``.")
.def_property_readonly("closed", &ReadableAudioFile::isClosed,
"True iff this file is closed (and no longer "
"usable), False otherwise.")
.def_property_readonly(
"samplerate", &ReadableAudioFile::getSampleRate,
"The sample rate of this file in samples (per channel) per second "
"(Hz). Sample rates are represented as floating-point numbers by "
"default, but this property will be an integer if the file's sample "
"rate has no fractional part.")
.def_property_readonly("num_channels", &ReadableAudioFile::getNumChannels,
"The number of channels in this file.")
.def_property_readonly("exact_duration_known",
&ReadableAudioFile::exactDurationKnown,
R"(
Returns :py:const:`True` if this file's :py:attr:`frames` and
:py:attr:`duration` attributes are exact values, or :py:const:`False` if the
:py:attr:`frames` and :py:attr:`duration` attributes are estimates based
on the file's size and bitrate.
If :py:attr:`exact_duration_known` is :py:const:`False`, this value will
change to :py:const:`True` once the file is read to completion. Once
:py:const:`True`, this value will not change back to :py:const:`False`
for the same :py:class:`AudioFile` object (even after calls to :meth:`seek`).
.. note::
:py:attr:`exact_duration_known` will only ever be :py:const:`False`
when reading certain MP3 files. For files in other formats than MP3,
:py:attr:`exact_duration_known` will always be equal to :py:const:`True`.
*Introduced in v0.7.2.*
)")
.def_property_readonly("frames", &ReadableAudioFile::getLengthInSamples,
R"(
The total number of frames (samples per channel) in this file.
For example, if this file contains 10 seconds of stereo audio at sample rate
of 44,100 Hz, ``frames`` will return ``441,000``.
.. warning::
When reading certain MP3 files that have been encoded in constant bitrate mode,
the :py:attr:`frames` and :py:attr:`duration` properties may initially be estimates
and **may change as the file is read**. The :py:attr:`exact_duration_known`
property indicates if the values of :py:attr:`frames` and :py:attr:`duration`
are estimates or exact values.
This discrepancy is due to the fact that MP3 files are not required to have
headers that indicate the duration of the file. If an MP3 file is opened and a
``Xing`` or ``Info`` header frame is not found, the initial value of the
:py:attr:`frames` and :py:attr:`duration` attributes are estimates based on the file's
bitrate and size. This may result in an overestimate of the file's duration
if there is additional data present in the file after the audio stream is finished.
If the exact number of frames in the file is required, read the entire file
first before accessing the :py:attr:`frames` or :py:attr:`duration` properties.
This operation forces each frame to be parsed and guarantees that
:py:attr:`frames` and :py:attr:`duration` are correct, at the expense of
scanning the entire file::
with AudioFile("my_file.mp3") as f:
while f.tell() < f.frames:
f.read(f.samplerate * 60)
# f.frames is now guaranteed to be exact, as the entire file has been read:
assert f.exact_duration_known == True
f.seek(0)
num_channels, num_samples = f.read(f.frames).shape
assert num_samples == f.frames
This behaviour is present in v0.7.2 and later; prior versions would
raise an exception when trying to read the ends of MP3 files that contained
trailing non-audio data and lacked ``Xing`` or ``Info`` headers.
)")
.def_property_readonly("duration", &ReadableAudioFile::getDuration,
R"(
The duration of this file in seconds (``frames`` divided by ``samplerate``).
.. warning::
:py:attr:`duration` may be an overestimate for certain MP3 files.
Use :py:attr:`exact_duration_known` property to determine if
:py:attr:`duration` is accurate. (See the documentation for the
:py:attr:`frames` attribute for more details.)
)")
.def_property_readonly(
"file_dtype", &ReadableAudioFile::getFileDatatype,
"The data type (``\"int16\"``, ``\"float32\"``, etc) stored "
"natively by this file.\n\nNote that :meth:`read` will always "
"return a ``float32`` array, regardless of the value of this "
"property. Use :meth:`read_raw` to read data from the file in its "
"``file_dtype``.")
.def(
"resampled_to",
[](std::shared_ptr<ReadableAudioFile> file, double targetSampleRate,
ResamplingQuality quality)
-> std::variant<std::shared_ptr<ReadableAudioFile>,
std::shared_ptr<ResampledReadableAudioFile>> {
if (file->getSampleRateAsDouble() == targetSampleRate)
return {file};
return {std::make_shared<ResampledReadableAudioFile>(
file, targetSampleRate, quality)};
},
py::arg("target_sample_rate"),
py::arg("quality") = ResamplingQuality::WindowedSinc32,
"Return a :class:`ResampledReadableAudioFile` that will "
"automatically resample this :class:`ReadableAudioFile` to the "
"provided `target_sample_rate`, using a constant amount of "
"memory.\n\nIf `target_sample_rate` matches the existing sample rate "
"of the file, the original file will be returned.\n\n*Introduced in "
"v0.6.0.*");
m.def("get_supported_read_formats", []() {
juce::AudioFormatManager manager;
registerPedalboardAudioFormats(manager, false);
std::vector<std::string> formatNames(manager.getNumKnownFormats());
juce::StringArray extensions;
for (int i = 0; i < manager.getNumKnownFormats(); i++) {
auto *format = manager.getKnownFormat(i);
extensions.addArray(format->getFileExtensions());
}
extensions.trim();
extensions.removeEmptyStrings();
extensions.removeDuplicates(true);
std::vector<std::string> output;
for (juce::String s : extensions) {
output.push_back(s.toStdString());
}
std::sort(
output.begin(), output.end(),
[](const std::string lhs, const std::string rhs) { return lhs < rhs; });
return output;
});
}