Harmonic Shepard tone, y axis controls pitch.

This commit is contained in:
yaw-man 2022-09-18 13:50:35 -04:00
parent bfe17282d1
commit 645ce2e092
8 changed files with 88 additions and 14 deletions

View File

@ -2,6 +2,28 @@
#define YAW_SCALE_INCLUDED #define YAW_SCALE_INCLUDED
#include <vector> #include <vector>
#include <string>
struct Note
{
std::string name;
double hz;
unsigned int number;
};
static std::vector<std::string> defaultNoteNames = {
"A",
"A#",
"B",
"C",
"C#",
"D",
"D#",
"E",
"F",
"F#",
"G",
"G#"};
class Scale class Scale
{ {
@ -10,6 +32,7 @@ class Scale
// freqs in hz, periods in samples // freqs in hz, periods in samples
std::vector<double> frequencies; std::vector<double> frequencies;
std::vector<double> periods; std::vector<double> periods;
std::vector<std::string> names;
public: public:
void newSampleRate(double rate) void newSampleRate(double rate)
@ -23,16 +46,18 @@ public:
// Default ctor: 12TET @ 48kHz // Default ctor: 12TET @ 48kHz
Scale(double hz = 440.0) Scale(double hz = 440.0)
{ {
hz /= 32.0; hz /= 64.0;
for (int i = 0; i < 12 * 8; ++i) for (int i = 0; i < 12 * 8; ++i)
{ {
frequencies.push_back(hz); frequencies.push_back(hz);
periods.push_back(sampleRate / hz); periods.push_back(sampleRate / hz);
names.push_back(defaultNoteNames[i % 12]);
hz *= exp2(1.0 / 12.0); hz *= exp2(1.0 / 12.0);
} }
} }
double getNearestPeriod(double period) double getNearestPeriod(double period)
{ {
for (auto note : periods) for (auto note : periods)
@ -45,6 +70,26 @@ public:
return 0; return 0;
} }
unsigned int getNearestNoteNumber(double hz)
{
for ( unsigned int i = 0; i < frequencies.size(); ++i )
{
if (hz < frequencies[i]) return i;
}
return 0;
}
double getNearestFrequency(double hz)
{
for (auto note : frequencies)
{
if (hz < note) return note;
}
return 0;
}
// TODO: parse scala files. new ctor that parses arbitrary scales // TODO: parse scala files. new ctor that parses arbitrary scales
}; };

View File

@ -10,6 +10,7 @@ dpf_add_plugin(yaw-tab-shepard
target_include_directories(yaw-tab-shepard PUBLIC target_include_directories(yaw-tab-shepard PUBLIC
"." "."
"../../lib/wintab") "../../lib/wintab"
"../../lib/scale")
target_compile_definitions(yaw-tab-shepard PUBLIC YAW_USE_WINTAB) target_compile_definitions(yaw-tab-shepard PUBLIC YAW_USE_WINTAB)

View File

@ -19,7 +19,8 @@ enum Parameters {
kParameterButtonA, kParameterButtonA,
kParameterButtonB, kParameterButtonB,
kParameterTime, kParameterTime,
kParameterCount kFundamentalFrequency,
kParameterCount,
}; };
#endif // DISTRHO_PLUGIN_INFO_H_INCLUDED #endif // DISTRHO_PLUGIN_INFO_H_INCLUDED

View File

@ -18,7 +18,7 @@ protected:
const char* getLabel() const override { return "yaw-shepard"; } const char* getLabel() const override { return "yaw-shepard"; }
const char* getDescription() const override { return "Generalized Shepard tone, tablet UI"; } 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-shepard"; }
const char* getLicense() const override { return "Fuck You Pay Me"; } 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'); }
@ -33,6 +33,13 @@ protected:
switch (index) switch (index)
{ {
case kFundamentalFrequency:
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 ktpax: case ktpax:
parameter.name = "x"; parameter.name = "x";
parameter.symbol = "x"; parameter.symbol = "x";
@ -105,6 +112,7 @@ protected:
synth.process(*outputs, frames); synth.process(*outputs, frames);
parity = !parity; parity = !parity;
fParameters[kParameterTime] += parity ? 1 : -1; fParameters[kParameterTime] += parity ? 1 : -1;
fParameters[kFundamentalFrequency] = synth.hzFund;
} }
private: private:

View File

