Update DPF submodule for VST3 build;
New shepard tone generator with mouse UI.
This commit is contained in:
parent
cbdddd6bbc
commit
f60f589359
|
@ -1,4 +1,3 @@
|
|||
[submodule "lib/DPF"]
|
||||
ignore = all
|
||||
path = lib/DPF
|
||||
url = https://github.com/DISTRHO/DPF.git
|
2
lib/DPF
2
lib/DPF
|
@ -1 +1 @@
|
|||
Subproject commit 86a621bfd86922a49ce593fec2a618a1e0cc6ef3
|
||||
Subproject commit 23692d024e57c617bd0692beb1835d48f98eb914
|
|
@ -3,3 +3,4 @@ add_subdirectory(yaw-tab)
|
|||
add_subdirectory(yaw-shepard)
|
||||
add_subdirectory(yaw-vowel)
|
||||
add_subdirectory(yaw-totune)
|
||||
add_subdirectory(yaw-shep)
|
|
@ -0,0 +1,11 @@
|
|||
dpf_add_plugin(yaw-shep
|
||||
TARGETS vst2 vst3
|
||||
FILES_DSP
|
||||
dsp.cpp
|
||||
synth.cpp
|
||||
FILES_UI
|
||||
ui.cpp)
|
||||
|
||||
target_include_directories(yaw-shep PUBLIC
|
||||
"."
|
||||
"../../lib/scale")
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED
|
||||
#define DISTRHO_PLUGIN_INFO_H_INCLUDED
|
||||
|
||||
#define DISTRHO_PLUGIN_BRAND "yaw-audio"
|
||||
#define DISTRHO_PLUGIN_NAME "yaw-shep"
|
||||
#define DISTRHO_PLUGIN_URI "https://yaw.man/plugins/yaw-shep"
|
||||
|
||||
|
||||
#define DISTRHO_PLUGIN_IS_RT_SAFE 1
|
||||
#define DISTRHO_PLUGIN_NUM_INPUTS 0
|
||||
#define DISTRHO_PLUGIN_NUM_OUTPUTS 1
|
||||
#define DISTRHO_PLUGIN_WANT_MIDI_INPUT 1
|
||||
#define DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 0
|
||||
|
||||
#define DISTRHO_PLUGIN_HAS_UI 1
|
||||
#define DISTRHO_UI_USE_NANOVG 1
|
||||
|
||||
enum Parameters {
|
||||
kMouseX = 0,
|
||||
kMouseY,
|
||||
kVolume,
|
||||
kHz,
|
||||
kParameterCount,
|
||||
};
|
||||
|
||||
#endif // DISTRHO_PLUGIN_INFO_H_INCLUDED
|
|
@ -0,0 +1,143 @@
|
|||
#include "DistrhoPlugin.hpp"
|
||||
#include "synth.h"
|
||||
|
||||
START_NAMESPACE_DISTRHO
|
||||
|
||||
class ShepPlug : public Plugin
|
||||
{
|
||||
public:
|
||||
ShepPlug()
|
||||
: Plugin(kParameterCount, 0, 0),
|
||||
sampleRate(getSampleRate()),
|
||||
synth(sampleRate),
|
||||
fParameters { 0 }
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
const char* getLabel() const override { return "yaw-shep"; }
|
||||
const char* getDescription() const override { return "Generalized Shepard tone, mouse UI"; }
|
||||
const char* getMaker() const override { return "yaw-audio"; }
|
||||
const char* getHomePage() const override { return "https://yaw.man/plugins/yaw-shep"; }
|
||||
const char* getLicense() const override { return "Fuck You Pay Me"; }
|
||||
uint32_t getVersion() const override { return d_version(1, 0, 0); }
|
||||
int64_t getUniqueId() const override { return d_cconst('y', 's', 'p', 'm'); }
|
||||
|
||||
|
||||
void initParameter(uint32_t index, Parameter& parameter) override
|
||||
{
|
||||
parameter.hints = kParameterIsAutomable;
|
||||
parameter.ranges.def = 0.0f;
|
||||
parameter.ranges.min = 0.0f;
|
||||
parameter.ranges.max = 1.0f;
|
||||
|
||||
switch (index)
|
||||
{
|
||||
case kHz:
|
||||
parameter.name = "hz";
|
||||
parameter.symbol = "hz";
|
||||
parameter.hints = kParameterIsOutput;
|
||||
parameter.ranges.def = 55.0f;
|
||||
parameter.ranges.min = 55.0f;
|
||||
parameter.ranges.max = 880.0f;
|
||||
case kMouseX:
|
||||
parameter.name = "x";
|
||||
parameter.symbol = "x";
|
||||
break;
|
||||
case kMouseY:
|
||||
parameter.name = "y";
|
||||
parameter.symbol = "y";
|
||||
break;
|
||||
case kVolume:
|
||||
parameter.name = "Volume";
|
||||
parameter.symbol = "vol";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void sampleRateChanged(double newRate) override
|
||||
{
|
||||
synth.setSampleRate(sampleRate, newRate);
|
||||
sampleRate = newRate;
|
||||
}
|
||||
|
||||
float getParameterValue(uint32_t index) const override
|
||||
{
|
||||
return fParameters[index];
|
||||
}
|
||||
|
||||
void setParameterValue(uint32_t idx, float val) override
|
||||
{
|
||||
fParameters[idx] = val;
|
||||
switch (idx) {
|
||||
case kMouseX:
|
||||
synth.setPhaseVelocity(val);
|
||||
break;
|
||||
case kMouseY:
|
||||
synth.setTone(val);
|
||||
break;
|
||||
case kVolume:
|
||||
synth.setVolume(val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void run(const float** inputs, float** outputs, uint32_t frames,
|
||||
const MidiEvent* midiEvents, uint32_t midiEventCount) override
|
||||
{
|
||||
|
||||
for (uint32_t i = 0; i < midiEventCount; ++i)
|
||||
{
|
||||
if (midiEvents[i].size <= 3)
|
||||
{
|
||||
uint8_t status = midiEvents[i].data[0];
|
||||
uint8_t data = midiEvents[i].data[1] & 127;
|
||||
uint8_t velocity = midiEvents[i].data[2] & 127;
|
||||
|
||||
float vol = velocity / 127.0f;
|
||||
|
||||
switch (status & 0xf0)
|
||||
{
|
||||
//Controller change.
|
||||
case 0xb0:
|
||||
//Channel Volume.
|
||||
if( data == 7 ) setParameterValue(kVolume, vol);
|
||||
//Breath Control.
|
||||
if( data == 2 ) setParameterValue(kVolume, vol);
|
||||
break;
|
||||
//Aftertouch.
|
||||
case 0xd0:
|
||||
setParameterValue(kVolume, vol);
|
||||
break;
|
||||
//Note On.
|
||||
case 0x90:
|
||||
synth.setNote(data);
|
||||
/* fall through */
|
||||
//Note Off.
|
||||
case 0x80:
|
||||
setParameterValue(kVolume, vol);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synth.process(*outputs, frames);
|
||||
fParameters[kHz] = synth.hzFund;
|
||||
}
|
||||
|
||||
private:
|
||||
float fParameters[kParameterCount];
|
||||
double sampleRate;
|
||||
bool parity = false;
|
||||
|
||||
Synth synth;
|
||||
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ShepPlug)
|
||||
};
|
||||
|
||||
Plugin* createPlugin()
|
||||
{
|
||||
return new ShepPlug();
|
||||
}
|
||||
|
||||
END_NAMESPACE_DISTRHO
|
|
@ -0,0 +1,119 @@
|
|||
#include "synth.h"
|
||||
|
||||
Synth::Synth(double sampleRate){
|
||||
setSampleRate(48000.0, sampleRate);
|
||||
}
|
||||
|
||||
void Synth::resetPhase(){
|
||||
spectrumPhase = 0.0;
|
||||
}
|
||||
|
||||
#define frac(x) ((x) - ((long)x))
|
||||
constexpr double MIN_VOLUME = 0.00001;
|
||||
|
||||
static constexpr double amin = 50;
|
||||
static constexpr double apiq = 500;
|
||||
static constexpr double amax = 10000;
|
||||
//Volume of voice as a function of sample rate independent frequency.
|
||||
static constexpr inline float getAmplitude( double hz )
|
||||
{
|
||||
if( hz < amin ) return 0.0;
|
||||
if( hz < apiq ) {
|
||||
double a = (hz - amin) / (apiq - amin);
|
||||
return a * a;}
|
||||
if( hz < amax ) {
|
||||
double a = 1.0 - (hz - apiq) / (amax - apiq);
|
||||
return a * a;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
//Interpolation function on unit interval with good regularity as a function on S^1.
|
||||
static constexpr inline float smooth( double x )
|
||||
{
|
||||
return x * x * x;//(3.0 * x - 2.0);
|
||||
}
|
||||
|
||||
//Sanity checks: voices should become silent outside audible frequencies.
|
||||
static_assert( MIN_VOLUME > getAmplitude(10.0) );
|
||||
static_assert( MIN_VOLUME > getAmplitude(20000.0));
|
||||
|
||||
//New fundamental is twice as high as old fundamental.
|
||||
//Even overtones become plain overtones.
|
||||
void Synth::shiftUp()
|
||||
{
|
||||
tablePhase *= 2.0;
|
||||
spectrumPhase += 1.0;
|
||||
}
|
||||
|
||||
//New fundamental is half as high as old fundamental.
|
||||
//Overtones become even overtones.
|
||||
void Synth::shiftDown()
|
||||
{
|
||||
tablePhase /= 2.0;
|
||||
spectrumPhase -= 1.0;
|
||||
}
|
||||
|
||||
void Synth::process(float* output, const uint32_t frames)
|
||||
{
|
||||
//Render.
|
||||
for(uint32_t i = 0; i < frames; i++){
|
||||
|
||||
//Set pitch.
|
||||
hzFund = exp2(spectrumPhase) * fMin;
|
||||
|
||||
tablePhase += hzFund * sampleInterval;
|
||||
tablePhase = frac(tablePhase);
|
||||
|
||||
bool isOddHarmonic = true;
|
||||
|
||||
for(uint voice = 0; voice < NUM_VOICES; ++voice){
|
||||
|
||||
double voicePhase = 2.0 * M_PI * tablePhase * (voice + 1.0);
|
||||
//Anti-aliasing: don't bother rendering anything over the Nyquist rate.
|
||||
if( hzFund * (voice + 1.0) > hzNyq ) break;
|
||||
|
||||
output[i] += (!isOddHarmonic + isOddHarmonic * smooth(spectrumPhase)) //Fade in odd harmonics.
|
||||
* getAmplitude(hzFund * (voice + 1.0)) //Frequency response.
|
||||
* sinf(static_cast<float>(voicePhase)); //Additives.
|
||||
|
||||
isOddHarmonic = !isOddHarmonic;
|
||||
}
|
||||
|
||||
output[i] *= fMin * volume / static_cast<float>(8 * NUM_VOICES);
|
||||
|
||||
//Wrapping.
|
||||
spectrumPhase += spectrumPhaseVelocity * sampleInterval;
|
||||
if( spectrumPhase > 1.0) shiftDown();
|
||||
if( spectrumPhase < 0.0) shiftUp();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Synth::setSampleRate(double oldRate, double newRate){
|
||||
sampleInterval = 1.0 / newRate;
|
||||
hzNyq = newRate / 2;
|
||||
}
|
||||
|
||||
//Takes value from 0 to 1 representing rate of wrapping pitch increase.
|
||||
//0 : lower one octave per second
|
||||
//1 : raise one octave per second
|
||||
void Synth::setPhaseVelocity(double in){
|
||||
spectrumPhaseVelocity = 2.0 * (in - 0.5);
|
||||
}
|
||||
|
||||
//Vary pitch of fundamental in a non-wrapping kinda way.
|
||||
//Current range is six octaves, current bottom is 13.75 Hz (A0)
|
||||
void Synth::setTone(double in){
|
||||
falloff = in;
|
||||
}
|
||||
|
||||
|
||||
void Synth::setVolume(double in){
|
||||
volume = in;
|
||||
}
|
||||
|
||||
void Synth::setNote(uint8_t step){
|
||||
fMin = 440.0 * exp2((step - 69.0) / 12.0);
|
||||
Synth::resetPhase();
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
#include "DistrhoPlugin.hpp"
|
||||
#include <array>
|
||||
|
||||
constexpr unsigned int NUM_VOICES = 512;
|
||||
constexpr unsigned int VOICE_MASK = NUM_VOICES - 1;
|
||||
|
||||
class Synth
|
||||
{
|
||||
public:
|
||||
explicit Synth(double sampleRate);
|
||||
void process(float *output, uint32_t frames);
|
||||
void setPhaseVelocity(double in);
|
||||
void setTone(double in);
|
||||
void setSampleRate(double oldRate, double newRate);
|
||||
void setVolume(double in);
|
||||
void setNote(uint8_t step);
|
||||
|
||||
//For recordable performances.
|
||||
//For now it requires a button push, but perhaps
|
||||
//we should require time position and have the phase reset automatically
|
||||
//on song start?
|
||||
void resetPhase();
|
||||
|
||||
|
||||
//Current fundamental frequency of blit.
|
||||
double hzFund = fMin;
|
||||
|
||||
private:
|
||||
void shiftUp();
|
||||
void shiftDown();
|
||||
//Phase of wavetable.
|
||||
double tablePhase = 0.0;
|
||||
float volume = 0.0f;
|
||||
//Parameter in unit circle controlling pitch (varies by one octave).
|
||||
double spectrumPhase = 1.0f;
|
||||
//Revolutions per second of spectrum phase.
|
||||
double spectrumPhaseVelocity = 0.0;
|
||||
//Lowest fundamental frequency of blit.
|
||||
double fMin = 55.0;
|
||||
|
||||
double falloff = 1.0;
|
||||
|
||||
double hzNyq = 24000.0;
|
||||
double sampleInterval = 1.0 / 48000.0;
|
||||
};
|
|
@ -0,0 +1,143 @@
|
|||
#include "ui.h"
|
||||
#include <format>
|
||||
#include <algorithm>
|
||||
|
||||
START_NAMESPACE_DISTRHO
|
||||
|
||||
static constexpr uint kInitialWidth = 800;
|
||||
static constexpr uint kInitialHeight = 600;
|
||||
|
||||
MouseUI::MouseUI()
|
||||
: UI(kInitialWidth, kInitialHeight)
|
||||
{
|
||||
|
||||
#ifdef DGL_NO_SHARED_RESOURCES
|
||||
createFontFromFile("sans", "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf");
|
||||
#else
|
||||
loadSharedResources();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void MouseUI::parameterChanged(uint32_t index, float value)
|
||||
{
|
||||
|
||||
if (index < kParameterCount)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case (kHz):
|
||||
hz = value; break;
|
||||
case (kMouseX):
|
||||
x = value; break;
|
||||
case (kMouseY):
|
||||
y = value; break;
|
||||
case (kVolume):
|
||||
vol = value; break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MouseUI::uiIdle()
|
||||
{
|
||||
}
|
||||
|
||||
bool MouseUI::onMouse(const MouseEvent &ev)
|
||||
{
|
||||
record = ev.press;
|
||||
repaint();
|
||||
return false; // Allow event to propagate.
|
||||
}
|
||||
|
||||
bool MouseUI::onMotion(const MotionEvent &ev)
|
||||
{
|
||||
if(!record) return false;
|
||||
|
||||
x = std::clamp(ev.pos.getX() / (1.0 * getWidth()), 0.0, 1.0);
|
||||
y = std::clamp(ev.pos.getY() / (1.0 * getHeight()), 0.0, 1.0);
|
||||
setParameterValue(kMouseX, x);
|
||||
setParameterValue(kMouseY, y);
|
||||
repaint();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MouseUI::onResize(const ResizeEvent &ev)
|
||||
{
|
||||
return UI::onResize(ev);
|
||||
}
|
||||
|
||||
void MouseUI::onNanoDisplay()
|
||||
{
|
||||
fontSize(15.0f);
|
||||
textLineHeight(1.f);
|
||||
|
||||
// Numerical feedback.
|
||||
beginPath();
|
||||
fillColor(200, 200, 200);
|
||||
textBox(0.f, 15.f, 250.f,
|
||||
std::format("Frequency: {:.3f}\nNearest: {:.3f}\n",
|
||||
hz, scale.getNearestNoteNumber(hz))
|
||||
.c_str(),
|
||||
nullptr);
|
||||
closePath();
|
||||
|
||||
beginPath();
|
||||
fillColor(200, 200, 200);
|
||||
textBox(0.f, 45.f, 250.f,
|
||||
std::format("x: {:.3f}\ny: {:.3f}\n",
|
||||
x * getWidth(), y * getHeight())
|
||||
.c_str(),
|
||||
nullptr);
|
||||
closePath();
|
||||
|
||||
|
||||
// Pen position and pressure.
|
||||
drawCircle(x, y, 0.f, 0.5f);
|
||||
}
|
||||
|
||||
void MouseUI::drawCircle(float x, float y, float z = 0.f, float p = 1.f)
|
||||
{
|
||||
|
||||
static constexpr float circleRadius = 25.f;
|
||||
x *= getWidth();
|
||||
y *= getHeight();
|
||||
z = 1.f - z;
|
||||
|
||||
beginPath();
|
||||
strokeColor(1.f, 1.f, 1.f, 0.5f);
|
||||
moveTo(x - z * circleRadius, y);
|
||||
lineTo(x + z * circleRadius, y);
|
||||
stroke();
|
||||
closePath();
|
||||
|
||||
beginPath();
|
||||
strokeColor(1.f, 1.f, 1.f, 0.5f);
|
||||
moveTo(x, y - z * circleRadius);
|
||||
lineTo(x, y + z * circleRadius);
|
||||
stroke();
|
||||
closePath();
|
||||
|
||||
beginPath();
|
||||
fillColor(1.f, 1.f, 1.f, p);
|
||||
strokeColor(255, 255, 255, 255);
|
||||
circle(x, y, circleRadius);
|
||||
fill();
|
||||
stroke();
|
||||
closePath();
|
||||
|
||||
beginPath();
|
||||
strokeColor(1.f, 1.f, 1.f, z);
|
||||
circle(x, y, z * circleRadius);
|
||||
stroke();
|
||||
closePath();
|
||||
}
|
||||
|
||||
UI *createUI()
|
||||
{
|
||||
return new MouseUI();
|
||||
}
|
||||
|
||||
END_NAMESPACE_DISTRHO
|
|
@ -0,0 +1,37 @@
|
|||
#include "DistrhoUI.hpp"
|
||||
#include "scale.h"
|
||||
#ifdef DEBUG
|
||||
#include <format>
|
||||
#include <optional>
|
||||
#endif
|
||||
|
||||
START_NAMESPACE_DISTRHO
|
||||
|
||||
class MouseUI : public UI
|
||||
{
|
||||
public:
|
||||
explicit MouseUI();
|
||||
|
||||
//void buttonClicked(SubWidget *const widget, int) override;
|
||||
void parameterChanged(uint32_t index, float value) override;
|
||||
|
||||
void uiIdle() override;
|
||||
bool onMouse(const MouseEvent &ev) override;
|
||||
bool onMotion(const MotionEvent &ev) override;
|
||||
void onResize(const ResizeEvent &ev) override;
|
||||
void onNanoDisplay() override;
|
||||
|
||||
private:
|
||||
void drawCircle(float x, float y, float z, float p);
|
||||
|
||||
bool record = false;
|
||||
float x = 0.f;
|
||||
float y = 0.f;
|
||||
float vol = 0.f;
|
||||
float hz = 55.f;
|
||||
Scale scale{440.0};
|
||||
|
||||
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MouseUI)
|
||||
};
|
||||
|
||||
END_NAMESPACE_DISTRHO
|
Loading…
Reference in New Issue