Added an AudioSpan and AudioBuffer, to better handle the memory for mono/stereo samples

This commit is contained in:
paulfd 2019-09-07 01:12:20 +02:00
parent b1ef2c26bd
commit c1d2384e09
13 changed files with 204 additions and 139 deletions

View file

@ -21,7 +21,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "StereoSpan.h" #include "AudioSpan.h"
#include "Synth.h" #include "Synth.h"
#include <absl/flags/parse.h> #include <absl/flags/parse.h>
#include <absl/types/span.h> #include <absl/types/span.h>
@ -116,13 +116,9 @@ int process(jack_nframes_t numFrames, void* arg [[maybe_unused]])
} }
} }
StereoSpan<float> output { auto leftOutput = reinterpret_cast<float*>(jack_port_get_buffer(outputPort1, numFrames));
jack_port_get_buffer(outputPort1, numFrames), auto rightOutput = reinterpret_cast<float*>(jack_port_get_buffer(outputPort2, numFrames));
jack_port_get_buffer(outputPort2, numFrames), synth->renderBlock({ { leftOutput, rightOutput }, numFrames });
numFrames
};
synth->renderBlock(output);
return 0; return 0;
} }

View file

@ -50,6 +50,14 @@ public:
buffers[i] = std::make_unique<buffer_type>(numFrames); buffers[i] = std::make_unique<buffer_type>(numFrames);
} }
bool resize(size_type newSize)
{
bool returnedOK = true;
for (auto i = 0; i < numChannels; ++i)
returnedOK &= buffers[i]->resize(newSize);
return returnedOK;
}
iterator channelWriter(int channelIndex) iterator channelWriter(int channelIndex)
{ {
ASSERT(channelIndex < numChannels) ASSERT(channelIndex < numChannels)
@ -68,7 +76,7 @@ public:
return {}; return {};
} }
const_iterator channelReader(int channelIndex) const_iterator channelReader(int channelIndex) const
{ {
ASSERT(channelIndex < numChannels) ASSERT(channelIndex < numChannels)
if (channelIndex < numChannels) if (channelIndex < numChannels)
@ -77,7 +85,7 @@ public:
return {}; return {};
} }
const_iterator channelReaderEnd(int channelIndex) const_iterator channelReaderEnd(int channelIndex) const
{ {
ASSERT(channelIndex < numChannels) ASSERT(channelIndex < numChannels)
if (channelIndex < numChannels) if (channelIndex < numChannels)
@ -86,7 +94,7 @@ public:
return {}; return {};
} }
absl::Span<value_type> getSpan(int channelIndex) absl::Span<value_type> getSpan(int channelIndex) const
{ {
ASSERT(channelIndex < numChannels) ASSERT(channelIndex < numChannels)
if (channelIndex < numChannels) if (channelIndex < numChannels)
@ -95,45 +103,45 @@ public:
return {}; return {};
} }
absl::Span<const value_type> getConstSpan(int channelIndex) absl::Span<const value_type> getConstSpan(int channelIndex) const
{ {
return getSpan(channelIndex); return getSpan(channelIndex);
} }
void addChannel() void addChannel()
{ {
if (numChannels < MaxChannels) if (numChannels < MaxChannels)
buffers[numChannels++] = std::make_unique<buffer_type>(numFrames); buffers[numChannels++] = std::make_unique<buffer_type>(numFrames);
} }
size_type getNumFrames() size_type getNumFrames() const
{ {
return numFrames; return numFrames;
} }
size_type getNumChannels() int getNumChannels() const
{ {
return numChannels; return numChannels;
} }
bool empty() bool empty() const
{ {
return numFrames == 0; return numFrames == 0;
} }
Type& getSample(int channelIndex, size_type frameIndex) Type& getSample(int channelIndex, size_type frameIndex)
{ {
// Uhoh // Uhoh
ASSERT(buffers[channelIndex] != nullptr); ASSERT(buffers[channelIndex] != nullptr);
ASSERT(frameIndex < numFrames); ASSERT(frameIndex < numFrames);
return *(buffers[channelIndex]->data() + frameIndex); return *(buffers[channelIndex]->data() + frameIndex);
} }
Type& operator()(int channelIndex, size_type frameIndex) Type& operator()(int channelIndex, size_type frameIndex)
{ {
return getSample(channelIndex, frameIndex); return getSample(channelIndex, frameIndex);
} }
private: private:
using buffer_type = Buffer<Type, Alignment>; using buffer_type = Buffer<Type, Alignment>;

View file

