Added synthesis.

This commit is contained in:
yaw-man 2022-08-19 15:03:29 -03:00
parent 42337e08cd
commit 9f50e9d40e
4 changed files with 157 additions and 34 deletions

View File

@ -2,22 +2,15 @@
#define DISTRHO_PLUGIN_INFO_H_INCLUDED #define DISTRHO_PLUGIN_INFO_H_INCLUDED
#define DISTRHO_PLUGIN_BRAND "yaw-audio" #define DISTRHO_PLUGIN_BRAND "yaw-audio"
#define DISTRHO_PLUGIN_NAME "yaw-tab" #define DISTRHO_PLUGIN_NAME "yaw-shepard"
#define DISTRHO_PLUGIN_URI "https://yaw.man/plugins/yaw-tab" #define DISTRHO_PLUGIN_URI "https://yaw.man/plugins/yaw-shepard"
#define DISTRHO_PLUGIN_HAS_UI 1 #define DISTRHO_PLUGIN_HAS_UI 1
#define DISTRHO_PLUGIN_IS_RT_SAFE 1 #define DISTRHO_PLUGIN_IS_RT_SAFE 1
#define DISTRHO_PLUGIN_NUM_INPUTS 0 #define DISTRHO_PLUGIN_NUM_INPUTS 0
#define DISTRHO_PLUGIN_NUM_OUTPUTS 2 #define DISTRHO_PLUGIN_NUM_OUTPUTS 1
#define DISTRHO_UI_USE_NANOVG 1 #define DISTRHO_UI_USE_NANOVG 1
// only checking if supported, not actually used
#define DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST 1
#ifdef __MOD_DEVICES__
#define DISTRHO_PLUGIN_USES_MODGUI 1
#endif
enum Parameters { enum Parameters {
ktpax = 0, ktpax = 0,
ktpay, ktpay,

View File

@ -1,4 +1,5 @@
#include "DistrhoPlugin.hpp" #include "DistrhoPlugin.hpp"
#include "synth.h"
START_NAMESPACE_DISTRHO START_NAMESPACE_DISTRHO
@ -7,19 +8,18 @@ class TabPlugin : public Plugin
public: public:
TabPlugin() TabPlugin()
: Plugin(kParameterCount, 0, 0), : Plugin(kParameterCount, 0, 0),
sampleRate(getSampleRate()) sampleRate(getSampleRate()),
synth(sampleRate),
fParameters { 0 }
{ {
// clear all parameters
std::memset(fParameters, 0, sizeof(float) * kParameterCount);
} }
protected: protected:
const char* getLabel() const override { return "yaw-tab-shepard"; } const char* getLabel() const override { return "yaw-shepard"; }
const char* getDescription() const override { return "Drawing tablet Shepard buzz tone"; } const char* getDescription() const override { return "Generalized Shepard tone, tablet UI"; }
const char* getMaker() const override { return "yaw-audio"; } const char* getMaker() const override { return "yaw-audio"; }
const char* getHomePage() const override { return "https://yaw.man/plugins/yaw-tab"; } const char* getHomePage() const override { return "https://yaw.man/plugins/yaw-tab"; }
const char* getLicense() const override { return "ISC"; } const char* getLicense() const override { return "Fuck You Pay Me"; }
uint32_t getVersion() const override { return d_version(1, 0, 0); } uint32_t getVersion() const override { return d_version(1, 0, 0); }
int64_t getUniqueId() const override { return d_cconst('y', 's', 'p', 'd'); } int64_t getUniqueId() const override { return d_cconst('y', 's', 'p', 'd'); }
@ -69,6 +69,7 @@ protected:
void sampleRateChanged(double newRate) override void sampleRateChanged(double newRate) override
{ {
synth.setSampleRate(sampleRate, newRate);
sampleRate = newRate; sampleRate = newRate;
} }
@ -82,14 +83,15 @@ protected:
fParameters[idx] = val; fParameters[idx] = val;
switch (idx) { switch (idx) {
case ktpax: case ktpax:
period = 0.02f * val * static_cast<float>(sampleRate); synth.setFrequencyShift(val);
break; break;
case ktpay: case ktpay:
synth.setTargetRatio(val);
break; break;
case ktpaz: case ktpaz:
break; break;
case ktpap: case ktpap:
volume = val; synth.setVolume(val);
break; break;
case kParameterButtonA: case kParameterButtonA:
break; break;
@ -100,30 +102,17 @@ protected:
void run(const float** inputs, float** outputs, uint32_t frames) override void run(const float** inputs, float** outputs, uint32_t frames) override
{ {
synth.process(*outputs, frames);
for (uint32_t i = 0; i < frames; ++i) {
counter++;
if (counter > period) {
outputs[0][i] = outputs[1][i] = volume;
counter = 0;
}
else {
outputs[0][i] = outputs[1][i] = 0.0f;
}
}
parity = !parity; parity = !parity;
fParameters[kParameterTime] += parity ? 1 : -1; fParameters[kParameterTime] += parity ? 1 : -1;
} }
private: private:
float fParameters[kParameterCount]; float fParameters[kParameterCount];
float period = 0.f;
float counter = 0.f;
float volume = 0.f;
double sampleRate; double sampleRate;
bool parity = false; bool parity = false;
Synth synth;
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TabPlugin) DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TabPlugin)
}; };

