Added synthesis.
This commit is contained in:
parent
42337e08cd
commit
9f50e9d40e
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
Loading…
Reference in New Issue