@ -39,37 +39,48 @@ public:
{ {
} }
AudioSpan(const std::array<Type*, MaxChannels>& spans, int numChannels, size_type offset, size_type size)
: numFrames(size)
, numChannels(numChannels)
{
ASSERT(static_cast<unsigned int>(numChannels) <= MaxChannels);
for (auto i = 0; i < numChannels; ++i)
this->spans[i] = spans[i] + offset;
}
AudioSpan(std::initializer_list<Type*> spans, size_type numFrames) AudioSpan(std::initializer_list<Type*> spans, size_type numFrames)
: numFrames(numFrames) : numFrames(numFrames)
, numChannels(spans.size()) , numChannels(spans.size())
{ {
static_assert(spans.size() <= MaxChannels); ASSERT(spans.size() <= MaxChannels);
for (auto i = 0; i < spans.size(); i++) { auto newSpan = spans.begin();
auto thisSpan = this->spans.begin();
for (; newSpan < spans.end() && thisSpan < this->spans.end(); thisSpan++, newSpan++) {
// This will not end well... // This will not end well...
ASSERT(spans[i] != nullptr); ASSERT(*newSpan != nullptr);
this->spans[i] = spans[i]; *thisSpan = *newSpan;
} }
} }
AudioSpan(std::initializer_list<absl::Span<Type>> spans) AudioSpan(std::initializer_list<absl::Span<Type>> spans)
: numChannels(spans.size()) : numChannels(spans.size())
{ {
static_assert(spans.size() <= MaxChannels); ASSERT(spans.size() <= MaxChannels);
auto size = absl::Span<Type>::npos; auto size = absl::Span<Type>::npos;
for (auto i = 0; i < spans.size(); i++) { auto newSpan = spans.begin();
// This will not end well... auto thisSpan = this->spans.begin();
ASSERT(spans[i] != nullptr); for (; newSpan < spans.end() && thisSpan < this->spans.end(); thisSpan++, newSpan++) {
this->spans[i] = spans[i].data(); *thisSpan = newSpan->data();
size = std::min(size, spans[i].size()); size = std::min(size, newSpan->size());
} }
} }
template <class U, unsigned int N, unsigned int Alignment, typename = std::enable_if<N <= MaxChannels>> template <class U, unsigned int N, unsigned int Alignment, typename = std::enable_if<N <= MaxChannels>>
AudioSpan(const AudioBuffer<U, N, Alignment>& audioBuffer) AudioSpan(AudioBuffer<U, N, Alignment>& audioBuffer)
: numFrames(audioBuffer.getNumFrames()) : numFrames(audioBuffer.getNumFrames())
, numChannels(audioBuffer.getNumChannels()) , numChannels(audioBuffer.getNumChannels())
{ {
for (auto i = 0; i < N; i++) { for (int i = 0; i < numChannels; i++) {
if constexpr (std::is_const<Type>::value) if constexpr (std::is_const<Type>::value)
this->spans[i] = audioBuffer.channelReader(i); this->spans[i] = audioBuffer.channelReader(i);
else else
@ -82,7 +93,7 @@ public:
: numFrames(other.getNumFrames()) : numFrames(other.getNumFrames())
, numChannels(other.getNumChannels()) , numChannels(other.getNumChannels())
{ {
for (auto i = 0; i < N; i++) { for (int i = 0; i < numChannels; i++) {
this->spans[i] = other.getChannel(i); this->spans[i] = other.getChannel(i);
} }
} }
@ -105,7 +116,7 @@ public:
return {}; return {};
} }
absl::Span<const std::remove_cv<Type>> getConstSpan(int channelIndex) absl::Span<const Type> getConstSpan(int channelIndex)
{ {
ASSERT(channelIndex < numChannels); ASSERT(channelIndex < numChannels);
if (channelIndex < numChannels) if (channelIndex < numChannels)
@ -117,23 +128,23 @@ public:
void fill(Type value) noexcept void fill(Type value) noexcept
{ {
for (int i = 0; i < numChannels; ++i) for (int i = 0; i < numChannels; ++i)
::fill<Type>({ getChannel(i), numFrames }, value); ::fill<Type>(getSpan(i), value);
} }
void applyGain(absl::Span<const Type> gain) noexcept void applyGain(absl::Span<const Type> gain) noexcept
{ {
for (int i = 0; i < numChannels; ++i) for (int i = 0; i < numChannels; ++i)
::applyGain<Type>({ getChannel(i), numFrames }, gain); ::applyGain<Type>(gain, getSpan(i));
} }
void applyGain(Type gain) noexcept void applyGain(Type gain) noexcept
{ {
for (int i = 0; i < numChannels; ++i) for (int i = 0; i < numChannels; ++i)
::applyGain<Type>({ getChannel(i), numFrames }, gain); ::applyGain<Type>(gain, getSpan(i));
} }
template <class U, unsigned int N, typename = std::enable_if<N <= MaxChannels>> template <class U, unsigned int N, typename = std::enable_if<N <= MaxChannels>>
void add(const AudioSpan<U, N>& other) void add(AudioSpan<U, N>& other)
{ {
ASSERT(other.getNumChannels() == numChannels); ASSERT(other.getNumChannels() == numChannels);
if (other.getNumChannels() == numChannels) { if (other.getNumChannels() == numChannels) {
@ -143,7 +154,7 @@ public:
} }
template <class U, unsigned int N, typename = std::enable_if<N <= MaxChannels>> template <class U, unsigned int N, typename = std::enable_if<N <= MaxChannels>>
void copy(const AudioSpan<U, N>& other) void copy(AudioSpan<U, N>& other)
{ {
ASSERT(other.getNumChannels() == numChannels); ASSERT(other.getNumChannels() == numChannels);
if (other.getNumChannels() == numChannels) { if (other.getNumChannels() == numChannels) {
@ -162,6 +173,24 @@ public:
return numChannels; return numChannels;
} }
AudioSpan<Type> first(size_type length)
{
ASSERT(length <= numFrames);
return { spans, numChannels, 0, length };
}
AudioSpan<Type> last(size_type length)
{
ASSERT(length <= numFrames);
return { spans, numChannels, numFrames - length, length };
}
AudioSpan<Type> subspan(size_type offset, size_type length)
{
ASSERT(length + offset <= numFrames);
return { spans, numChannels, offset, length };
}
private: private:
std::array<Type*, MaxChannels> spans; std::array<Type*, MaxChannels> spans;
size_type numFrames { 0 }; size_type numFrames { 0 };

View file

@ -28,7 +28,7 @@ namespace sfz {
namespace config { namespace config {
constexpr float defaultSampleRate { 48000 }; constexpr float defaultSampleRate { 48000 };
constexpr int defaultSamplesPerBlock { 1024 }; constexpr int defaultSamplesPerBlock { 1024 };
constexpr int preloadSize { 8192 * 2 }; constexpr int preloadSize { 8192 };
constexpr int numChannels { 2 }; constexpr int numChannels { 2 };
constexpr int numVoices { 64 }; constexpr int numVoices { 64 };
constexpr int numLoadingThreads { 4 }; constexpr int numLoadingThreads { 4 };

View file

@ -22,26 +22,27 @@
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "FilePool.h" #include "FilePool.h"
#include "AudioBuffer.h"
#include "Config.h" #include "Config.h"
#include "Debug.h" #include "Debug.h"
#include "absl/types/span.h" #include "absl/types/span.h"
#include <chrono> #include <chrono>
#include <memory>
#include <sndfile.hh> #include <sndfile.hh>
using namespace std::chrono_literals; using namespace std::chrono_literals;
template <class T> template <class T>
void readFromFile(SndfileHandle& sndFile, int numFrames, StereoBuffer<T>& output) std::unique_ptr<AudioBuffer<T>> readFromFile(SndfileHandle& sndFile, int numFrames)
{ {
auto returnedBuffer = std::make_unique<AudioBuffer<T>>(sndFile.channels(), numFrames);
if (sndFile.channels() == 1) { if (sndFile.channels() == 1) {
auto tempReadBuffer = std::make_unique<Buffer<float>>(numFrames); sndFile.readf(returnedBuffer->channelWriter(0), numFrames);
sndFile.readf(tempReadBuffer->data(), numFrames);
std::copy(tempReadBuffer->begin(), tempReadBuffer->end(), output.begin(Channel::left));
std::copy(tempReadBuffer->begin(), tempReadBuffer->end(), output.begin(Channel::right));
} else if (sndFile.channels() == 2) { } else if (sndFile.channels() == 2) {
auto tempReadBuffer = std::make_unique<Buffer<float>>(2 * numFrames); auto tempReadBuffer = std::make_unique<AudioBuffer<float>>(1, 2 * numFrames);
sndFile.readf(tempReadBuffer->data(), numFrames); sndFile.readf(tempReadBuffer->channelWriter(0), numFrames);
output.readInterleaved(*tempReadBuffer); ::readInterleaved<float>(tempReadBuffer->getSpan(0), returnedBuffer->getSpan(0), returnedBuffer->getSpan(1));
} }
return returnedBuffer;
} }
std::optional<sfz::FilePool::FileInformation> sfz::FilePool::getFileInformation(std::string_view filename) noexcept std::optional<sfz::FilePool::FileInformation> sfz::FilePool::getFileInformation(std::string_view filename) noexcept
@ -52,12 +53,11 @@ std::optional<sfz::FilePool::FileInformation> sfz::FilePool::getFileInformation(
SndfileHandle sndFile(reinterpret_cast<const char*>(file.c_str())); SndfileHandle sndFile(reinterpret_cast<const char*>(file.c_str()));
if (sndFile.channels() != 1 && sndFile.channels() != 2) { if (sndFile.channels() != 1 && sndFile.channels() != 2) {
DBG("Missing logic for " << sndFile.channels() << ", discarding sample " << filename); DBG("Missing logic for " << sndFile.channels() << " channels, discarding sample " << filename);
return {}; return {};
} }
FileInformation returnedValue; FileInformation returnedValue;
returnedValue.numChannels = sndFile.channels();
returnedValue.end = static_cast<uint32_t>(sndFile.frames()); returnedValue.end = static_cast<uint32_t>(sndFile.frames());
returnedValue.sampleRate = static_cast<double>(sndFile.samplerate()); returnedValue.sampleRate = static_cast<double>(sndFile.samplerate());
@ -78,8 +78,7 @@ std::optional<sfz::FilePool::FileInformation> sfz::FilePool::getFileInformation(
if (preloadedData.contains(filename)) { if (preloadedData.contains(filename)) {
returnedValue.preloadedData = preloadedData[filename]; returnedValue.preloadedData = preloadedData[filename];
} else { } else {
returnedValue.preloadedData = std::make_shared<StereoBuffer<float>>(preloadedSize); returnedValue.preloadedData = std::shared_ptr<AudioBuffer<float>>(readFromFile<float>(sndFile, preloadedSize));
readFromFile(sndFile, preloadedSize, *returnedValue.preloadedData);
preloadedData[filename] = returnedValue.preloadedData; preloadedData[filename] = returnedValue.preloadedData;
} }
@ -97,7 +96,7 @@ void sfz::FilePool::loadingThread() noexcept
{ {
FileLoadingInformation fileToLoad {}; FileLoadingInformation fileToLoad {};
while (!quitThread) { while (!quitThread) {
if (!loadingQueue.wait_dequeue_timed(fileToLoad, 1ms)) if (!loadingQueue.wait_dequeue_timed(fileToLoad, 100ms))
continue; continue;
if (fileToLoad.voice == nullptr) { if (fileToLoad.voice == nullptr) {
@ -113,8 +112,7 @@ void sfz::FilePool::loadingThread() noexcept
} }
SndfileHandle sndFile(reinterpret_cast<const char*>(file.c_str())); SndfileHandle sndFile(reinterpret_cast<const char*>(file.c_str()));
auto fileLoaded = std::make_unique<StereoBuffer<float>>(fileToLoad.numFrames); auto fileLoaded = std::make_unique<AudioBuffer<float>>(sndFile.channels(), fileToLoad.numFrames);
readFromFile(sndFile, fileToLoad.numFrames, *fileLoaded); fileToLoad.voice->setFileData(readFromFile<float>(sndFile, fileToLoad.numFrames));
fileToLoad.voice->setFileData(std::move(fileLoaded));
} }
} }

View file

@ -24,7 +24,7 @@
#pragma once #pragma once
#include "Defaults.h" #include "Defaults.h"
#include "LeakDetector.h" #include "LeakDetector.h"
#include "StereoBuffer.h" #include "AudioBuffer.h"
#include "Voice.h" #include "Voice.h"
#include "readerwriterqueue.h" #include "readerwriterqueue.h"
#include <absl/container/flat_hash_map.h> #include <absl/container/flat_hash_map.h>
@ -50,12 +50,11 @@ public:
size_t getNumPreloadedSamples() const noexcept { return preloadedData.size(); } size_t getNumPreloadedSamples() const noexcept { return preloadedData.size(); }
struct FileInformation { struct FileInformation {
int numChannels { 1 };
uint32_t end { Default::sampleEndRange.getEnd() }; uint32_t end { Default::sampleEndRange.getEnd() };
uint32_t loopBegin { Default::loopRange.getStart() }; uint32_t loopBegin { Default::loopRange.getStart() };
uint32_t loopEnd { Default::loopRange.getEnd() }; uint32_t loopEnd { Default::loopRange.getEnd() };
double sampleRate { config::defaultSampleRate }; double sampleRate { config::defaultSampleRate };
std::shared_ptr<StereoBuffer<float>> preloadedData; std::shared_ptr<AudioBuffer<float>> preloadedData;
}; };
std::optional<FileInformation> getFileInformation(std::string_view filename) noexcept; std::optional<FileInformation> getFileInformation(std::string_view filename) noexcept;
void enqueueLoading(Voice* voice, std::string_view sample, int numFrames) noexcept; void enqueueLoading(Voice* voice, std::string_view sample, int numFrames) noexcept;
@ -71,7 +70,7 @@ private:
void loadingThread() noexcept; void loadingThread() noexcept;
std::thread fileLoadingThread; std::thread fileLoadingThread;
bool quitThread { false }; bool quitThread { false };
absl::flat_hash_map<std::string_view, std::shared_ptr<StereoBuffer<float>>> preloadedData; absl::flat_hash_map<std::string_view, std::shared_ptr<AudioBuffer<float>>> preloadedData;
LEAK_DETECTOR(FilePool); LEAK_DETECTOR(FilePool);
}; };
} }

View file

@ -22,6 +22,7 @@
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "Region.h" #include "Region.h"
#include "MathHelpers.h"
#include "Debug.h" #include "Debug.h"
#include "StringViewHelpers.h" #include "StringViewHelpers.h"
#include "absl/strings/str_replace.h" #include "absl/strings/str_replace.h"
@ -651,7 +652,10 @@ bool sfz::Region::canUsePreloadedData() const noexcept
bool sfz::Region::isStereo() const noexcept bool sfz::Region::isStereo() const noexcept
{ {
return this->numChannels == 2; if (isGenerator())
return 1;
return (this->preloadedData->getNumChannels() == 2);
} }
template<class T, class U> template<class T, class U>

View file

@ -27,7 +27,7 @@
#include "Defaults.h" #include "Defaults.h"
#include "EGDescription.h" #include "EGDescription.h"
#include "Opcode.h" #include "Opcode.h"
#include "StereoBuffer.h" #include "AudioBuffer.h"
#include <bitset> #include <bitset>
#include <optional> #include <optional>
#include <random> #include <random>
@ -148,9 +148,7 @@ struct Region {
EGDescription filterEG; EGDescription filterEG;
double sampleRate { config::defaultSampleRate }; double sampleRate { config::defaultSampleRate };
int numChannels { 1 }; std::shared_ptr<AudioBuffer<float>> preloadedData { nullptr };
std::shared_ptr<StereoBuffer<float>> preloadedData { nullptr };
private: private:
bool keySwitched { true }; bool keySwitched { true };
bool previousKeySwitched { true }; bool previousKeySwitched { true };

View file

@ -197,8 +197,6 @@ bool sfz::Synth::loadSfzFile(const std::filesystem::path& filename)
lastRegion--; lastRegion--;
continue; continue;
} }
region->numChannels = fileInformation->numChannels;
region->sampleEnd = std::min(region->sampleEnd, fileInformation->end); region->sampleEnd = std::min(region->sampleEnd, fileInformation->end);
region->loopRange.shrinkIfSmaller(fileInformation->loopBegin, fileInformation->loopEnd); region->loopRange.shrinkIfSmaller(fileInformation->loopBegin, fileInformation->loopEnd);
region->preloadedData = fileInformation->preloadedData; region->preloadedData = fileInformation->preloadedData;
@ -274,11 +272,11 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept
voice->setSampleRate(sampleRate); voice->setSampleRate(sampleRate);
} }
void sfz::Synth::renderBlock(StereoSpan<float> buffer) noexcept void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
{ {
ScopedFTZ ftz; ScopedFTZ ftz;
buffer.fill(0.0f); buffer.fill(0.0f);
StereoSpan<float> tempSpan { tempBuffer, buffer.size() }; auto tempSpan = AudioSpan<float>(tempBuffer).first(buffer.getNumFrames());
for (auto& voice : voices) { for (auto& voice : voices) {
voice->renderBlock(tempSpan); voice->renderBlock(tempSpan);
buffer.add(tempSpan); buffer.add(tempSpan);

View file

@ -26,7 +26,7 @@
#include "Parser.h" #include "Parser.h"
#include "Region.h" #include "Region.h"
#include "LeakDetector.h" #include "LeakDetector.h"
#include "StereoSpan.h" #include "AudioSpan.h"
#include "absl/types/span.h" #include "absl/types/span.h"
#include <optional> #include <optional>
#include <random> #include <random>
@ -51,7 +51,7 @@ public:
void setSamplesPerBlock(int samplesPerBlock) noexcept; void setSamplesPerBlock(int samplesPerBlock) noexcept;
void setSampleRate(float sampleRate) noexcept; void setSampleRate(float sampleRate) noexcept;
void renderBlock(StereoSpan<float> buffer) noexcept; void renderBlock(AudioSpan<float> buffer) noexcept;
void noteOn(int delay, int channel, int noteNumber, uint8_t velocity) noexcept; void noteOn(int delay, int channel, int noteNumber, uint8_t velocity) noexcept;
void noteOff(int delay, int channel, int noteNumber, uint8_t velocity) noexcept; void noteOff(int delay, int channel, int noteNumber, uint8_t velocity) noexcept;
void cc(int delay, int channel, int ccNumber, uint8_t ccValue) noexcept; void cc(int delay, int channel, int ccNumber, uint8_t ccValue) noexcept;
@ -91,7 +91,7 @@ private:
std::array<RegionPtrVector, 128> noteActivationLists; std::array<RegionPtrVector, 128> noteActivationLists;
std::array<RegionPtrVector, 128> ccActivationLists; std::array<RegionPtrVector, 128> ccActivationLists;
StereoBuffer<float> tempBuffer { config::defaultSamplesPerBlock }; AudioBuffer<float> tempBuffer { 2, config::defaultSamplesPerBlock };
int samplesPerBlock { config::defaultSamplesPerBlock }; int samplesPerBlock { config::defaultSamplesPerBlock };
float sampleRate { config::defaultSampleRate }; float sampleRate { config::defaultSampleRate };

View file

@ -80,8 +80,9 @@ void sfz::Voice::prepareEGEnvelope(int delay, uint8_t velocity) noexcept
normalizePercents(region->amplitudeEG.getStart(ccState, velocity))); normalizePercents(region->amplitudeEG.getStart(ccState, velocity)));
} }
void sfz::Voice::setFileData(std::unique_ptr<StereoBuffer<float>> file) noexcept void sfz::Voice::setFileData(std::unique_ptr<AudioBuffer<float>> file) noexcept
{ {
// DBG("File data set for sample " << region->sample);
fileData = std::move(file); fileData = std::move(file);
dataReady.store(true); dataReady.store(true);
} }
@ -161,10 +162,9 @@ void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept
indexSpan = absl::MakeSpan(indexBuffer); indexSpan = absl::MakeSpan(indexBuffer);
} }
void sfz::Voice::renderBlock(StereoSpan<float> buffer) noexcept void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
{ {
const auto numSamples = buffer.size(); ASSERT(static_cast<int>(buffer.getNumFrames()) <= samplesPerBlock);
ASSERT(static_cast<int>(numSamples) <= samplesPerBlock);
buffer.fill(0.0f); buffer.fill(0.0f);
if (state == State::idle || region == nullptr) if (state == State::idle || region == nullptr)
@ -175,34 +175,59 @@ void sfz::Voice::renderBlock(StereoSpan<float> buffer) noexcept
else else
fillWithData(buffer); fillWithData(buffer);
auto envelopeSpan = tempSpan1.first(numSamples); if (region->isStereo())
amplitudeEnvelope.getBlock(envelopeSpan); processStereo(buffer);
buffer.applyGain(envelopeSpan); else
processMono(buffer);
egEnvelope.getBlock(envelopeSpan);
buffer.applyGain(envelopeSpan);
if (!egEnvelope.isSmoothing()) if (!egEnvelope.isSmoothing())
reset(); reset();
} }
void sfz::Voice::fillWithData(StereoSpan<float> buffer) noexcept void sfz::Voice::processMono(AudioSpan<float> buffer) noexcept
{
const auto numSamples = buffer.getNumFrames();
auto leftBuffer = buffer.getSpan(0);
auto rightBuffer = buffer.getSpan(1);
auto envelopeSpan = tempSpan1.first(numSamples);
amplitudeEnvelope.getBlock(envelopeSpan);
::applyGain<float>(envelopeSpan, leftBuffer);
egEnvelope.getBlock(envelopeSpan);
::applyGain<float>(envelopeSpan, leftBuffer);
::copy<float>(leftBuffer, rightBuffer);
}
void sfz::Voice::processStereo(AudioSpan<float> buffer) noexcept
{
const auto numSamples = buffer.getNumFrames();
auto envelopeSpan = tempSpan1.first(numSamples);
amplitudeEnvelope.getBlock(envelopeSpan);
buffer.applyGain(envelopeSpan);
egEnvelope.getBlock(envelopeSpan);
buffer.applyGain(envelopeSpan);
}
void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
{ {
auto source { [&]() { auto source { [&]() {
if (region->canUsePreloadedData() || !dataReady) if (region->canUsePreloadedData() || !dataReady)
return StereoSpan<const float>(*region->preloadedData); return AudioSpan<const float>(*region->preloadedData);
else else
return StereoSpan<const float>(*fileData); return AudioSpan<const float>(*fileData);
}() }; }() };
auto indices = indexSpan.first(buffer.size()); auto indices = indexSpan.first(buffer.getNumFrames());
auto jumps = tempSpan1.first(buffer.size()); auto jumps = tempSpan1.first(buffer.getNumFrames());
auto leftCoeffs = tempSpan1.first(buffer.size()); auto leftCoeffs = tempSpan1.first(buffer.getNumFrames());
auto rightCoeffs = tempSpan2.first(buffer.size()); auto rightCoeffs = tempSpan2.first(buffer.getNumFrames());
::fill<float>(jumps, pitchRatio * speedRatio); ::fill<float>(jumps, pitchRatio * speedRatio);
if (region->shouldLoop() && region->trueSampleEnd() <= source.size()) { if (region->shouldLoop() && region->trueSampleEnd() <= source.getNumFrames()) {
floatPosition = ::loopingSFZIndex<float, false>( floatPosition = ::loopingSFZIndex<float, false>(
jumps, jumps,
leftCoeffs, leftCoeffs,
@ -218,32 +243,42 @@ void sfz::Voice::fillWithData(StereoSpan<float> buffer) noexcept
rightCoeffs, rightCoeffs,
indices, indices,
floatPosition, floatPosition,
source.size() - 1); source.getNumFrames() - 1);
} }
auto ind = indices.data(); auto ind = indices.data();
auto leftCoeff = leftCoeffs.data(); auto leftCoeff = leftCoeffs.data();
auto rightCoeff = rightCoeffs.data(); auto rightCoeff = rightCoeffs.data();
auto left = buffer.left().data(); auto left = buffer.getChannel(0);
auto right = buffer.right().data(); if (source.getNumChannels() == 1) {
while (ind < indices.end()) { while (ind < indices.end()) {
*left = source.left()[*ind] * (*leftCoeff) + source.left()[*ind + 1] * (*rightCoeff); *left = source.getChannel(0)[*ind] * (*leftCoeff) + source.getChannel(0)[*ind + 1] * (*rightCoeff);
*right = source.right()[*ind] * (*leftCoeff) + source.right()[*ind + 1] * (*rightCoeff); left++;
left++; ind++;
right++; leftCoeff++;
ind++; rightCoeff++;
leftCoeff++; }
rightCoeff++; } else {
auto right = buffer.getChannel(1);
while (ind < indices.end()) {
*left = source.getChannel(0)[*ind] * (*leftCoeff) + source.getChannel(0)[*ind + 1] * (*rightCoeff);
*right = source.getChannel(1)[*ind] * (*leftCoeff) + source.getChannel(1)[*ind + 1] * (*rightCoeff);
left++;
right++;
ind++;
leftCoeff++;
rightCoeff++;
}
} }
if (!region->shouldLoop() && (floatPosition + 1.01) > source.size()) { if (!region->shouldLoop() && (floatPosition + 1.01) > source.getNumFrames()) {
DBG("Releasing " << region->sample); DBG("Releasing " << region->sample);
auto last = std::distance(indices.begin(), absl::c_find(indices, region->trueSampleEnd() - 1)); auto last = std::distance(indices.begin(), absl::c_find(indices, region->trueSampleEnd() - 1));
release(last); release(last);
} }
} }
void sfz::Voice::fillWithGenerator(StereoSpan<float> buffer) noexcept void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
{ {
if (region->sample != "*sine") if (region->sample != "*sine")
return; return;
@ -251,10 +286,10 @@ void sfz::Voice::fillWithGenerator(StereoSpan<float> buffer) noexcept
float step = baseFrequency * twoPi<float> / sampleRate; float step = baseFrequency * twoPi<float> / sampleRate;
phase = ::linearRamp<float>(tempSpan1, phase, step); phase = ::linearRamp<float>(tempSpan1, phase, step);
::sin<float>(tempSpan1.first(buffer.size()), buffer.left()); ::sin<float>(tempSpan1.first(buffer.getNumFrames()), buffer.getSpan(0));
absl::c_copy(buffer.left(), buffer.right().begin()); ::copy<float>(buffer.getSpan(0), buffer.getSpan(1));
sourcePosition += buffer.size(); sourcePosition += buffer.getNumFrames();
} }
bool sfz::Voice::checkOffGroup(int delay, uint32_t group) noexcept bool sfz::Voice::checkOffGroup(int delay, uint32_t group) noexcept

View file

@ -26,8 +26,8 @@
#include "Config.h" #include "Config.h"
#include "LinearEnvelope.h" #include "LinearEnvelope.h"
#include "Region.h" #include "Region.h"
#include "StereoBuffer.h" #include "AudioBuffer.h"
#include "StereoSpan.h" #include "AudioSpan.h"
#include "LeakDetector.h" #include "LeakDetector.h"
#include <absl/types/span.h> #include <absl/types/span.h>
#include <atomic> #include <atomic>
@ -47,7 +47,7 @@ public:
void startVoice(Region* region, int delay, int channel, int number, uint8_t value, TriggerType triggerType) noexcept; void startVoice(Region* region, int delay, int channel, int number, uint8_t value, TriggerType triggerType) noexcept;
void setFileData(std::unique_ptr<StereoBuffer<float>> file) noexcept; void setFileData(std::unique_ptr<AudioBuffer<float>> file) noexcept;
void registerNoteOff(int delay, int channel, int noteNumber, uint8_t velocity) noexcept; void registerNoteOff(int delay, int channel, int noteNumber, uint8_t velocity) noexcept;
void registerCC(int delay, int channel, int ccNumber, uint8_t ccValue) noexcept; void registerCC(int delay, int channel, int ccNumber, uint8_t ccValue) noexcept;
void registerPitchWheel(int delay, int channel, int pitch) noexcept; void registerPitchWheel(int delay, int channel, int pitch) noexcept;
@ -55,7 +55,7 @@ public:
void registerTempo(int delay, float secondsPerQuarter) noexcept; void registerTempo(int delay, float secondsPerQuarter) noexcept;
bool checkOffGroup(int delay, uint32_t group) noexcept; bool checkOffGroup(int delay, uint32_t group) noexcept;
void renderBlock(StereoSpan<float> buffer) noexcept; void renderBlock(AudioSpan<float, 2> buffer) noexcept;
bool isFree() const noexcept; bool isFree() const noexcept;
int getTriggerNumber() const noexcept; int getTriggerNumber() const noexcept;
@ -66,9 +66,11 @@ public:
void reset() noexcept; void reset() noexcept;
void garbageCollect() noexcept; void garbageCollect() noexcept;
private: private:
void fillWithData(StereoSpan<float> buffer) noexcept; void fillWithData(AudioSpan<float> buffer) noexcept;
void fillWithGenerator(StereoSpan<float> buffer) noexcept; void fillWithGenerator(AudioSpan<float> buffer) noexcept;
void prepareEGEnvelope(int delay, uint8_t velocity) noexcept; void prepareEGEnvelope(int delay, uint8_t velocity) noexcept;
void processMono(AudioSpan<float> buffer) noexcept;
void processStereo(AudioSpan<float> buffer) noexcept;
void release(int delay) noexcept; void release(int delay) noexcept;
Region* region { nullptr }; Region* region { nullptr };
@ -96,7 +98,7 @@ private:
uint32_t initialDelay { 0 }; uint32_t initialDelay { 0 };
std::atomic<bool> dataReady { false }; std::atomic<bool> dataReady { false };
std::unique_ptr<StereoBuffer<float>> fileData { nullptr }; std::unique_ptr<AudioBuffer<float>> fileData { nullptr };
Buffer<float> tempBuffer1; Buffer<float> tempBuffer1;
Buffer<float> tempBuffer2; Buffer<float> tempBuffer2;

View file

@ -257,10 +257,8 @@ TEST_CASE("[Files] Channels (channels.sfz)")
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/channels.sfz"); synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/channels.sfz");
REQUIRE(synth.getNumRegions() == 2); REQUIRE(synth.getNumRegions() == 2);
REQUIRE(synth.getRegionView(0)->sample == "mono_sample.wav"); REQUIRE(synth.getRegionView(0)->sample == "mono_sample.wav");
REQUIRE(synth.getRegionView(0)->numChannels == 1);
REQUIRE(!synth.getRegionView(0)->isStereo()); REQUIRE(!synth.getRegionView(0)->isStereo());
REQUIRE(synth.getRegionView(1)->sample == "stereo_sample.wav"); REQUIRE(synth.getRegionView(1)->sample == "stereo_sample.wav");
REQUIRE(synth.getRegionView(1)->numChannels == 2);
REQUIRE(synth.getRegionView(1)->isStereo()); REQUIRE(synth.getRegionView(1)->isStereo());
} }