@ -57,7 +57,7 @@ void Synth::process(float* output, const uint32_t frames)
for(uint32_t i = 0; i < frames; i++){ for(uint32_t i = 0; i < frames; i++){
//Set pitch. //Set pitch.
hz = hzFund = spectrumPhase * fMin + fMin; hz = hzFund = exp2(spectrumPhase) * fMin;
bool isOddHarmonic = true; bool isOddHarmonic = true;
@ -80,7 +80,7 @@ void Synth::process(float* output, const uint32_t frames)
isOddHarmonic = !isOddHarmonic; isOddHarmonic = !isOddHarmonic;
} }
output[i] *= volume / static_cast<double>(NUM_VOICES); output[i] *= 32 * volume / static_cast<double>(NUM_VOICES);
//Wrapping. //Wrapping.
spectrumPhase += spectrumPhaseVelocity * sampleInterval; spectrumPhase += spectrumPhaseVelocity * sampleInterval;
@ -102,8 +102,10 @@ void Synth::setPhaseVelocity(double in){
spectrumPhaseVelocity = 2.0 * (in - 0.5); spectrumPhaseVelocity = 2.0 * (in - 0.5);
} }
//Vary pitch of fundamental in a non-wrapping kinda way.
//Current range is six octaves, current bottom is 110.0 Hz (A3)
void Synth::setTimbre(double in){ void Synth::setTimbre(double in){
fMin = exp2(in * 2.0) * 110.0;
} }
void Synth::setVolume(double in){ void Synth::setVolume(double in){

View File

@ -7,7 +7,7 @@ struct Voice
double phase; double phase;
}; };
constexpr unsigned int NUM_VOICES = 256; constexpr unsigned int NUM_VOICES = 512;
constexpr unsigned int VOICE_MASK = NUM_VOICES - 1; constexpr unsigned int VOICE_MASK = NUM_VOICES - 1;
class Synth class Synth
@ -22,6 +22,9 @@ public:
void shiftUp(); void shiftUp();
void shiftDown(); void shiftDown();
//Current fundamental frequency of blit.
double hzFund = fMin;
private: private:
//Phase must persist between run invocation. //Phase must persist between run invocation.
//Phase of each voice, considered a function on the unit circle. //Phase of each voice, considered a function on the unit circle.
@ -32,9 +35,8 @@ private:
//Revolutions per second of spectrum phase. //Revolutions per second of spectrum phase.
double spectrumPhaseVelocity = 0.0; double spectrumPhaseVelocity = 0.0;
//Lowest fundamental frequency of blit. //Lowest fundamental frequency of blit.
static inline constexpr double fMin = 55.0; double fMin = 55.0;
//Current fundamental frequency of blit.
double hzFund = fMin;
double hzNyq = 24000.0; double hzNyq = 24000.0;
double sampleInterval = 1.0 / 48000.0; double sampleInterval = 1.0 / 48000.0;
}; };

View File

@ -1,6 +1,6 @@
#include "ui.h" #include "ui.h"
#ifdef DEBUG
#include <format> #include <format>
#ifdef DEBUG
#include <optional> #include <optional>
#endif #endif
@ -113,6 +113,8 @@ void TabUI::parameterChanged(uint32_t index, float value)
{ {
switch (index) switch (index)
{ {
case (kFundamentalFrequency):
hz = value;
case (ktpax): case (ktpax):
pkt.x = value; pkt.x = value;
case (ktpay): case (ktpay):
@ -125,8 +127,6 @@ void TabUI::parameterChanged(uint32_t index, float value)
return; return;
} }
getTabletData();
repaint();
} }
void TabUI::uiIdle() void TabUI::uiIdle()
@ -173,6 +173,16 @@ void TabUI::onNanoDisplay()
fontSize(15.0f); fontSize(15.0f);
textLineHeight(1.f); textLineHeight(1.f);
// Numerical feedback.
beginPath();
fillColor(200, 200, 200);
textBox(0.f, 15.f, 250.f,
std::format("Frequency: {:.3f}\nNearest: {}\n",
hz, scale.getNearestNoteNumber(hz))
.c_str(),
nullptr);
closePath();
// Report tablet errors. // Report tablet errors.
#ifdef DEBUG #ifdef DEBUG
if (!tab.initialized) if (!tab.initialized)

View File

@ -1,5 +1,6 @@
#include "DistrhoUI.hpp" #include "DistrhoUI.hpp"
#include "tablet.h" #include "tablet.h"
#include "scale.h"
#ifdef DEBUG #ifdef DEBUG
#include <format> #include <format>
#include <optional> #include <optional>
@ -63,6 +64,10 @@ private:
ButtonMappingWidget AButtonWidget; ButtonMappingWidget AButtonWidget;
ButtonMappingWidget BButtonWidget; ButtonMappingWidget BButtonWidget;
// For displaying frequency.
float hz = 55.f;
Scale scale{440.0};
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TabUI) DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TabUI)
}; };