149 lines
4.5 KiB
C++
149 lines
4.5 KiB
C++
// SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
|
// license. You should have receive a LICENSE.md file along with the code.
|
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
|
|
|
|
|
/**
|
|
Note(jpc): implementation status
|
|
|
|
- [x] strings_number
|
|
- [ ] strings_wet_oncc
|
|
|
|
Extensions
|
|
- [x] strings_wet
|
|
*/
|
|
|
|
#include "Strings.h"
|
|
#include "impl/ResonantArray.h"
|
|
#include "impl/ResonantArraySSE.h"
|
|
#include "impl/ResonantArrayAVX.h"
|
|
#include "Opcode.h"
|
|
#include "MathHelpers.h"
|
|
#include "SIMDHelpers.h"
|
|
#include "cpuid/cpuinfo.hpp"
|
|
#include "absl/memory/memory.h"
|
|
#include <cmath>
|
|
|
|
namespace sfz {
|
|
namespace fx {
|
|
|
|
Strings::Strings()
|
|
{
|
|
ResonantArray* array = nullptr;
|
|
|
|
#if SFIZZ_CPU_FAMILY_X86_64 || SFIZZ_CPU_FAMILY_I386
|
|
cpuid::cpuinfo cpuInfo;
|
|
if (cpuInfo.has_avx())
|
|
array = new ResonantArrayAVX;
|
|
else if (cpuInfo.has_sse())
|
|
array = new ResonantArraySSE;
|
|
#endif
|
|
if (!array)
|
|
array = new ResonantArrayScalar;
|
|
|
|
_stringsArray.reset(array);
|
|
}
|
|
|
|
Strings::~Strings()
|
|
{
|
|
}
|
|
|
|
void Strings::setSampleRate(double sampleRate)
|
|
{
|
|
const unsigned numStrings = _numStrings;
|
|
|
|
AudioBuffer<float, 4> parameterBuffers { 4, numStrings };
|
|
|
|
auto pitches = parameterBuffers.getSpan(0);
|
|
auto bandwidths = parameterBuffers.getSpan(1);
|
|
auto feedbacks = parameterBuffers.getSpan(2);
|
|
auto gains = parameterBuffers.getSpan(3);
|
|
|
|
for (unsigned i = 0; i < numStrings; ++i) {
|
|
int midiNote = i + 24;
|
|
pitches[i] = 440.0 * std::exp2((midiNote - 69) * (1.0 / 12.0));
|
|
}
|
|
|
|
// 1 Hz works decently as compromise of selectivity/speed
|
|
sfz::fill(bandwidths, 1.0f);
|
|
|
|
// TODO(jpc) find how to adjust the string feedbacks
|
|
// for now set a fixed release time for all strings
|
|
const double releaseTime = 50e-3;
|
|
const double releaseFeedback = std::exp(-6.91 / (releaseTime * sampleRate));
|
|
sfz::fill<float>(feedbacks, releaseFeedback);
|
|
|
|
// TODO(jpc) damping of the high frequencies
|
|
// fixed gains for now
|
|
sfz::fill(gains, 1e-3f);
|
|
|
|
_stringsArray->setup(
|
|
sampleRate, numStrings,
|
|
pitches.data(), bandwidths.data(), feedbacks.data(), gains.data());
|
|
}
|
|
|
|
void Strings::setSamplesPerBlock(int samplesPerBlock)
|
|
{
|
|
_tempBuffer.resize(samplesPerBlock);
|
|
|
|
_stringsArray->setSamplesPerBlock(samplesPerBlock);
|
|
}
|
|
|
|
void Strings::clear()
|
|
{
|
|
_stringsArray->clear();
|
|
}
|
|
|
|
void Strings::process(const float* const inputs[], float* const outputs[], unsigned nframes)
|
|
{
|
|
auto inputL = absl::MakeConstSpan(inputs[0], nframes);
|
|
auto inputR = absl::MakeConstSpan(inputs[1], nframes);
|
|
|
|
// mix down the stereo signal to create the resonator excitation source
|
|
absl::Span<float> resInput = _tempBuffer.getSpan(0).first(nframes);
|
|
sfz::applyGain1<float>(M_SQRT1_2, inputL, resInput);
|
|
sfz::multiplyAdd1<float>(M_SQRT1_2, inputR, resInput);
|
|
|
|
// generate the strings summed into a common buffer
|
|
absl::Span<float> resOutput = _tempBuffer.getSpan(1).first(nframes);
|
|
|
|
_stringsArray->process(resInput.data(), resOutput.data(), nframes);
|
|
|
|
// mix the resonator into the output
|
|
auto outputL = absl::MakeSpan(outputs[0], nframes);
|
|
auto outputR = absl::MakeSpan(outputs[1], nframes);
|
|
|
|
absl::Span<float> wet = _tempBuffer.getSpan(2).first(nframes);
|
|
sfz::fill(wet, 0.01f *_wet); // TOD strings_wet_oncc modulation...
|
|
|
|
sfz::copy(inputL, outputL);
|
|
sfz::copy(inputR, outputR);
|
|
sfz::multiplyAdd<float>(wet, resOutput, outputL);
|
|
sfz::multiplyAdd<float>(wet, resOutput, outputR);
|
|
}
|
|
|
|
std::unique_ptr<Effect> Strings::makeInstance(absl::Span<const Opcode> members)
|
|
{
|
|
Strings* strings = new Strings;
|
|
std::unique_ptr<Effect> fx { strings };
|
|
|
|
for (const Opcode& opc : members) {
|
|
switch (opc.lettersOnlyHash) {
|
|
case hash("strings_number"):
|
|
if (auto value = opc.read(Default::stringsNumber))
|
|
strings->_numStrings = *value;
|
|
break;
|
|
case hash("strings_wet"):
|
|
if (auto value = opc.read(Default::effect))
|
|
strings->_wet = *value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return fx;
|
|
}
|
|
|
|
} // namespace fx
|
|
} // namespace sfz
|