in pedalboard/io/WriteableAudioFile.h [477:617]
void write(py::array_t<SampleType, py::array::c_style> inputArray) {
const juce::ScopedReadLock scopedReadLock(objectLock);
if (!writer)
throw std::runtime_error("I/O operation on a closed file.");
py::buffer_info inputInfo = inputArray.request();
unsigned int numChannels = 0;
unsigned int numSamples = 0;
if (lastChannelLayout) {
try {
lastChannelLayout = detectChannelLayout(inputArray, {getNumChannels()});
} catch (...) {
// Use the last cached layout.
}
} else {
// We have no cached layout; detect it now and raise if necessary:
try {
lastChannelLayout = detectChannelLayout(inputArray, {getNumChannels()});
} catch (const std::exception &e) {
throw std::runtime_error(
std::string(e.what()) +
" Provide a non-square array first to allow Pedalboard to "
"determine which dimension corresponds with the number of channels "
"and which dimension corresponds with the number of samples.");
}
}
// Release the GIL when we do the writing, after we
// already have a reference to the input array:
pybind11::gil_scoped_release release;
if (inputInfo.ndim == 1) {
numSamples = inputInfo.shape[0];
numChannels = 1;
} else if (inputInfo.ndim == 2) {
switch (*lastChannelLayout) {
case ChannelLayout::Interleaved:
numSamples = inputInfo.shape[0];
numChannels = inputInfo.shape[1];
break;
case ChannelLayout::NotInterleaved:
numSamples = inputInfo.shape[1];
numChannels = inputInfo.shape[0];
break;
}
} else {
throw std::runtime_error(
"Number of input dimensions must be 1 or 2 (got " +
std::to_string(inputInfo.ndim) + ").");
}
if (numChannels == 0) {
// No work to do.
return;
} else if (numChannels != getNumChannels()) {
throw std::runtime_error(
"WriteableAudioFile was opened with num_channels=" +
std::to_string(getNumChannels()) +
", but was passed an array containing " +
std::to_string(numChannels) + "-channel audio!");
}
// Depending on the input channel layout, we need to copy data
// differently. This loop is duplicated here to move the if statement
// outside of the tight loop, as we don't need to re-check that the input
// channel is still the same on every iteration of the loop.
switch (*lastChannelLayout) {
case ChannelLayout::Interleaved: {
std::vector<std::vector<SampleType>> deinterleaveBuffers;
// Use a temporary buffer to chunk the audio input
// and pass it into the writer, chunk by chunk, rather
// than de-interleaving the entire buffer at once:
deinterleaveBuffers.resize(numChannels);
const SampleType **channelPointers =
(const SampleType **)alloca(numChannels * sizeof(SampleType *));
for (int startSample = 0; startSample < numSamples;
startSample += DEFAULT_AUDIO_BUFFER_SIZE_FRAMES) {
int samplesToWrite = std::min(numSamples - startSample,
DEFAULT_AUDIO_BUFFER_SIZE_FRAMES);
for (int c = 0; c < numChannels; c++) {
deinterleaveBuffers[c].resize(samplesToWrite);
channelPointers[c] = deinterleaveBuffers[c].data();
// We're de-interleaving the data here, so we can't use copyFrom.
for (unsigned int i = 0; i < samplesToWrite; i++) {
deinterleaveBuffers[c][i] =
((SampleType
*)(inputInfo.ptr))[((i + startSample) * numChannels) + c];
}
}
bool writeSuccessful =
write(channelPointers, numChannels, samplesToWrite);
if (!writeSuccessful) {
PythonException::raise();
throw std::runtime_error("Unable to write data to audio file.");
}
}
break;
}
case ChannelLayout::NotInterleaved: {
// We can just pass all the data to write:
const SampleType **channelPointers =
(const SampleType **)alloca(numChannels * sizeof(SampleType *));
for (int c = 0; c < numChannels; c++) {
channelPointers[c] = ((SampleType *)inputInfo.ptr) + (numSamples * c);
}
bool writeSuccessful = write(channelPointers, numChannels, numSamples);
if (!writeSuccessful) {
PythonException::raise();
throw std::runtime_error("Unable to write data to audio file.");
}
break;
}
default:
throw std::runtime_error(
"Internal error: got unexpected channel layout.");
}
{
ScopedTryWriteLock scopedTryWriteLock(objectLock);
if (!scopedTryWriteLock.isLocked()) {
throw std::runtime_error(
"Another thread is currently writing to this AudioFile. Note "
"that using multiple concurrent writers on the same AudioFile "
"object will produce nondeterministic results.");
}
framesWritten += numSamples;
}
}