104
src/yaw-shepard/synth.cpp Normal file
View File

@ -0,0 +1,104 @@
#include "synth.h"
Synth::Synth(double sampleRate){
setSampleRate(48000.0, sampleRate);
}
#define frac(x) ((x) - ((long)x))
constexpr double MIN_VOLUME = 0.00001;
//Volume of voice as a function of sample rate independent frequency.
static constexpr double getAmplitude( double hz )
{
if( hz < 20.0 ) return 0.0;
if( hz < 440.0 ) return hz / 440.0;
if( hz < 16000.0 ) return (16000.0 - hz) / 15560.0;
return 0.0;
}
//Sanity checks: voices should become silent outside audible frequencies.
//Voice index code will loop infinitely if these fail.
static_assert( MIN_VOLUME > getAmplitude(10.0) );
static_assert( MIN_VOLUME > getAmplitude(20000.0));
void Synth::process(float* output, const uint32_t frames)
{
double hz;
//Render.
for(uint32_t i = 0; i < frames; i++){
//Set pitch.
hz = hzFund *= hzShift;
//Set timbre.
ratio = ratioSlewRate * ratio + (1.0 - ratioSlewRate) * targetRatio;
for(uint voice = 0; voice < NUM_VOICES; ++voice){
//Get new phase.
double phase = phases[(voice + idxFund) & VOICE_MASK];
phase += hz * sampleInterval;
phase = frac(phase);
//Don't bother rendering anything over the Nyquist rate.
if( hz > hzNyq ) break;
output[i] += getAmplitude(hz) * sin(2.0 * M_PI * phase);
//Remember phase, move to higher overtone.
phases[(voice + idxFund) & VOICE_MASK] = phase;
hz *= ratio;
}
output[i] *= volume;
}
//Make timbre cyclical.
if( hzShift < 1.0 )
{
//Pitch low and decreasing, shift bottom voices to top.
while( getAmplitude(hz) > MIN_VOLUME
&& getAmplitude(hzFund) < MIN_VOLUME)
{
++idxFund;
hz *= ratio;
hzFund *= ratio;
}
}
if( (hzShift > 1.0) || (targetRatio > ratio))
{
//Pitch high and increasing. Shift top voices to bottom.
while( getAmplitude(hz) < MIN_VOLUME
&& getAmplitude(hzFund) > MIN_VOLUME)
{
--idxFund;
hz /= ratio;
hzFund /= ratio;
}
}
}
void Synth::setSampleRate(double oldRate, double newRate){
sampleInterval = 1.0 / newRate;
hzNyq = newRate / 2;
ratioSlewRate *= newRate / oldRate;
hzShift = pow(hzShift, newRate / oldRate);
}
//Takes value from 0 to 1 representing frequency shift factor.
//0 : lower one octave per second
//1 : raise one octave per second
void Synth::setFrequencyShift(double in){
hzShift = exp2(2.0 * (in - 0.5) * sampleInterval);
}
//Slew to given ratio.
//0 : next voice is one fifth above previous voice.
//1 : next voice is one octave above previous voice.
void Synth::setTargetRatio(double in){
targetRatio = 1.5 + in * 0.5;
}
void Synth::setVolume(double in){
volume = in;
}

37
src/yaw-shepard/synth.h Normal file
View File

@ -0,0 +1,37 @@
#include "DistrhoPlugin.hpp"
#include <array>
struct Voice
{
double hz;
double phase;
};
constexpr int NUM_VOICES = 256;
constexpr uchar VOICE_MASK = 0xFF;
class Synth
{
public:
explicit Synth(double sampleRate);
void process(float *output, uint32_t frames);
void setFrequencyShift(double in);
void setTargetRatio(double in);
void setSampleRate(double oldRate, double newRate);
void setVolume(double in);
private:
//Phase must persist between run invocation.
//Phase of each voice, considered as a period function on the unit interval.
std::array<double, NUM_VOICES> phases = {0};
//Index of lowest voice in phase array.
uchar idxFund = 0;
float volume = 0.0f;
double hzFund = 32.7;
double hzShift = 1.0;
double hzNyq = 24000.0;
double ratio = 2.0;
double targetRatio = 2.0;
double ratioSlewRate = 0.99999;
double sampleInterval = 1.0 / 48000.0;
};